summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/build/InnerObjectTestGen.scala2
-rw-r--r--src/compiler/scala/reflect/quasiquotes/Holes.scala2
-rw-r--r--src/compiler/scala/reflect/quasiquotes/Reifiers.scala2
-rw-r--r--src/compiler/scala/tools/nsc/PhaseAssembly.scala2
-rw-r--r--src/compiler/scala/tools/nsc/Properties.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/GenICode.scala4
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala52
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala4
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala19
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala35
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala22
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala8
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala17
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala4
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala450
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala4
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala12
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala38
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala369
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala189
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala4
-rw-r--r--src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala2
-rw-r--r--src/compiler/scala/tools/nsc/javac/JavaParsers.scala24
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaSettings.scala9
-rw-r--r--src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala2
-rw-r--r--src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/Logic.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Implicits.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Infer.scala2
-rwxr-xr-xsrc/compiler/scala/tools/nsc/util/DocStrings.scala2
-rw-r--r--src/library/scala/collection/generic/Sorted.scala2
-rw-r--r--src/library/scala/collection/immutable/Stream.scala2
-rw-r--r--src/library/scala/sys/BooleanProp.scala7
-rw-r--r--src/partest-extras/scala/tools/partest/ReplTest.scala12
-rw-r--r--src/reflect/scala/reflect/api/FlagSets.scala2
-rw-r--r--src/reflect/scala/reflect/api/Printers.scala2
-rw-r--r--src/reflect/scala/reflect/internal/ClassfileConstants.scala13
-rw-r--r--src/reflect/scala/reflect/internal/ReificationSupport.scala2
-rw-r--r--src/reflect/scala/reflect/internal/tpe/TypeMaps.scala2
-rw-r--r--src/reflect/scala/reflect/internal/util/StripMarginInterpolator.scala2
-rw-r--r--src/repl/scala/tools/nsc/interpreter/Formatting.scala27
-rw-r--r--src/repl/scala/tools/nsc/interpreter/ILoop.scala110
-rw-r--r--src/repl/scala/tools/nsc/interpreter/IMain.scala19
-rw-r--r--src/repl/scala/tools/nsc/interpreter/JavapClass.scala6
-rw-r--r--src/repl/scala/tools/nsc/interpreter/Pasted.scala22
-rw-r--r--src/repl/scala/tools/nsc/interpreter/ReplProps.scala11
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala2
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala2
52 files changed, 1235 insertions, 306 deletions
diff --git a/src/build/InnerObjectTestGen.scala b/src/build/InnerObjectTestGen.scala
index b66112609c..e0b889c969 100644
--- a/src/build/InnerObjectTestGen.scala
+++ b/src/build/InnerObjectTestGen.scala
@@ -22,7 +22,7 @@ object Contexts extends Enumeration {
* object, or equivalent).
*
* Usage: TestGen <nr of levels>
- * - by default it's 2 leves. Currently, 3-level deep uncovers bugs in the type checker.
+ * - by default it's 2 levels. Currently, 3-level deep uncovers bugs in the type checker.
*
* @author Iulian Dragos
*/
diff --git a/src/compiler/scala/reflect/quasiquotes/Holes.scala b/src/compiler/scala/reflect/quasiquotes/Holes.scala
index 6fa6b9b37a..47084fc317 100644
--- a/src/compiler/scala/reflect/quasiquotes/Holes.scala
+++ b/src/compiler/scala/reflect/quasiquotes/Holes.scala
@@ -151,7 +151,7 @@ trait Holes { self: Quasiquotes =>
else None
}
- /** Map high-rank unquotee onto an expression that eveluates as a list of given rank.
+ /** Map high-rank unquotee onto an expression that evaluates as a list of given rank.
*
* All possible combinations of representations are given in the table below:
*
diff --git a/src/compiler/scala/reflect/quasiquotes/Reifiers.scala b/src/compiler/scala/reflect/quasiquotes/Reifiers.scala
index e753c9787a..8462debe21 100644
--- a/src/compiler/scala/reflect/quasiquotes/Reifiers.scala
+++ b/src/compiler/scala/reflect/quasiquotes/Reifiers.scala
@@ -322,7 +322,7 @@ trait Reifiers { self: Quasiquotes =>
* in the domain of the fill function;
*
* 2. fold the groups into a sequence of lists added together with ++ using
- * fill reification for holeMap and fallback reification for non-holeMap.
+ * fill reification for holeMap and fallback reification for non-holeMap.
*
* Example:
*
diff --git a/src/compiler/scala/tools/nsc/PhaseAssembly.scala b/src/compiler/scala/tools/nsc/PhaseAssembly.scala
index 4b32aab5ee..ef9818c62d 100644
--- a/src/compiler/scala/tools/nsc/PhaseAssembly.scala
+++ b/src/compiler/scala/tools/nsc/PhaseAssembly.scala
@@ -226,7 +226,7 @@ trait PhaseAssembly {
}
/** Given the phases set, will build a dependency graph from the phases set
- * Using the aux. method of the DependencyGraph to create nodes and egdes.
+ * Using the aux. method of the DependencyGraph to create nodes and edges.
*/
private def phasesSetToDepGraph(phsSet: mutable.HashSet[SubComponent]): DependencyGraph = {
val graph = new DependencyGraph()
diff --git a/src/compiler/scala/tools/nsc/Properties.scala b/src/compiler/scala/tools/nsc/Properties.scala
index 9f160e2485..ca7d8776d4 100644
--- a/src/compiler/scala/tools/nsc/Properties.scala
+++ b/src/compiler/scala/tools/nsc/Properties.scala
@@ -13,7 +13,7 @@ object Properties extends scala.util.PropertiesTrait {
// settings based on jar properties, falling back to System prefixed by "scala."
def residentPromptString = scalaPropOrElse("resident.prompt", "\nnsc> ")
- def shellPromptString = scalaPropOrElse("shell.prompt", "\nscala> ")
+ def shellPromptString = scalaPropOrElse("shell.prompt", "%nscala> ")
// message to display at EOF (which by default ends with
// a newline so as not to break the user's terminal)
def shellInterruptedString = scalaPropOrElse("shell.interrupted", f":quit$lineSeparator")
diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
index 137954b52d..3e23291e92 100644
--- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
+++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
@@ -1022,7 +1022,7 @@ abstract class GenICode extends SubComponent {
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 emitt a redundant [DROP, CONSTANT(null)]
+ // 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)
@@ -2108,7 +2108,7 @@ abstract class GenICode extends SubComponent {
/**
* Represent a label in the current method code. In order
* to support forward jumps, labels can be created without
- * having a deisgnated target block. They can later be attached
+ * having a designated target block. They can later be attached
* by calling `anchor`.
*/
class Label(val symbol: Symbol) {
diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala
index 058b6a161d..64c9901a3e 100644
--- a/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala
+++ b/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala
@@ -387,7 +387,7 @@ abstract class TypeFlowAnalysis {
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 decsision is described in `populatePerimeter()`. All `blockTransfer()` needs to do (in order to know at which instruction it can stop)
+ 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.
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
index eadc404bee..dec5adc9aa 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
@@ -9,6 +9,7 @@ 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
@@ -229,6 +230,44 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
}
/**
+ * 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(
+ 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.hasEnumFlag && classSym.hasAbstractFlag) ACC_ABSTRACT else 0,
+ if (classSym.isArtifact) ACC_SYNTHETIC else 0,
+ if (classSym.hasEnumFlag) 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.
@@ -399,3 +438,16 @@ final class BCodeAsmCommon[G <: Global](val global: G) {
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/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
index 8f2a17a2bf..eb0da7caef 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala
@@ -439,7 +439,7 @@ abstract class BCodeIdiomatic extends SubComponent {
else { emitTypeBased(JCodeMethodN.returnOpcodes, tk) }
}
- /* Emits one of tableswitch or lookoupswitch.
+ /* Emits one of tableswitch or lookupswitch.
*
* 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 a2fd22d24c..d2d510e8a9 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
@@ -90,7 +90,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
override def getCurrentCUnit(): CompilationUnit = { cunit }
- /* ---------------- helper utils for generating classes and fiels ---------------- */
+ /* ---------------- helper utils for generating classes and fields ---------------- */
def genPlainClass(cd: ClassDef) {
assert(cnode == null, "GenBCode detected nested methods.")
@@ -140,7 +140,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern))
AsmUtils.traceClass(cnode)
- if (settings.YoptInlinerEnabled) {
+ if (settings.YoptAddToBytecodeRepository) {
// The inliner needs to find all classes in the code repo, also those being compiled
byteCodeRepository.add(cnode, ByteCodeRepository.CompilationUnit)
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
index e61190bf3a..7f0aaa7305 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
@@ -44,6 +44,8 @@ abstract class BTypes {
val inliner: Inliner[this.type]
+ val closureOptimizer: ClosureOptimizer[this.type]
+
val callGraph: CallGraph[this.type]
val backendReporting: BackendReporting
@@ -898,7 +900,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
- ) & ClassBType.INNER_CLASSES_FLAGS
+ ) & BCodeAsmCommon.INNER_CLASSES_FLAGS
)
})
@@ -940,7 +942,7 @@ abstract class BTypes {
*/
def jvmWiseLUB(other: ClassBType): Either[NoClassBTypeInfo, ClassBType] = {
def isNotNullOrNothing(c: ClassBType) = !c.isNullType && !c.isNothingType
- assert(isNotNullOrNothing(this) && isNotNullOrNothing(other), s"jvmWiseLub for null or nothing: $this - $other")
+ assert(isNotNullOrNothing(this) && isNotNullOrNothing(other), s"jvmWiseLUB for null or nothing: $this - $other")
tryEither {
val res: ClassBType = (this.isInterface.orThrow, other.isInterface.orThrow) match {
@@ -965,7 +967,7 @@ abstract class BTypes {
firstCommonSuffix(this :: this.superClassesTransitive.orThrow, other :: other.superClassesTransitive.orThrow)
}
- assert(isNotNullOrNothing(res), s"jvmWiseLub computed: $res")
+ assert(isNotNullOrNothing(res), s"jvmWiseLUB computed: $res")
Right(res)
}
}
@@ -987,17 +989,6 @@ abstract class BTypes {
}
object ClassBType {
- /**
- * Valid flags for InnerClass attribute entry.
- * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6
- */
- private 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
- }
-
// Primitive classes have no super class. A ClassBType for those is only created when
// they are actually being compiled (e.g., when compiling scala/Boolean.scala).
private val hasNoSuper = Set(
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
index fffb9286b8..5f8f0e167c 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala
@@ -7,7 +7,7 @@ package scala.tools.nsc
package backend.jvm
import scala.tools.asm
-import scala.tools.nsc.backend.jvm.opt.{LocalOpt, CallGraph, Inliner, ByteCodeRepository}
+import scala.tools.nsc.backend.jvm.opt._
import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo, InternalName}
import BackendReporting._
import scala.tools.nsc.settings.ScalaSettings
@@ -42,6 +42,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
val inliner: Inliner[this.type] = new Inliner(this)
+ val closureOptimizer: ClosureOptimizer[this.type] = new ClosureOptimizer(this)
+
val callGraph: CallGraph[this.type] = new CallGraph(this)
val backendReporting: BackendReporting = new BackendReportingImpl(global)
@@ -213,35 +215,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
assert(!primitiveTypeMap.contains(sym) || isCompilingPrimitive, sym)
}
- /**
- * Reconstruct the classfile flags from a Java defined class symbol.
- *
- * The implementation of this method is slightly different that [[javaFlags]]. 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
- */
- private def javaClassfileFlags(classSym: Symbol): Int = {
- assert(classSym.isJava, s"Expected Java class symbol, got ${classSym.fullName}")
- import asm.Opcodes._
- GenBCode.mkFlags(
- if (classSym.isPublic) ACC_PUBLIC else 0,
- if (classSym.isFinal) ACC_FINAL else 0,
- if (classSym.isInterface) ACC_INTERFACE else ACC_SUPER, // see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces.
- if (classSym.hasAbstractFlag) ACC_ABSTRACT else 0,
- if (classSym.isArtifact) ACC_SYNTHETIC else 0,
- if (classSym.hasEnumFlag) ACC_ENUM else 0
- )
- }
-
private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = {
val superClassSym = if (classSym.isImplClass) ObjectClass else classSym.superClass
assert(
@@ -322,7 +295,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
val javaCompatMembers = {
if (linkedClass != NoSymbol && isTopLevelModuleClass(linkedClass))
// phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only sees member
- // classes, not local classes of the companion module (E in the exmaple) that were lifted by lambdalift.
+ // classes, not local classes of the companion module (E in the example) that were lifted by lambdalift.
exitingPickler(memberClassesForInnerClassTable(linkedClass))
else
Nil
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
index d641f708d2..4fc05cafdc 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
@@ -247,6 +247,28 @@ object BackendReporting {
callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning
/**
+ * Used in `rewriteClosureApplyInvocations` when a closure apply callsite cannot be rewritten
+ * to the closure body method.
+ */
+ trait RewriteClosureApplyToClosureBodyFailed extends OptimizerWarning {
+ def pos: Position
+
+ override def emitWarning(settings: ScalaSettings): Boolean = this match {
+ case RewriteClosureAccessCheckFailed(_, cause) => cause.emitWarning(settings)
+ case RewriteClosureIllegalAccess(_, _) => settings.YoptWarningEmitAtInlineFailed
+ }
+
+ override def toString: String = this match {
+ case RewriteClosureAccessCheckFailed(_, cause) =>
+ s"Failed to rewrite the closure invocation to its implementation method:\n" + cause
+ case RewriteClosureIllegalAccess(_, callsiteClass) =>
+ s"The closure body invocation cannot be rewritten because the target method is not accessible in class $callsiteClass."
+ }
+ }
+ case class RewriteClosureAccessCheckFailed(pos: Position, cause: OptimizerWarning) extends RewriteClosureApplyToClosureBodyFailed
+ case class RewriteClosureIllegalAccess(pos: Position, callsiteClass: InternalName) extends RewriteClosureApplyToClosureBodyFailed
+
+ /**
* Used in the InlineInfo of a ClassBType, when some issue occurred obtaining the inline information.
*/
sealed trait ClassInlineInfoWarning extends OptimizerWarning {
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
index 76af40b330..71686fd9d7 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
@@ -479,10 +479,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
val CLASS_CONSTRUCTOR_NAME = "<clinit>"
val INSTANCE_CONSTRUCTOR_NAME = "<init>"
- val INNER_CLASSES_FLAGS =
- (asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED |
- asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_INTERFACE | asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_FINAL)
-
// -----------------------------------------------------------------------------------------
// factory methods
// -----------------------------------------------------------------------------------------
@@ -756,9 +752,9 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self =>
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,
- javaFlags(innerSym),
+ (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
- ) & (INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED)
+ ) & (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
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
index c6ee36d7b2..455117d837 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
@@ -216,12 +216,17 @@ abstract class GenBCode extends BCodeSyncAndTry {
class Worker2 {
def runGlobalOptimizations(): Unit = {
import scala.collection.convert.decorateAsScala._
- 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)
+ 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)
+ }
}
- bTypes.inliner.runInliner()
+ if (settings.YoptInlinerEnabled)
+ bTypes.inliner.runInliner()
+ if (settings.YoptClosureElimination)
+ closureOptimizer.rewriteClosureApplyInvocations()
}
def localOptimizations(classNode: ClassNode): Unit = {
@@ -229,7 +234,7 @@ abstract class GenBCode extends BCodeSyncAndTry {
}
def run() {
- if (settings.YoptInlinerEnabled) runGlobalOptimizations()
+ runGlobalOptimizations()
while (true) {
val item = q2.poll
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 98e93c125b..8d8ea839e6 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala
@@ -94,7 +94,7 @@ object InstructionStackEffect {
val isSize2 = peekStack(0).getSize == 2
if (isSize2) t(1, 0) else t(2, 0)
- case DUP => t(0, 1)
+ case DUP => t(1, 2)
case DUP_X1 => t(2, 3)
@@ -104,7 +104,7 @@ object InstructionStackEffect {
case DUP2 =>
val isSize2 = peekStack(0).getSize == 2
- if (isSize2) t(0, 1) else t(0, 2)
+ if (isSize2) t(1, 2) else t(2, 4)
case DUP2_X1 =>
val isSize2 = peekStack(0).getSize == 2
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala
new file mode 100644
index 0000000000..40f91cbed4
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala
@@ -0,0 +1,450 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2015 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+package analysis
+
+import java.util
+
+import scala.annotation.switch
+import scala.collection.mutable
+import scala.tools.asm.{Type, MethodVisitor}
+import scala.tools.asm.Opcodes._
+import scala.tools.asm.tree._
+import scala.tools.asm.tree.analysis._
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
+
+import opt.BytecodeUtils._
+
+import scala.collection.convert.decorateAsScala._
+
+/**
+ * This class provides additional queries over ASM's built-in `SourceValue` analysis.
+ *
+ * The analysis computes for each value in a frame a set of source instructions, which are the
+ * potential producers. Most instructions produce either nothing or a stack value. For example,
+ * a `LOAD` instruction is the producer of the value pushed onto the stack. The exception are
+ * `STORE` instructions, which produce a new value for a local variable slot, so they are used
+ * as producers for the value they stored.
+ *
+ * Note that pseudo-instructions are used as initial producers for parameters and local variables.
+ * See the documentation on class InitialProducer.
+ *
+ * This class implements the following queries over the data computed by the SourceValue analysis:
+ *
+ * - producersForValueAt(insn, slot)
+ * - consumersOfValueAt(insn, slot)
+ *
+ * - producersForInputsOf(insn)
+ * - consumersOfOutputsFrom(insn)
+ *
+ * - initialProducersForValueAt(insn, slot)
+ * - ultimateConsumersOfValueAt(insn, slot)
+ *
+ * - initialProducersForInputsOf(insn)
+ * - ultimateConsumersOfOutputsFrom(insn)
+ *
+ * The following operations are considered as copying operations:
+ * - xLOAD, xSTORE
+ * - DUP, DUP2, DUP_X1, DUP_X2, DUP2_X1, DUP2_X2
+ * - SWAP
+ * - CHECKCAST
+ *
+ * If ever needed, we could introduce a mode where primitive conversions (l2i) are considered as
+ * copying operations.
+ */
+class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) {
+ val analyzer = new Analyzer(new InitialProducerSourceInterpreter)
+ analyzer.analyze(classInternalName, methodNode)
+
+ def frameAt(insn: AbstractInsnNode) = analyzer.frameAt(insn, methodNode)
+
+ /**
+ * Returns the potential producer instructions of a (local or stack) value in the frame of `insn`.
+ * This method simply returns the producer information computed by the SourceValue analysis.
+ */
+ def producersForValueAt(insn: AbstractInsnNode, slot: Int): Set[AbstractInsnNode] = {
+ frameAt(insn).getValue(slot).insns.asScala.toSet
+ }
+
+ /**
+ * Returns the potential consumer instructions of a (local or stack) value in the frame of `insn`.
+ * This is the counterpart of `producersForValueAt`.
+ */
+ def consumersOfValueAt(insn: AbstractInsnNode, slot: Int): Set[AbstractInsnNode] = {
+ producersForValueAt(insn, slot).flatMap(prod => {
+ val outputNumber = outputValueSlots(prod).indexOf(slot)
+ _consumersOfOutputsFrom.get(prod).map(v => {
+ v(outputNumber)
+ }).getOrElse(Set.empty)
+ })
+ }
+
+ /**
+ * Returns the potential producer instructions of any of the values consumed by `insn`.
+ */
+ def producersForInputsOf(insn: AbstractInsnNode): Set[AbstractInsnNode] = {
+ 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)
+
+ /**
+ * Returns the potential initial producer instructions of a value in the frame of `insn`.
+ *
+ * Unlike `producersForValueAt`, producers are tracked through copying instructions such as STORE
+ * and LOAD. If the producer of the value is a LOAD, then the producers of the stored value(s) are
+ * returned instead.
+ */
+ def initialProducersForValueAt(insn: AbstractInsnNode, slot: Int): Set[AbstractInsnNode] = {
+ def initialProducers(insn: AbstractInsnNode, producedSlot: Int): Set[AbstractInsnNode] = {
+ if (isCopyOperation(insn)) {
+ _initialProducersCache.getOrElseUpdate((insn, producedSlot), {
+ val (sourceValue, sourceValueSlot) = copyOperationSourceValue(insn, producedSlot)
+ sourceValue.insns.iterator.asScala.flatMap(initialProducers(_, sourceValueSlot)).toSet
+ })
+ } else {
+ Set(insn)
+ }
+ }
+ producersForValueAt(insn, slot).flatMap(initialProducers(_, slot))
+ }
+
+ /**
+ * Returns the potential ultimate consumers of a value in the frame of `insn`. Consumers are
+ * tracked through copying operations such as SOTRE and LOAD.
+ */
+ def ultimateConsumersOfValueAt(insn: AbstractInsnNode, slot: Int): Set[AbstractInsnNode] = {
+ def ultimateConsumers(insn: AbstractInsnNode, consumedSlot: Int): Set[AbstractInsnNode] = {
+ if (isCopyOperation(insn)) {
+ _ultimateConsumersCache.getOrElseUpdate((insn, consumedSlot), {
+ for {
+ producedSlot <- copyOperationProducedValueSlots(insn, consumedSlot)
+ consumer <- consumersOfValueAt(insn.getNext, producedSlot)
+ ultimateConsumer <- ultimateConsumers(consumer, producedSlot)
+ } yield ultimateConsumer
+ })
+ } else {
+ Set(insn)
+ }
+ }
+ consumersOfValueAt(insn, slot).flatMap(ultimateConsumers(_, slot))
+ }
+
+ def initialProducersForInputsOf(insn: AbstractInsnNode): Set[AbstractInsnNode] = {
+ 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
+ }
+
+ private def isCopyOperation(insn: AbstractInsnNode): Boolean = {
+ isVarInstruction(insn) || {
+ (insn.getOpcode: @switch) match {
+ case DUP | DUP_X1 | DUP_X2 | DUP2 | DUP2_X1 | DUP2_X2 | SWAP | CHECKCAST => true
+ case _ => false
+ }
+ }
+ }
+
+ /**
+ * Returns the value and its frame slot that `copyOp` copies into `producedSlot`.
+ *
+ * Example:
+ * - copyOp = DUP_X1, assume it produces slots 2,3,4
+ * - producedSlot = 3
+ * - the result is the value at slot 2 in the frame of `copyOp`
+ */
+ private def copyOperationSourceValue(copyOp: AbstractInsnNode, producedSlot: Int): (SourceValue, Int) = {
+ val frame = frameAt(copyOp)
+
+ // Index of the produced value. Example: DUP_X1 produces 3 values, so producedIndex is 0, 1 or 2,
+ // where 0 corresponds to the lowest value on the stack.
+ def producedIndex(numConsumed: Int) = {
+ val numUsedSlotsBeforeCopy = frame.stackTop + 1
+ producedSlot - (numUsedSlotsBeforeCopy - numConsumed)
+ }
+
+ def stackValue(n: Int) = (frame.peekStack(n), frame.stackTop - n)
+
+ def dupX1Case = (producedIndex(2): @switch) match {
+ case 0 | 2 => stackValue(0)
+ case 1 => stackValue(1)
+ }
+
+ // Form 1 of dup_x2
+ def dupX2Case = (producedIndex(3): @switch) match {
+ case 0 | 3 => stackValue(0)
+ case 1 => stackValue(2)
+ case 2 => stackValue(1)
+ }
+
+ // Form 1 of dup2_x1
+ def dup2X1Case = (producedIndex(3): @switch) match {
+ case 0 | 3 => stackValue(1)
+ case 1 | 4 => stackValue(0)
+ case 2 => stackValue(2)
+ }
+
+ if (isLoad(copyOp)) {
+ val slot = copyOp.asInstanceOf[VarInsnNode].`var`
+ (frame.getLocal(slot), slot)
+ } else if (isStore(copyOp)) {
+ stackValue(0)
+ } else (copyOp.getOpcode: @switch) match {
+ case DUP =>
+ stackValue(0) // the current stack top is the source of both produced values
+
+ case DUP_X1 =>
+ dupX1Case
+
+ case DUP_X2 =>
+ if (frame.peekStack(1).getSize == 2) dupX1Case
+ else dupX2Case
+
+ case DUP2 =>
+ if (frame.peekStack(0).getSize == 2) stackValue(0)
+ else {
+ (producedIndex(2): @switch) match {
+ case 0 | 2 => stackValue(1)
+ case 1 | 3 => stackValue(0)
+ }
+ }
+
+ case DUP2_X1 =>
+ if (frame.peekStack(0).getSize == 2) dupX1Case
+ else dup2X1Case
+
+ case DUP2_X2 =>
+ val v1isSize2 = frame.peekStack(0).getSize == 2
+ if (v1isSize2) {
+ val v2isSize2 = frame.peekStack(1).getSize == 2
+ if (v2isSize2) dupX1Case // Form 4
+ else dupX2Case // Form 2
+ } else {
+ val v3isSize2 = frame.peekStack(2).getSize == 2
+ if (v3isSize2) dup2X1Case // Form 3
+ else {
+ // Form 1
+ (producedIndex(4): @switch) match {
+ case 0 | 4 => stackValue(1)
+ case 1 | 5 => stackValue(0)
+ case 2 => stackValue(3)
+ case 3 => stackValue(2)
+ }
+ }
+ }
+
+ case SWAP =>
+ if (producedIndex(2) == 0) stackValue(0)
+ else stackValue(1)
+
+ case CHECKCAST =>
+ stackValue(0)
+ }
+ }
+
+ /**
+ * Returns the value slots into which `copyOp` copies the value at `consumedSlot`.
+ *
+ * Example:
+ * - copyOp = DUP_X1, assume it consumes slots 2,3 and produces 2,3,4
+ * - if consumedSlot == 2, the result is Set(3)
+ * - if consumedSlot == 3, the result is Set(2, 4)
+ */
+ private def copyOperationProducedValueSlots(copyOp: AbstractInsnNode, consumedSlot: Int): Set[Int] = {
+ if (isStore(copyOp)) Set(copyOp.asInstanceOf[VarInsnNode].`var`)
+ else {
+ val nextFrame = frameAt(copyOp.getNext)
+ val top = nextFrame.stackTop
+
+ // Index of the consumed value. Example: DUP_X1 consumes two values, so consumedIndex is
+ // 0 or 1, where 0 corresponds to the lower value on the stack.
+ def consumedIndex(numProduced: Int) = {
+ val numUsedSlotsAfterCopy = top + 1
+ consumedSlot - (numUsedSlotsAfterCopy - numProduced)
+ }
+
+ def dupX1Case = (consumedIndex(3): @switch) match {
+ case 0 => Set(top - 1)
+ case 1 => Set(top - 2, top)
+ }
+
+ def dupX2Case = (consumedIndex(4): @switch) match {
+ case 0 => Set(top - 2)
+ case 1 => Set(top - 1)
+ case 2 => Set(top - 3, top)
+ }
+
+ def dup2X1Case = (consumedIndex(5): @switch) match {
+ case 0 => Set(top - 2)
+ case 1 => Set(top - 4, top - 1)
+ case 2 => Set(top - 3, top)
+ }
+
+ if (isLoad(copyOp)) Set(top)
+ else (copyOp.getOpcode: @switch) match {
+ case DUP =>
+ Set(top - 1, top)
+
+ case DUP_X1 =>
+ dupX1Case
+
+ case DUP_X2 =>
+ if (nextFrame.peekStack(1).getSize == 2) dupX1Case
+ else dupX2Case
+
+ case DUP2 =>
+ if (nextFrame.peekStack(0).getSize == 2) Set(top - 1, top)
+ else (consumedIndex(4): @switch) match {
+ case 0 => Set(top - 3, top - 1)
+ case 1 => Set(top - 2, top)
+ }
+
+ case DUP2_X1 =>
+ if (nextFrame.peekStack(0).getSize == 2) dupX1Case
+ else dup2X1Case
+
+ case DUP2_X2 =>
+ val v1isSize2 = nextFrame.peekStack(0).getSize == 2
+ if (v1isSize2) {
+ val v2isSize2 = nextFrame.peekStack(1).getSize == 2
+ if (v2isSize2) dupX1Case // Form 4
+ else dupX2Case // Form 2
+ } else {
+ val v3isSize2 = nextFrame.peekStack(2).getSize == 2
+ if (v3isSize2) dup2X1Case // Form 3
+ else {
+ // Form 1
+ (consumedIndex(6): @switch) match {
+ case 0 => Set(top - 3)
+ case 1 => Set(top - 2)
+ case 2 => Set(top - 5, top - 1)
+ case 3 => Set(top - 4, top)
+ }
+ }
+ }
+
+ case SWAP =>
+ if (consumedIndex(2) == 0) Set(top)
+ else Set(top - 1)
+
+ case CHECKCAST =>
+ Set(top)
+ }
+ }
+ }
+
+ /** Returns the frame values consumed by executing `insn`. */
+ private def inputValues(insn: AbstractInsnNode): Seq[SourceValue] = {
+ lazy val frame = frameAt(insn)
+ inputValueSlots(insn) map frame.getValue
+ }
+
+ /** Returns the frame slots holding the values consumed by executing `insn`. */
+ private def inputValueSlots(insn: AbstractInsnNode): Seq[Int] = {
+ if (insn.getOpcode == -1) return Seq.empty
+ if (isLoad(insn)) {
+ Seq(insn.asInstanceOf[VarInsnNode].`var`)
+ } else if (insn.getOpcode == IINC) {
+ Seq(insn.asInstanceOf[IincInsnNode].`var`)
+ } else {
+ val frame = frameAt(insn)
+ val stackEffect = InstructionStackEffect(insn, frame)
+ val stackSize = frame.getLocals + frame.getStackSize
+ (stackSize - stackEffect._1) until stackSize
+ }
+ }
+
+ /** Returns the frame slots holding the values produced by executing `insn`. */
+ 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 _ =>
+ if (insn.getOpcode == -1) return Seq.empty
+ if (isStore(insn)) {
+ Seq(insn.asInstanceOf[VarInsnNode].`var`)
+ } else if (insn.getOpcode == IINC) {
+ Seq(insn.asInstanceOf[IincInsnNode].`var`)
+ } else {
+ val frame = frameAt(insn)
+ val stackEffect = InstructionStackEffect(insn, frame)
+ val nextFrame = frameAt(insn.getNext)
+ val stackSize = nextFrame.getLocals + nextFrame.getStackSize
+ (stackSize - stackEffect._2) until stackSize
+ }
+ }
+
+ /** For each instruction, a set of potential consumers of the produced values. */
+ private lazy val _consumersOfOutputsFrom: Map[AbstractInsnNode, Vector[Set[AbstractInsnNode]]] = {
+ var res = Map.empty[AbstractInsnNode, Vector[Set[AbstractInsnNode]]]
+ for {
+ insn <- methodNode.instructions.iterator.asScala
+ frame = frameAt(insn)
+ i <- inputValueSlots(insn)
+ producer <- frame.getValue(i).insns.asScala
+ } {
+ val producedSlots = outputValueSlots(producer)
+ val currentConsumers = res.getOrElse(producer, Vector.fill(producedSlots.size)(Set.empty[AbstractInsnNode]))
+ val outputIndex = producedSlots.indexOf(i)
+ res = res.updated(producer, currentConsumers.updated(outputIndex, currentConsumers(outputIndex) + insn))
+ }
+ res
+ }
+
+ private val _initialProducersCache: mutable.AnyRefMap[(AbstractInsnNode, Int), Set[AbstractInsnNode]] = mutable.AnyRefMap.empty
+ private val _ultimateConsumersCache: mutable.AnyRefMap[(AbstractInsnNode, Int), Set[AbstractInsnNode]] = mutable.AnyRefMap.empty
+}
+
+/**
+ * A class for pseudo-instructions representing the initial producers of local values that have
+ * no producer instruction in the method:
+ * - parameters, including `this`
+ * - uninitialized local variables
+ * - exception values in handlers
+ *
+ * The ASM built-in SourceValue analysis yields an empty producers set for such values. This leads
+ * to ambiguities. Example (in Java one can re-assign parameter):
+ *
+ * void foo(int a) {
+ * if (a == 0) a = 1;
+ * return a;
+ * }
+ *
+ * In the first frame of the method, the SoruceValue 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
+ * 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.
+ */
+abstract class InitialProducer extends AbstractInsnNode(-1) {
+ override def getType: Int = throw new UnsupportedOperationException
+ override def clone(labels: util.Map[LabelNode, LabelNode]): AbstractInsnNode = throw new UnsupportedOperationException
+ 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
+
+class InitialProducerSourceInterpreter extends SourceInterpreter {
+ override def newParameterValue(isInstanceMethod: Boolean, local: Int, tp: Type): SourceValue = {
+ new SourceValue(tp.getSize, ParameterProducer(local))
+ }
+
+ override def newEmptyNonParameterLocalValue(local: Int): SourceValue = {
+ new SourceValue(1, UninitializedLocalProducer(local))
+ }
+
+ override def newExceptionValue(tryCatchBlockNode: TryCatchBlockNode, handlerFrame: Frame[_ <: Value], exceptionType: Type): SourceValue = {
+ new SourceValue(1, ExceptionProducer(handlerFrame))
+ }
+} \ No newline at end of file
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 607b7145d6..a5b85e54e7 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
@@ -102,7 +102,7 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav
}
/**
- * The method node for a method matching `name` and `descriptor`, accessed in class `classInternalName`.
+ * 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.
*
* @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring
@@ -127,7 +127,7 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav
case Nil => Left(failedClasses)
}
- // In a MethodInsnNode, the `owner` field may be an array descriptor, for exmple when invoking `clone`. We don't have a method node to return in this case.
+ // In a MethodInsnNode, the `owner` field may be an array descriptor, for example when invoking `clone`. We don't have a method node to return in this case.
if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[')
Left(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, Nil))
else
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 9bd016f964..0ec550981a 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
@@ -14,7 +14,6 @@ import scala.tools.asm.commons.CodeSizeEvaluator
import scala.tools.asm.tree.analysis._
import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes}
import scala.tools.asm.tree._
-import scala.collection.convert.decorateAsScala._
import GenBCode._
import scala.collection.convert.decorateAsScala._
import scala.collection.convert.decorateAsJava._
@@ -73,11 +72,18 @@ object BytecodeUtils {
op >= Opcodes.IRETURN && op <= Opcodes.RETURN
}
- def isVarInstruction(instruction: AbstractInsnNode): Boolean = {
+ def isLoad(instruction: AbstractInsnNode): Boolean = {
val op = instruction.getOpcode
- (op >= Opcodes.ILOAD && op <= Opcodes.ALOAD) || (op >= Opcodes.ISTORE && op <= Opcodes.ASTORE)
+ op >= Opcodes.ILOAD && op <= Opcodes.ALOAD
}
+ def isStore(instruction: AbstractInsnNode): Boolean = {
+ val op = instruction.getOpcode
+ op >= Opcodes.ISTORE && op <= Opcodes.ASTORE
+ }
+
+ def isVarInstruction(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction)
+
def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0
def isConstructor(methodNode: MethodNode): Boolean = {
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 0932564b1f..8abecdb261 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
@@ -11,6 +11,7 @@ import scala.reflect.internal.util.{NoPosition, Position}
import scala.tools.asm.tree.analysis.{Value, Analyzer, BasicInterpreter}
import scala.tools.asm.{Opcodes, Type}
import scala.tools.asm.tree._
+import scala.collection.concurrent
import scala.collection.convert.decorateAsScala._
import scala.tools.nsc.backend.jvm.BTypes.InternalName
import scala.tools.nsc.backend.jvm.BackendReporting._
@@ -21,14 +22,25 @@ import BytecodeUtils._
class CallGraph[BT <: BTypes](val btypes: BT) {
import btypes._
- val callsites: collection.concurrent.Map[MethodInsnNode, Callsite] = recordPerRunCache(collection.concurrent.TrieMap.empty[MethodInsnNode, Callsite])
+ val callsites: concurrent.Map[MethodInsnNode, Callsite] = recordPerRunCache(concurrent.TrieMap.empty)
+
+ val closureInstantiations: concurrent.Map[InvokeDynamicInsnNode, (MethodNode, ClassBType)] = recordPerRunCache(concurrent.TrieMap.empty)
def addClass(classNode: ClassNode): Unit = {
- for (m <- classNode.methods.asScala; callsite <- analyzeCallsites(m, classBTypeFromClassNode(classNode)))
- callsites(callsite.callsiteInstruction) = callsite
+ val classType = classBTypeFromClassNode(classNode)
+ for {
+ m <- classNode.methods.asScala
+ (calls, closureInits) = analyzeCallsites(m, classType)
+ } {
+ calls foreach (callsite => callsites(callsite.callsiteInstruction) = callsite)
+ closureInits foreach (indy => closureInstantiations(indy) = (m, classType))
+ }
}
- def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): List[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[InvokeDynamicInsnNode]) = {
case class CallsiteInfo(safeToInline: Boolean, safeToRewrite: Boolean,
annotatedInline: Boolean, annotatedNoInline: Boolean,
@@ -116,7 +128,10 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
case _ => false
}
- methodNode.instructions.iterator.asScala.collect({
+ val callsites = new collection.mutable.ListBuffer[Callsite]
+ val closureInstantiations = new collection.mutable.ListBuffer[InvokeDynamicInsnNode]
+
+ 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)]
@@ -147,7 +162,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
receiverNotNullByAnalysis(call, numArgs)
}
- Callsite(
+ callsites += Callsite(
callsiteInstruction = call,
callsiteMethod = methodNode,
callsiteClass = definingClass,
@@ -157,7 +172,14 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
receiverKnownNotNull = receiverNotNull,
callsitePosition = callsitePositions.getOrElse(call, NoPosition)
)
- }).toList
+
+ case indy: InvokeDynamicInsnNode =>
+ if (closureOptimizer.isClosureInstantiation(indy)) closureInstantiations += indy
+
+ case _ =>
+ }
+
+ (callsites.toList, closureInstantiations.toList)
}
/**
@@ -201,7 +223,7 @@ 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 the interface method of a concrete trait method
+ * @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
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
new file mode 100644
index 0000000000..1648a53ed8
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
@@ -0,0 +1,369 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2015 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import scala.annotation.switch
+import scala.reflect.internal.util.NoPosition
+import scala.tools.asm.{Handle, Type, Opcodes}
+import scala.tools.asm.tree._
+import scala.tools.nsc.backend.jvm.BTypes.InternalName
+import scala.tools.nsc.backend.jvm.analysis.ProdConsAnalyzer
+import BytecodeUtils._
+import BackendReporting._
+import Opcodes._
+import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository.CompilationUnit
+import scala.collection.convert.decorateAsScala._
+
+class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
+ import btypes._
+ import callGraph._
+
+ def rewriteClosureApplyInvocations(): Unit = {
+ closureInstantiations foreach {
+ case (indy, (methodNode, ownerClass)) =>
+ val warnings = rewriteClosureApplyInvocations(indy, methodNode, ownerClass)
+ warnings.foreach(w => backendReporting.inlinerWarning(w.pos, w.toString))
+ }
+ }
+
+ private val lambdaMetaFactoryInternalName: InternalName = "java/lang/invoke/LambdaMetafactory"
+
+ private val metafactoryHandle = {
+ val metafactoryMethodName: String = "metafactory"
+ val metafactoryDesc: String = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;"
+ new Handle(H_INVOKESTATIC, lambdaMetaFactoryInternalName, metafactoryMethodName, metafactoryDesc)
+ }
+
+ private val altMetafactoryHandle = {
+ val altMetafactoryMethodName: String = "altMetafactory"
+ val altMetafactoryDesc: String = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"
+ new Handle(H_INVOKESTATIC, lambdaMetaFactoryInternalName, altMetafactoryMethodName, altMetafactoryDesc)
+ }
+
+ def isClosureInstantiation(indy: InvokeDynamicInsnNode): Boolean = {
+ (indy.bsm == metafactoryHandle || indy.bsm == altMetafactoryHandle) &&
+ {
+ indy.bsmArgs match {
+ case Array(samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type, xs @ _*) =>
+ // LambdaMetaFactory performs a number of automatic adaptations when invoking the lambda
+ // implementation method (casting, boxing, unboxing, and primitive widening, see Javadoc).
+ //
+ // The closure optimizer supports only one of those adaptations: it will cast arguments
+ // to the correct type when re-writing a closure call to the body method. Example:
+ //
+ // val fun: String => String = l => l
+ // val l = List("")
+ // fun(l.head)
+ //
+ // The samMethodType of Function1 is `(Object)Object`, while the instantiatedMethodType
+ // is `(String)String`. The return type of `List.head` is `Object`.
+ //
+ // The implMethod has the signature `C$anonfun(String)String`.
+ //
+ // At the closure callsite, we have an `INVOKEINTERFACE Function1.apply (Object)Object`,
+ // so the object returned by `List.head` can be directly passed into the call (no cast).
+ //
+ // The closure object will cast the object to String before passing it to the implMethod.
+ //
+ // 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
+ // 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
+ // are reference types, so that we can insert casts to perform the same adaptation
+ // that the closure object would.
+
+ val isStatic = implMethod.getTag == H_INVOKESTATIC
+ val indyParamTypes = Type.getArgumentTypes(indy.desc)
+ val instantiatedMethodArgTypes = instantiatedMethodType.getArgumentTypes
+ val expectedImplMethodType = {
+ val paramTypes = (if (isStatic) indyParamTypes else indyParamTypes.tail) ++ instantiatedMethodArgTypes
+ Type.getMethodType(instantiatedMethodType.getReturnType, paramTypes: _*)
+ }
+
+ {
+ Type.getType(implMethod.getDesc) == expectedImplMethodType // (1)
+ } && {
+ isStatic || implMethod.getOwner == indyParamTypes(0).getInternalName // (2)
+ } && {
+ def isReference(t: Type) = t.getSort == Type.OBJECT || t.getSort == Type.ARRAY
+ (samMethodType.getArgumentTypes, instantiatedMethodArgTypes).zipped forall {
+ case (samArgType, instArgType) =>
+ samArgType == instArgType || isReference(samArgType) && isReference(instArgType) // (3)
+ }
+ }
+
+ case _ =>
+ false
+ }
+ }
+ }
+
+ def isSamInvocation(invocation: MethodInsnNode, indy: InvokeDynamicInsnNode, prodCons: => ProdConsAnalyzer): Boolean = {
+ if (invocation.getOpcode == INVOKESTATIC) false
+ else {
+ def closureIsReceiver = {
+ val invocationFrame = prodCons.frameAt(invocation)
+ val receiverSlot = {
+ val numArgs = Type.getArgumentTypes(invocation.desc).length
+ invocationFrame.stackTop - numArgs
+ }
+ val receiverProducers = prodCons.initialProducersForValueAt(invocation, receiverSlot)
+ receiverProducers.size == 1 && receiverProducers.head == indy
+ }
+
+ invocation.name == indy.name && {
+ val indySamMethodDesc = indy.bsmArgs(0).asInstanceOf[Type].getDescriptor // safe, checked in isClosureInstantiation
+ indySamMethodDesc == invocation.desc
+ } &&
+ closureIsReceiver // most expensive check last
+ }
+ }
+
+ /**
+ * Stores the values captured by a closure creation into fresh local variables.
+ * Returns the list of locals holding the captured values.
+ */
+ private def storeCaptures(indy: InvokeDynamicInsnNode, methodNode: MethodNode): LocalsList = {
+ val capturedTypes = Type.getArgumentTypes(indy.desc)
+ val firstCaptureLocal = methodNode.maxLocals
+
+ // This could be optimized: in many cases the captured values are produced by LOAD instructions.
+ // If the variable is not modified within the method, we could avoid introducing yet another
+ // 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)
+ methodNode.maxLocals = firstCaptureLocal + localsForCaptures.size
+
+ insertStoreOps(indy, methodNode, localsForCaptures)
+ insertLoadOps(indy, methodNode, localsForCaptures)
+
+ localsForCaptures
+ }
+
+ /**
+ * Insert store operations in front of the `before` instruction to copy stack values into the
+ * locals denoted by `localsList`.
+ *
+ * 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)
+
+ /**
+ * Insert load operations in front of the `before` instruction to copy the local values denoted
+ * by `localsList` onto the stack.
+ *
+ * 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
+ 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))
+ }
+ }
+
+ def rewriteClosureApplyInvocations(indy: InvokeDynamicInsnNode, methodNode: MethodNode, ownerClass: ClassBType): List[RewriteClosureApplyToClosureBodyFailed] = {
+ val lambdaBodyHandle = indy.bsmArgs(1).asInstanceOf[Handle] // safe, checked in isClosureInstantiation
+
+ // Kept as a lazy val to make sure the analysis is only computed if it's actually needed.
+ // ProdCons is used to identify closure body invocations (see isSamInvocation), but only if the
+ // callsite has the right name and signature. If the method has no invcation instruction with
+ // the right name and signature, the analysis is not executed.
+ lazy val prodCons = new ProdConsAnalyzer(methodNode, ownerClass.internalName)
+
+ // First collect all callsites without modifying the instructions list yet.
+ // Once we start modifying the instruction list, prodCons becomes unusable.
+
+ // A list of callsites and stack heights. If the invocation cannot be rewritten, a warning
+ // message is stored in the stack height value.
+ val invocationsToRewrite: List[(MethodInsnNode, Either[RewriteClosureApplyToClosureBodyFailed, Int])] = methodNode.instructions.iterator.asScala.collect({
+ case invocation: MethodInsnNode if isSamInvocation(invocation, indy, prodCons) =>
+ 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)
+ } yield {
+ isAccessible
+ }
+
+ def pos = callGraph.callsites.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))
+ case _ => Right(prodCons.frameAt(invocation).getStackSize)
+ }
+
+ (invocation, stackSize)
+ }).toList
+
+ if (invocationsToRewrite.isEmpty) Nil
+ else {
+ // lazy val to make sure locals for captures and arguments are only allocated if there's
+ // effectively a callsite to rewrite.
+ lazy val (localsForCapturedValues, argumentLocalsList) = {
+ val captureLocals = storeCaptures(indy, methodNode)
+
+ // allocate locals for storing the arguments of the closure apply callsites.
+ // if there are multiple callsites, the same locals are re-used.
+ val argTypes = indy.bsmArgs(0).asInstanceOf[Type].getArgumentTypes // safe, checked in isClosureInstantiation
+ val firstArgLocal = methodNode.maxLocals
+
+ // The comment in `isClosureInstantiation` explains why we have to introduce casts for
+ // arguments that have different types in samMethodType and instantiatedMethodType.
+ val castLoadTypes = {
+ val instantiatedMethodType = indy.bsmArgs(2).asInstanceOf[Type]
+ (argTypes, instantiatedMethodType.getArgumentTypes).zipped map {
+ case (samArgType, instantiatedArgType) if samArgType != instantiatedArgType =>
+ // isClosureInstantiation 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)
+ methodNode.maxLocals = firstArgLocal + argLocals.size
+
+ (captureLocals, argLocals)
+ }
+
+ val warnings = invocationsToRewrite flatMap {
+ case (invocation, Left(warning)) => Some(warning)
+
+ case (invocation, Right(stackHeight)) =>
+ // store arguments
+ insertStoreOps(invocation, methodNode, argumentLocalsList)
+
+ // drop the closure from the stack
+ methodNode.instructions.insertBefore(invocation, new InsnNode(POP))
+
+ // load captured values and arguments
+ insertLoadOps(invocation, methodNode, localsForCapturedValues)
+ insertLoadOps(invocation, methodNode, argumentLocalsList)
+
+ // update maxStack
+ val capturesStackSize = localsForCapturedValues.size
+ val invocationStackHeight = stackHeight + capturesStackSize - 1 // -1 because the closure is gone
+ if (invocationStackHeight > methodNode.maxStack)
+ methodNode.maxStack = invocationStackHeight
+
+ // replace the callsite with a new call to the body method
+ val bodyOpcode = (lambdaBodyHandle.getTag: @switch) match {
+ case H_INVOKEVIRTUAL => INVOKEVIRTUAL
+ case H_INVOKESTATIC => INVOKESTATIC
+ case H_INVOKESPECIAL => INVOKESPECIAL
+ case H_INVOKEINTERFACE => INVOKEINTERFACE
+ case H_NEWINVOKESPECIAL =>
+ val insns = methodNode.instructions
+ insns.insertBefore(invocation, new TypeInsnNode(NEW, lambdaBodyHandle.getOwner))
+ insns.insertBefore(invocation, new InsnNode(DUP))
+ INVOKESPECIAL
+ }
+ val isInterface = bodyOpcode == INVOKEINTERFACE
+ val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, isInterface)
+ methodNode.instructions.insertBefore(invocation, bodyInvocation)
+ methodNode.instructions.remove(invocation)
+
+ // update the call graph
+ val originalCallsite = callGraph.callsites.remove(invocation)
+
+ // 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 = methodNode,
+ callsiteClass = ownerClass,
+ callee = bodyMethod.map({
+ case (bodyMethodNode, bodyMethodDeclClass) => Callee(
+ callee = bodyMethodNode,
+ calleeDeclarationClass = classBTypeFromParsedClassfile(bodyMethodDeclClass),
+ safeToInline = compilerSettings.YoptInlineGlobal || bodyMethodIsBeingCompiled,
+ safeToRewrite = false, // the lambda body method is not a trait interface method
+ annotatedInline = false,
+ annotatedNoInline = false,
+ calleeInfoWarning = None)
+ }),
+ argInfos = Nil,
+ callsiteStackHeight = invocationStackHeight,
+ receiverKnownNotNull = true, // see below (*)
+ callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition)
+ )
+ // (*) 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
+ None
+ }
+
+ warnings.toList
+ }
+ }
+
+ /**
+ * A list of local variables. Each local stores information about its type, see class [[Local]].
+ */
+ case class LocalsList(locals: List[Local]) {
+ val size = locals.iterator.map(_.size).sum
+ }
+
+ object LocalsList {
+ /**
+ * A list of local variables starting at `firstLocal` that can hold values of the types in the
+ * `types` parameter.
+ *
+ * For example, `fromTypes(3, Array(Int, Long, String))` returns
+ * Local(3, intOpOffset) ::
+ * Local(4, longOpOffset) :: // note that this local occupies two slots, the next is at 6
+ * Local(6, refOpOffset) ::
+ * Nil
+ */
+ def fromTypes(firstLocal: Int, types: Array[Type], castLoadTypes: Int => Option[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))
+ if (local.size == 2) sizeTwoOffset += 1
+ local
+ })(collection.breakOut)
+ LocalsList(locals)
+ }
+ }
+
+ /**
+ * Stores a local varaible index the opcode offset required for operating on that variable.
+ *
+ * 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]) {
+ def size = if (loadOpcode == LLOAD || loadOpcode == DLOAD) 2 else 1
+
+ def loadOpcode = ILOAD + opcodeOffset
+ def storeOpcode = ISTORE + opcodeOffset
+ }
+}
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 b4f091b37f..e8e848161c 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
@@ -26,7 +26,8 @@ class Inliner[BT <: BTypes](val btypes: BT) {
def eliminateUnreachableCodeAndUpdateCallGraph(methodNode: MethodNode, definingClass: InternalName): Unit = {
localOpt.minimalRemoveUnreachableCode(methodNode, definingClass) foreach {
- case invocation: MethodInsnNode => callGraph.callsites.remove(invocation)
+ case invocation: MethodInsnNode => callGraph.callsites.remove(invocation)
+ case indy: InvokeDynamicInsnNode => callGraph.closureInstantiations.remove(indy)
case _ =>
}
}
@@ -432,7 +433,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
callsiteMethod.localVariables.addAll(cloneLocalVariableNodes(callee, labelsMap, callee.name + "_").asJava)
callsiteMethod.tryCatchBlocks.addAll(cloneTryCatchBlockNodes(callee, labelsMap).asJava)
- // Add all invocation instructions that were inlined to the call graph
+ // 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 {
@@ -452,6 +453,15 @@ class Inliner[BT <: BTypes](val btypes: BT) {
case None =>
}
+ case indy: InvokeDynamicInsnNode =>
+ callGraph.closureInstantiations.get(indy) match {
+ case Some((methodNode, ownerClass)) =>
+ val newIndy = instructionMap(indy).asInstanceOf[InvokeDynamicInsnNode]
+ callGraph.closureInstantiations(newIndy) = (callsiteMethod, callsiteClass)
+
+ case None =>
+ }
+
case _ =>
}
// Remove the elided invocation from the call graph
@@ -529,98 +539,97 @@ class Inliner[BT <: BTypes](val btypes: BT) {
}
/**
- * Returns the first instruction in the `instructions` list that would cause a
- * [[java.lang.IllegalAccessError]] when inlined into the `destinationClass`.
- *
- * If validity of some instruction could not be checked because an error occurred, the instruction
- * is returned together with a warning message that describes the problem.
+ * Check if a type is accessible to some class, as defined in JVMS 5.4.4.
+ * (A1) C is public
+ * (A2) C and D are members of the same run-time package
*/
- def findIllegalAccess(instructions: InsnList, calleeDeclarationClass: ClassBType, destinationClass: ClassBType): Option[(AbstractInsnNode, Option[OptimizerWarning])] = {
-
- /**
- * Check if a type is accessible to some class, as defined in JVMS 5.4.4.
- * (A1) C is public
- * (A2) C and D are members of the same run-time package
- */
- def classIsAccessible(accessed: BType, from: ClassBType = destinationClass): Either[OptimizerWarning, Boolean] = (accessed: @unchecked) match {
- // TODO: A2 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok?
- case c: ClassBType => c.isPublic.map(_ || c.packageInternalName == from.packageInternalName)
- case a: ArrayBType => classIsAccessible(a.elementType, from)
- case _: PrimitiveBType => Right(true)
- }
+ def classIsAccessible(accessed: BType, from: ClassBType): Either[OptimizerWarning, Boolean] = (accessed: @unchecked) match {
+ // TODO: A2 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok?
+ case c: ClassBType => c.isPublic.map(_ || c.packageInternalName == from.packageInternalName)
+ case a: ArrayBType => classIsAccessible(a.elementType, from)
+ case _: PrimitiveBType => Right(true)
+ }
- /**
- * Check if a member reference is accessible from the [[destinationClass]], as defined in the
- * JVMS 5.4.4. Note that the class name in a field / method reference is not necessarily the
- * class in which the member is declared:
- *
- * class A { def f = 0 }; class B extends A { f }
- *
- * The INVOKEVIRTUAL instruction uses a method reference "B.f ()I". Therefore this method has
- * two parameters:
- *
- * @param memberDeclClass The class in which the member is declared (A)
- * @param memberRefClass The class used in the member reference (B)
- *
- * (B0) JVMS 5.4.3.2 / 5.4.3.3: when resolving a member of class C in D, the class C is resolved
- * first. According to 5.4.3.1, this requires C to be accessible in D.
- *
- * JVMS 5.4.4 summary: A field or method R is accessible to a class D (destinationClass) iff
- * (B1) R is public
- * (B2) R is protected, declared in C (memberDeclClass) and D is a subclass of C.
- * If R is not static, R must contain a symbolic reference to a class T (memberRefClass),
- * such that T is either a subclass of D, a superclass of D, or D itself.
- * Also (P) needs to be satisfied.
- * (B3) R is either protected or has default access and declared by a class in the same
- * run-time package as D.
- * If R is protected, also (P) needs to be satisfied.
- * (B4) R is private and is declared in D.
- *
- * (P) When accessing a protected instance member, the target object on the stack (the receiver)
- * has to be a subtype of D (destinationClass). This is enforced by classfile verification
- * (https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.8).
- *
- * TODO: we cannot currently implement (P) because we don't have the necessary information
- * available. Once we have a type propagation analysis implemented, we can extract the receiver
- * type from there (https://github.com/scala-opt/scala/issues/13).
- */
- def memberIsAccessible(memberFlags: Int, memberDeclClass: ClassBType, memberRefClass: ClassBType): Either[OptimizerWarning, Boolean] = {
- // TODO: B3 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok?
- def samePackageAsDestination = memberDeclClass.packageInternalName == destinationClass.packageInternalName
- def targetObjectConformsToDestinationClass = false // needs type propagation analysis, see above
-
- def memberIsAccessibleImpl = {
- val key = (ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE) & memberFlags
- key match {
- case ACC_PUBLIC => // B1
- Right(true)
-
- case ACC_PROTECTED => // B2
- val isStatic = (ACC_STATIC & memberFlags) != 0
- tryEither {
- val condB2 = destinationClass.isSubtypeOf(memberDeclClass).orThrow && {
- isStatic || memberRefClass.isSubtypeOf(destinationClass).orThrow || destinationClass.isSubtypeOf(memberRefClass).orThrow
- }
- Right(
- (condB2 || samePackageAsDestination /* B3 (protected) */) &&
- (isStatic || targetObjectConformsToDestinationClass) // (P)
- )
+ /**
+ * Check if a member reference is accessible from the [[destinationClass]], as defined in the
+ * JVMS 5.4.4. Note that the class name in a field / method reference is not necessarily the
+ * class in which the member is declared:
+ *
+ * class A { def f = 0 }; class B extends A { f }
+ *
+ * The INVOKEVIRTUAL instruction uses a method reference "B.f ()I". Therefore this method has
+ * two parameters:
+ *
+ * @param memberDeclClass The class in which the member is declared (A)
+ * @param memberRefClass The class used in the member reference (B)
+ *
+ * (B0) JVMS 5.4.3.2 / 5.4.3.3: when resolving a member of class C in D, the class C is resolved
+ * first. According to 5.4.3.1, this requires C to be accessible in D.
+ *
+ * JVMS 5.4.4 summary: A field or method R is accessible to a class D (destinationClass) iff
+ * (B1) R is public
+ * (B2) R is protected, declared in C (memberDeclClass) and D is a subclass of C.
+ * If R is not static, R must contain a symbolic reference to a class T (memberRefClass),
+ * such that T is either a subclass of D, a superclass of D, or D itself.
+ * Also (P) needs to be satisfied.
+ * (B3) R is either protected or has default access and declared by a class in the same
+ * run-time package as D.
+ * If R is protected, also (P) needs to be satisfied.
+ * (B4) R is private and is declared in D.
+ *
+ * (P) When accessing a protected instance member, the target object on the stack (the receiver)
+ * has to be a subtype of D (destinationClass). This is enforced by classfile verification
+ * (https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.8).
+ *
+ * TODO: we cannot currently implement (P) because we don't have the necessary information
+ * available. Once we have a type propagation analysis implemented, we can extract the receiver
+ * type from there (https://github.com/scala-opt/scala/issues/13).
+ */
+ def memberIsAccessible(memberFlags: Int, memberDeclClass: ClassBType, memberRefClass: ClassBType, from: ClassBType): Either[OptimizerWarning, Boolean] = {
+ // TODO: B3 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok?
+ def samePackageAsDestination = memberDeclClass.packageInternalName == from.packageInternalName
+ def targetObjectConformsToDestinationClass = false // needs type propagation analysis, see above
+
+ def memberIsAccessibleImpl = {
+ val key = (ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE) & memberFlags
+ key match {
+ case ACC_PUBLIC => // B1
+ Right(true)
+
+ case ACC_PROTECTED => // B2
+ val isStatic = (ACC_STATIC & memberFlags) != 0
+ tryEither {
+ val condB2 = from.isSubtypeOf(memberDeclClass).orThrow && {
+ isStatic || memberRefClass.isSubtypeOf(from).orThrow || from.isSubtypeOf(memberRefClass).orThrow
}
+ Right(
+ (condB2 || samePackageAsDestination /* B3 (protected) */) &&
+ (isStatic || targetObjectConformsToDestinationClass) // (P)
+ )
+ }
- case 0 => // B3 (default access)
- Right(samePackageAsDestination)
+ case 0 => // B3 (default access)
+ Right(samePackageAsDestination)
- case ACC_PRIVATE => // B4
- Right(memberDeclClass == destinationClass)
- }
+ case ACC_PRIVATE => // B4
+ Right(memberDeclClass == from)
}
+ }
- classIsAccessible(memberDeclClass) match { // B0
- case Right(true) => memberIsAccessibleImpl
- case r => r
- }
+ classIsAccessible(memberDeclClass, from) match { // B0
+ case Right(true) => memberIsAccessibleImpl
+ case r => r
}
+ }
+ /**
+ * Returns the first instruction in the `instructions` list that would cause a
+ * [[java.lang.IllegalAccessError]] when inlined into the `destinationClass`.
+ *
+ * If validity of some instruction could not be checked because an error occurred, the instruction
+ * is returned together with a warning message that describes the problem.
+ */
+ def findIllegalAccess(instructions: InsnList, calleeDeclarationClass: ClassBType, destinationClass: ClassBType): Option[(AbstractInsnNode, Option[OptimizerWarning])] = {
/**
* Check if `instruction` can be transplanted to `destinationClass`.
*
@@ -637,18 +646,18 @@ class Inliner[BT <: BTypes](val btypes: BT) {
// NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. For these instructions, the reference
// "must be a symbolic reference to a class, array, or interface type" (JVMS 6), so
// it can be an internal name, or a full array descriptor.
- classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ti.desc))
+ classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ti.desc), destinationClass)
case ma: MultiANewArrayInsnNode =>
// "a symbolic reference to a class, array, or interface type"
- classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ma.desc))
+ classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ma.desc), destinationClass)
case fi: FieldInsnNode =>
val fieldRefClass = classBTypeFromParsedClassfile(fi.owner)
for {
(fieldNode, fieldDeclClassNode) <- byteCodeRepository.fieldNode(fieldRefClass.internalName, fi.name, fi.desc): Either[OptimizerWarning, (FieldNode, InternalName)]
fieldDeclClass = classBTypeFromParsedClassfile(fieldDeclClassNode)
- res <- memberIsAccessible(fieldNode.access, fieldDeclClass, fieldRefClass)
+ res <- memberIsAccessible(fieldNode.access, fieldDeclClass, fieldRefClass, destinationClass)
} yield {
res
}
@@ -664,7 +673,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
Right(destinationClass == calleeDeclarationClass)
case _ => // INVOKEVIRTUAL, INVOKESTATIC, INVOKEINTERFACE and INVOKESPECIAL of constructors
- memberIsAccessible(methodFlags, methodDeclClass, methodRefClass)
+ memberIsAccessible(methodFlags, methodDeclClass, methodRefClass, destinationClass)
}
}
@@ -683,7 +692,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
Right(false)
case ci: LdcInsnNode => ci.cst match {
- case t: asm.Type => classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName))
+ case t: asm.Type => classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName), destinationClass)
case _ => Right(true)
}
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 5f51a94673..bd5bab28b5 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
@@ -262,7 +262,7 @@ object LocalOptImpls {
* the same index, but distinct start / end ranges are different variables, they may have not the
* same type or name.
*/
- def removeUnusedLocalVariableNodes(method: MethodNode)(fistLocalIndex: Int = parametersSize(method), renumber: Int => Int = identity): Boolean = {
+ def removeUnusedLocalVariableNodes(method: MethodNode)(firstLocalIndex: Int = parametersSize(method), renumber: Int => Int = identity): Boolean = {
def variableIsUsed(start: AbstractInsnNode, end: LabelNode, varIndex: Int): Boolean = {
start != end && (start match {
case v: VarInsnNode if v.`var` == varIndex => true
@@ -276,7 +276,7 @@ object LocalOptImpls {
val local = localsIter.next()
val index = local.index
// parameters and `this` (the lowest indices, starting at 0) are never removed or renumbered
- if (index >= fistLocalIndex) {
+ if (index >= firstLocalIndex) {
if (!variableIsUsed(local.start, local.end, index)) localsIter.remove()
else if (renumber(index) != index) local.index = renumber(index)
}
diff --git a/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala b/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala
index 1b6631e7a4..8911a3a28c 100644
--- a/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala
+++ b/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala
@@ -378,7 +378,7 @@ abstract class DeadCodeElimination extends SubComponent {
} else {
i match {
case NEW(REFERENCE(sym)) =>
- log(s"Eliminated instantation of $sym inside $m")
+ 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
diff --git a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala
index d34c14be0f..9708cba281 100644
--- a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala
+++ b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala
@@ -761,9 +761,13 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
val interfaces = interfacesOpt()
accept(LBRACE)
val buf = new ListBuffer[Tree]
+ var enumIsFinal = true
def parseEnumConsts() {
if (in.token != RBRACE && in.token != SEMI && in.token != EOF) {
- buf += enumConst(enumType)
+ val (const, hasClassBody) = enumConst(enumType)
+ buf += const
+ // if any of the enum constants has a class body, the enum class is not final (JLS 8.9.)
+ enumIsFinal &&= !hasClassBody
if (in.token == COMMA) {
in.nextToken()
parseEnumConsts()
@@ -793,15 +797,25 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
accept(RBRACE)
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.ENUM, name, List(),
+ ClassDef(mods | Flags.ENUM | finalFlag | abstractFlag, name, List(),
makeTemplate(superclazz :: interfaces, body))
})
}
- def enumConst(enumType: Tree) = {
+ def enumConst(enumType: Tree): (ValDef, Boolean) = {
annotations()
- atPos(in.currentPos) {
+ var hasClassBody = false
+ val res = atPos(in.currentPos) {
val name = ident()
if (in.token == LPAREN) {
// skip arguments
@@ -809,12 +823,14 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
accept(RPAREN)
}
if (in.token == LBRACE) {
+ hasClassBody = true
// skip classbody
skipAhead()
accept(RBRACE)
}
ValDef(Modifiers(Flags.ENUM | Flags.STABLE | Flags.JAVA | Flags.STATIC), name.toTermName, enumType, blankExpr)
}
+ (res, hasClassBody)
}
def typeDecl(mods: Modifiers): List[Tree] = in.token match {
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
index 953e43eaca..0cdece59e1 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -76,7 +76,7 @@ trait ScalaSettings extends AbsScalaSettings
val implicitConversions = Choice("implicitConversions", "Allow definition of implicit functions called views")
val higherKinds = Choice("higherKinds", "Allow higher-kinded types")
val existentials = Choice("existentials", "Existential types (besides wildcard types) can be written and inferred")
- val macros = Choice("experimental.macros", "Allow macro defintion (besides implementation and application)")
+ val macros = Choice("experimental.macros", "Allow macro definition (besides implementation and application)")
}
val language = {
val description = "Enable or disable language features"
@@ -235,6 +235,7 @@ trait ScalaSettings extends AbsScalaSettings
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 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.")
@@ -243,7 +244,7 @@ trait ScalaSettings extends AbsScalaSettings
private val defaultChoices = List(unreachableCode)
val lDefault = Choice("l:default", "Enable default optimizations: "+ defaultChoices.mkString(","), expandsTo = defaultChoices)
- private val methodChoices = List(unreachableCode, simplifyJumps, emptyLineNumbers, emptyLabels, compactLocals, nullnessTracking)
+ 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 projectChoices = List(lMethod, inlineProject)
@@ -266,11 +267,15 @@ trait ScalaSettings extends AbsScalaSettings
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)
def YoptInlinerEnabled = YoptInlineProject || YoptInlineGlobal
+ def YoptBuildCallGraph = YoptInlinerEnabled || YoptClosureElimination
+ def YoptAddToBytecodeRepository = YoptInlinerEnabled || YoptClosureElimination
+
val YoptInlineHeuristics = ChoiceSetting(
name = "-Yopt-inline-heuristics",
helpArg = "strategy",
diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
index 518a402230..660028eab8 100644
--- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
+++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala
@@ -539,6 +539,8 @@ 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.
+ // This is a bit of a hack and requires excluding the ABSTRACT flag in the backend, see method javaClassfileFlags.
linked setFlag (SEALED | ABSTRACT)
linked addChild sym
}
diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala
index ea46116976..438a71061e 100644
--- a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala
+++ b/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala
@@ -599,7 +599,7 @@ abstract class ICodeReader extends ClassfileParser {
}
case JVM.invokedynamic =>
// TODO, this is just a place holder. A real implementation must parse the class constant entry
- debuglog("Found JVM invokedynamic instructionm, inserting place holder ICode INVOKE_DYNAMIC.")
+ debuglog("Found JVM invokedynamic instruction, inserting place holder ICode INVOKE_DYNAMIC.")
containsInvokeDynamic = true
val poolEntry = in.nextChar.toInt
in.skip(2)
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
index 55ab73028e..5a7f6c52da 100644
--- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
+++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
@@ -444,7 +444,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
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 elimnates ErasedValueTypes
+ val postErasedTree = trans.atOwner(currentOwner)(trans.transform(adaptedTree)) // SI-8017 eliminates ErasedValueTypes
(needsAdapt, postErasedTree)
}
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala
index 227c45b3a7..49a4990722 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala
@@ -510,7 +510,7 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis {
def propForEqualsTo(c: Const): Prop = {observed(); symForEqualsTo.getOrElse(c, False)}
// [implementation NOTE: don't access until all potential equalities have been registered using registerEquality]p
- /** the information needed to construct the boolean proposition that encods the equality proposition (V = C)
+ /** the information needed to construct the boolean proposition that encodes the equality proposition (V = C)
*
* that models a type test pattern `_: C` or constant pattern `C`, where the type test gives rise to a TypeConst C,
* and the constant pattern yields a ValueConst C
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
index e1fe220556..e0fcc05de2 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
@@ -642,7 +642,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
}
// override def apply
- // debug.patmat("before fixerupper: "+ xTree)
+ // debug.patmat("before fixerUpper: "+ xTree)
// currentRun.trackerFactory.snapshot()
// debug.patmat("after fixerupper")
// currentRun.trackerFactory.snapshot()
diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
index 5ecca5abce..80e06eb8fa 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
@@ -985,7 +985,7 @@ trait Implicits {
if (implicitInfoss.forall(_.isEmpty)) SearchFailure
else new ImplicitComputation(implicitInfoss, isLocalToCallsite) findBest()
- /** Produce an implicict info map, i.e. a map from the class symbols C of all parts of this type to
+ /** Produce an implicit info map, i.e. a map from the class symbols C of all parts of this type to
* the implicit infos in the companion objects of these class symbols C.
* The parts of a type is the smallest set of types that contains
* - the type itself
diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
index f9582a54ff..ea0a9bb243 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
@@ -934,7 +934,7 @@ trait Infer extends Checkable {
def infer_s = map3(tparams, tvars, targs)((tparam, tvar, targ) => s"$tparam=$tvar/$targ") mkString ","
printTyping(tree, s"infer expr instance from pt=$pt, $infer_s")
- // SI-7899 infering by-name types is unsound. The correct behaviour is conditional because the hole is
+ // SI-7899 inferring by-name types is unsound. The correct behaviour is conditional because the hole is
// exploited in Scalaz (Free.scala), as seen in: run/t7899-regression.
def dropByNameIfStrict(tp: Type): Type = if (settings.inferByName) tp else dropByName(tp)
def targsStrict = if (targs eq null) null else targs mapConserve dropByNameIfStrict
diff --git a/src/compiler/scala/tools/nsc/util/DocStrings.scala b/src/compiler/scala/tools/nsc/util/DocStrings.scala
index 352816803f..4ff7067a21 100755
--- a/src/compiler/scala/tools/nsc/util/DocStrings.scala
+++ b/src/compiler/scala/tools/nsc/util/DocStrings.scala
@@ -184,7 +184,7 @@ object DocStrings {
extractSectionTag(str, section) -> section
}
- /** Extract the section tag, treating the section tag as an indentifier */
+ /** Extract the section tag, treating the section tag as an identifier */
def extractSectionTag(str: String, section: (Int, Int)): String =
str.substring(section._1, skipTag(str, section._1))
diff --git a/src/library/scala/collection/generic/Sorted.scala b/src/library/scala/collection/generic/Sorted.scala
index a0b0e1318b..b2e63daaba 100644
--- a/src/library/scala/collection/generic/Sorted.scala
+++ b/src/library/scala/collection/generic/Sorted.scala
@@ -36,7 +36,7 @@ trait Sorted[K, +This <: Sorted[K, This]] {
/** Creates a ranged projection of this collection. Any mutations in the
* ranged projection will update this collection and vice versa.
*
- * Note: keys are not garuanteed to be consistent between this collection
+ * Note: keys are not guaranteed to be consistent between this collection
* and the projection. This is the case for buffers where indexing is
* relative to the projection.
*
diff --git a/src/library/scala/collection/immutable/Stream.scala b/src/library/scala/collection/immutable/Stream.scala
index 7edd36dc22..17cf02cce6 100644
--- a/src/library/scala/collection/immutable/Stream.scala
+++ b/src/library/scala/collection/immutable/Stream.scala
@@ -153,7 +153,7 @@ import scala.language.implicitConversions
*
* - The fact that `tail` works at all is of interest. In the definition of
* `fibs` we have an initial `(0, 1, Stream(...))` so `tail` is deterministic.
- * If we deinfed `fibs` such that only `0` were concretely known then the act
+ * If we defined `fibs` such that only `0` were concretely known then the act
* of determining `tail` would require the evaluation of `tail` which would
* cause an infinite recursion and stack overflow. If we define a definition
* where the tail is not initially computable then we're going to have an
diff --git a/src/library/scala/sys/BooleanProp.scala b/src/library/scala/sys/BooleanProp.scala
index 74b0a9077b..e5e4668edb 100644
--- a/src/library/scala/sys/BooleanProp.scala
+++ b/src/library/scala/sys/BooleanProp.scala
@@ -63,12 +63,13 @@ object BooleanProp {
def valueIsTrue[T](key: String): BooleanProp = new BooleanPropImpl(key, _.toLowerCase == "true")
/** As an alternative, this method creates a BooleanProp which is true
- * if the key exists in the map. This way -Dfoo.bar is enough to be
- * considered true.
+ * if the key exists in the map and is not assigned a value other than "true",
+ * compared case-insensitively, or the empty string. This way -Dmy.property
+ * results in a true-valued property, but -Dmy.property=false does not.
*
* @return A BooleanProp with a liberal truth policy
*/
- def keyExists[T](key: String): BooleanProp = new BooleanPropImpl(key, _ => true)
+ def keyExists[T](key: String): BooleanProp = new BooleanPropImpl(key, s => s == "" || s.equalsIgnoreCase("true"))
/** A constant true or false property which ignores all method calls.
*/
diff --git a/src/partest-extras/scala/tools/partest/ReplTest.scala b/src/partest-extras/scala/tools/partest/ReplTest.scala
index 5b65d6ab9b..1fde2370d3 100644
--- a/src/partest-extras/scala/tools/partest/ReplTest.scala
+++ b/src/partest-extras/scala/tools/partest/ReplTest.scala
@@ -75,18 +75,20 @@ abstract class SessionTest extends ReplTest {
* Retain user input: prompt lines and continuations, without the prefix; or pasted text plus ctl-D.
*/
import SessionTest._
- override final def code = input findAllMatchIn (expected mkString ("", "\n", "\n")) map {
- case input(null, null, prompted) =>
+ lazy val pasted = input(prompt)
+ override final def code = pasted findAllMatchIn (expected mkString ("", "\n", "\n")) map {
+ case pasted(null, null, prompted) =>
def continued(m: Match): Option[String] = m match {
case margin(text) => Some(text)
case _ => None
}
margin.replaceSomeIn(prompted, continued)
- case input(cmd, pasted, null) =>
+ case pasted(cmd, pasted, null) =>
cmd + pasted + "\u0004"
} mkString
- final def prompt = "scala> "
+ // Just the last line of the interactive prompt
+ def prompt = "scala> "
/** Default test is to compare expected and actual output and emit the diff on a failed comparison. */
override def show() = {
@@ -98,7 +100,7 @@ abstract class SessionTest extends ReplTest {
}
object SessionTest {
// \R for line break is Java 8, \v for vertical space might suffice
- val input = """(?m)^scala> (:pa.*\u000A)// Entering paste mode.*\u000A\u000A((?:.*\u000A)*)\u000A// Exiting paste mode.*\u000A|^scala> (.*\u000A(?:\s*\| .*\u000A)*)""".r
+ def input(prompt: String) = s"""(?m)^$prompt(:pa.*\u000A)// Entering paste mode.*\u000A\u000A((?:.*\u000A)*)\u000A// Exiting paste mode.*\u000A|^scala> (.*\u000A(?:\\s*\\| .*\u000A)*)""".r
val margin = """(?m)^\s*\| (.*)$""".r
}
diff --git a/src/reflect/scala/reflect/api/FlagSets.scala b/src/reflect/scala/reflect/api/FlagSets.scala
index bcad84a3f0..d3294dad9b 100644
--- a/src/reflect/scala/reflect/api/FlagSets.scala
+++ b/src/reflect/scala/reflect/api/FlagSets.scala
@@ -48,7 +48,7 @@ import scala.language.implicitConversions
* ''Of Note:'' This part of the Reflection API is being considered as a candidate for redesign. It is
* quite possible that in future releases of the reflection API, flag sets could be replaced with something else.
*
- * For more details about `FlagSet`s and other aspects of Scala reflection, see the
+ * For more details about `FlagSet`s and other aspects of Scala reflection, see the
* [[http://docs.scala-lang.org/overviews/reflection/overview.html Reflection Guide]]
*
* @group ReflectionAPI
diff --git a/src/reflect/scala/reflect/api/Printers.scala b/src/reflect/scala/reflect/api/Printers.scala
index 01b9759c70..c0abc5120c 100644
--- a/src/reflect/scala/reflect/api/Printers.scala
+++ b/src/reflect/scala/reflect/api/Printers.scala
@@ -130,7 +130,7 @@ import java.io.{ PrintWriter, StringWriter }
* TermName("y")#2541#GET))
* }}}
*
- * For more details about `Printer`s and other aspects of Scala reflection, see the
+ * For more details about `Printer`s and other aspects of Scala reflection, see the
* [[http://docs.scala-lang.org/overviews/reflection/overview.html Reflection Guide]]
*
* @group ReflectionAPI
diff --git a/src/reflect/scala/reflect/internal/ClassfileConstants.scala b/src/reflect/scala/reflect/internal/ClassfileConstants.scala
index e0a6757d34..53241fb15b 100644
--- a/src/reflect/scala/reflect/internal/ClassfileConstants.scala
+++ b/src/reflect/scala/reflect/internal/ClassfileConstants.scala
@@ -344,10 +344,12 @@ object ClassfileConstants {
case JAVA_ACC_STATIC => STATIC
case JAVA_ACC_ABSTRACT => if (isAnnotation) 0L else if (isClass) ABSTRACT else DEFERRED
case JAVA_ACC_INTERFACE => if (isAnnotation) 0L else TRAIT | INTERFACE | ABSTRACT
+ case JAVA_ACC_ENUM => ENUM
case _ => 0L
}
- private def translateFlags(jflags: Int, baseFlags: Long, isAnnotation: Boolean, isClass: Boolean): Long = {
- def translateFlag0(jflags: Int): Long = translateFlag(jflags, isAnnotation, isClass)
+ private def translateFlags(jflags: Int, baseFlags: Long, isClass: Boolean): Long = {
+ val isAnnot = isAnnotation(jflags)
+ def translateFlag0(jflags: Int): Long = translateFlag(jflags, isAnnot, isClass)
var res: Long = JAVA | baseFlags
/* fast, elegant, maintainable, pick any two... */
res |= translateFlag0(jflags & JAVA_ACC_PRIVATE)
@@ -357,17 +359,18 @@ object ClassfileConstants {
res |= translateFlag0(jflags & JAVA_ACC_STATIC)
res |= translateFlag0(jflags & JAVA_ACC_ABSTRACT)
res |= translateFlag0(jflags & JAVA_ACC_INTERFACE)
+ res |= translateFlag0(jflags & JAVA_ACC_ENUM)
res
}
def classFlags(jflags: Int): Long = {
- translateFlags(jflags, 0, isAnnotation(jflags), isClass = true)
+ translateFlags(jflags, 0, isClass = true)
}
def fieldFlags(jflags: Int): Long = {
- translateFlags(jflags, if ((jflags & JAVA_ACC_FINAL) == 0) MUTABLE else 0 , isAnnotation(jflags), isClass = false)
+ translateFlags(jflags, if ((jflags & JAVA_ACC_FINAL) == 0) MUTABLE else 0 , isClass = false)
}
def methodFlags(jflags: Int): Long = {
- translateFlags(jflags, if ((jflags & JAVA_ACC_BRIDGE) != 0) BRIDGE | ARTIFACT else 0, isAnnotation(jflags), isClass = false)
+ translateFlags(jflags, if ((jflags & JAVA_ACC_BRIDGE) != 0) BRIDGE | ARTIFACT else 0, isClass = false)
}
}
object FlagTranslation extends FlagTranslation { }
diff --git a/src/reflect/scala/reflect/internal/ReificationSupport.scala b/src/reflect/scala/reflect/internal/ReificationSupport.scala
index eddfec82e7..d393a841b7 100644
--- a/src/reflect/scala/reflect/internal/ReificationSupport.scala
+++ b/src/reflect/scala/reflect/internal/ReificationSupport.scala
@@ -802,7 +802,7 @@ trait ReificationSupport { self: SymbolTable =>
require(enums.nonEmpty, "enumerators can't be empty")
enums.head match {
case SyntacticValFrom(_, _) =>
- case t => throw new IllegalArgumentException(s"$t is not a valid fist enumerator of for loop")
+ case t => throw new IllegalArgumentException(s"$t is not a valid first enumerator of for loop")
}
enums.tail.foreach {
case SyntacticValEq(_, _) | SyntacticValFrom(_, _) | SyntacticFilter(_) =>
diff --git a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala
index c705ca7069..15a87200f1 100644
--- a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala
+++ b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala
@@ -561,7 +561,7 @@ private[internal] trait TypeMaps {
| tparams ${rhsSym.typeParams map own_s mkString ", "}
|"""
- if (argIndex < 0)
+ if (!rhsArgs.isDefinedAt(argIndex))
abort(s"Something is wrong: cannot find $lhs in applied type $rhs\n" + explain)
else {
val targ = rhsArgs(argIndex)
diff --git a/src/reflect/scala/reflect/internal/util/StripMarginInterpolator.scala b/src/reflect/scala/reflect/internal/util/StripMarginInterpolator.scala
index e622e78d57..35858cdc78 100644
--- a/src/reflect/scala/reflect/internal/util/StripMarginInterpolator.scala
+++ b/src/reflect/scala/reflect/internal/util/StripMarginInterpolator.scala
@@ -13,7 +13,7 @@ trait StripMarginInterpolator {
* The margin of each line is defined by whitespace leading up to a '|' character.
* This margin is stripped '''before''' the arguments are interpolated into to string.
*
- * String escape sequences are '''not''' processed; this interpolater is designed to
+ * String escape sequences are '''not''' processed; this interpolator is designed to
* be used with triple quoted Strings.
*
* {{{
diff --git a/src/repl/scala/tools/nsc/interpreter/Formatting.scala b/src/repl/scala/tools/nsc/interpreter/Formatting.scala
index 43e653edfd..844997429c 100644
--- a/src/repl/scala/tools/nsc/interpreter/Formatting.scala
+++ b/src/repl/scala/tools/nsc/interpreter/Formatting.scala
@@ -8,28 +8,25 @@ package interpreter
import util.stringFromWriter
-trait Formatting {
- def prompt: String
+class Formatting(indent: Int) {
- def spaces(code: String): String = {
+ private val indentation = " " * indent
+
+ private def indenting(code: String): Boolean = {
/** Heuristic to avoid indenting and thereby corrupting """-strings and XML literals. */
val tokens = List("\"\"\"", "</", "/>")
val noIndent = (code contains "\n") && (tokens exists code.contains)
- if (noIndent) ""
- else prompt drop 1 map (_ => ' ')
+ !noIndent
}
/** Indent some code by the width of the scala> prompt.
* This way, compiler error messages read better.
*/
- def indentCode(code: String) = {
- val indent = spaces(code)
- stringFromWriter(str =>
- for (line <- code.lines) {
- str print indent
- str print (line + "\n")
- str.flush()
- }
- )
- }
+ def indentCode(code: String) = stringFromWriter(str =>
+ for (line <- code.lines) {
+ if (indenting(code)) str print indentation
+ str println line
+ str.flush()
+ }
+ )
}
diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala
index a3047ccc8e..525609171e 100644
--- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala
+++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala
@@ -111,11 +111,10 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
}
class ILoopInterpreter extends IMain(settings, out) {
- outer =>
-
- override lazy val formatting = new Formatting {
- def prompt = ILoop.this.prompt
- }
+ // the expanded prompt but without color escapes and without leading newline, for purposes of indenting
+ override lazy val formatting: Formatting = new Formatting(
+ (replProps.promptString format Properties.versionNumberString).lines.toList.last.length
+ )
override protected def parentClassLoader =
settings.explicitParentLoader.getOrElse( classOf[ILoop].getClassLoader )
}
@@ -199,10 +198,8 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
echo("%d %s".format(index + offset, line))
}
- private val currentPrompt = Properties.shellPromptString
-
/** Prompt to print when awaiting input */
- def prompt = currentPrompt
+ def prompt = replProps.prompt
import LoopCommand.{ cmd, nullary }
@@ -412,14 +409,8 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
}
private def readOneLine() = {
- import scala.io.AnsiColor.{ MAGENTA, RESET }
out.flush()
- in readLine (
- if (replProps.colorOk)
- MAGENTA + prompt + RESET
- else
- prompt
- )
+ in readLine prompt
}
/** The main read-eval-print loop for the repl. It calls
@@ -770,8 +761,13 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
}
private object paste extends Pasted {
+ import scala.util.matching.Regex.quote
val ContinueString = " | "
- val PromptString = "scala> "
+ val PromptString = prompt.lines.toList.last
+ val anyPrompt = s"""\\s*(?:${quote(PromptString.trim)}|${quote(AltPromptString.trim)})\\s*""".r
+
+ def isPrompted(line: String) = matchesPrompt(line)
+ def isPromptOnly(line: String) = line match { case anyPrompt() => true ; case _ => false }
def interpret(line: String): Unit = {
echo(line.trim)
@@ -781,10 +777,17 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
def transcript(start: String) = {
echo("\n// Detected repl transcript paste: ctrl-D to finish.\n")
- apply(Iterator(start) ++ readWhile(_.trim != PromptString.trim))
+ apply(Iterator(start) ++ readWhile(!isPromptOnly(_)))
}
+
+ def unapply(line: String): Boolean = isPrompted(line)
+ }
+
+ private object invocation {
+ def unapply(line: String): Boolean = Completion.looksLikeInvocation(line)
}
- import paste.{ ContinueString, PromptString }
+
+ private val lineComment = """\s*//.*""".r // all comment
/** Interpret expressions starting with the first line.
* Read lines until a complete compilation unit is available
@@ -796,53 +799,42 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
// signal completion non-completion input has been received
in.completion.resetVerbosity()
- def reallyInterpret = {
- val reallyResult = intp.interpret(code)
- (reallyResult, reallyResult match {
- case IR.Error => None
- case IR.Success => Some(code)
- case IR.Incomplete =>
- if (in.interactive && code.endsWith("\n\n")) {
- echo("You typed two blank lines. Starting a new command.")
+ def reallyInterpret = intp.interpret(code) match {
+ case IR.Error => None
+ case IR.Success => Some(code)
+ case IR.Incomplete if in.interactive && code.endsWith("\n\n") =>
+ echo("You typed two blank lines. Starting a new command.")
+ None
+ case IR.Incomplete =>
+ in.readLine(paste.ContinueString) match {
+ case null =>
+ // we know compilation is going to fail since we're at EOF and the
+ // parser thinks the input is still incomplete, but since this is
+ // a file being read non-interactively we want to fail. So we send
+ // it straight to the compiler for the nice error message.
+ intp.compileString(code)
None
- }
- else in.readLine(ContinueString) match {
- case null =>
- // we know compilation is going to fail since we're at EOF and the
- // parser thinks the input is still incomplete, but since this is
- // a file being read non-interactively we want to fail. So we send
- // it straight to the compiler for the nice error message.
- intp.compileString(code)
- None
-
- case line => interpretStartingWith(code + "\n" + line)
- }
- })
+
+ case line => interpretStartingWith(code + "\n" + line)
+ }
}
- /** Here we place ourselves between the user and the interpreter and examine
- * the input they are ostensibly submitting. We intervene in several cases:
+ /* Here we place ourselves between the user and the interpreter and examine
+ * the input they are ostensibly submitting. We intervene in several cases:
*
- * 1) If the line starts with "scala> " it is assumed to be an interpreter paste.
- * 2) If the line starts with "." (but not ".." or "./") it is treated as an invocation
- * on the previous result.
- * 3) If the Completion object's execute returns Some(_), we inject that value
- * and avoid the interpreter, as it's likely not valid scala code.
+ * 1) If the line starts with "scala> " it is assumed to be an interpreter paste.
+ * 2) If the line starts with "." (but not ".." or "./") it is treated as an invocation
+ * on the previous result.
+ * 3) If the Completion object's execute returns Some(_), we inject that value
+ * and avoid the interpreter, as it's likely not valid scala code.
*/
- if (code == "") None
- else if (!paste.running && code.trim.startsWith(PromptString)) {
- paste.transcript(code)
- None
- }
- else if (Completion.looksLikeInvocation(code) && intp.mostRecentVar != "") {
- interpretStartingWith(intp.mostRecentVar + code)
+ code match {
+ case "" => None
+ case lineComment() => None // line comment, do nothing
+ case paste() if !paste.running => paste.transcript(code) ; None
+ case invocation() if intp.mostRecentVar != "" => interpretStartingWith(intp.mostRecentVar + code)
+ case _ => reallyInterpret
}
- else if (code.trim startsWith "//") {
- // line comment, do nothing
- None
- }
- else
- reallyInterpret._2
}
// runs :load `file` on any files passed via -i
diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala
index e355d9f864..2550a5dc57 100644
--- a/src/repl/scala/tools/nsc/interpreter/IMain.scala
+++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala
@@ -112,12 +112,13 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
def this(factory: ScriptEngineFactory) = this(factory, new Settings())
def this() = this(new Settings())
- lazy val formatting: Formatting = new Formatting {
- val prompt = Properties.shellPromptString
- }
+ // the expanded prompt but without color escapes and without leading newline, for purposes of indenting
+ lazy val formatting: Formatting = new Formatting(
+ (replProps.promptString format Properties.versionNumberString).lines.toList.last.length
+ )
lazy val reporter: ReplReporter = new ReplReporter(this)
- import formatting._
+ import formatting.indentCode
import reporter.{ printMessage, printUntruncatedMessage }
// This exists mostly because using the reporter too early leads to deadlock.
@@ -468,7 +469,7 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
}
private def requestFromLine(line: String, synthetic: Boolean): Either[IR.Result, Request] = {
- val content = indentCode(line)
+ val content = line //indentCode(line)
val trees = parse(content) match {
case parse.Incomplete => return Left(IR.Incomplete)
case parse.Error => return Left(IR.Error)
@@ -909,10 +910,10 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
else List("def %s = %s".format("$line", tquoted(originalLine)), "def %s = Nil".format("$trees"))
}
def preamble = s"""
- |$preambleHeader
- |%s%s%s
- """.stripMargin.format(lineRep.readName, envLines.map(" " + _ + ";\n").mkString,
- importsPreamble, indentCode(toCompute))
+ |${preambleHeader format lineRep.readName}
+ |${envLines mkString (" ", ";\n ", ";\n")}
+ |$importsPreamble
+ |${indentCode(toCompute)}""".stripMargin
val generate = (m: MemberHandler) => m extraCodeToEvaluate Request.this
diff --git a/src/repl/scala/tools/nsc/interpreter/JavapClass.scala b/src/repl/scala/tools/nsc/interpreter/JavapClass.scala
index 1ccade2172..c240ab027a 100644
--- a/src/repl/scala/tools/nsc/interpreter/JavapClass.scala
+++ b/src/repl/scala/tools/nsc/interpreter/JavapClass.scala
@@ -747,15 +747,15 @@ object JavapClass {
// on second thought, we don't care about lambda method classes, just the impl methods
val rev =
res flatMap {
- case x @ closure(_, "lambda", _, _) => labdaMethod(x, target)
- //target.member flatMap (_ => labdaMethod(x, target)) getOrElse s"${target.name}#$$anonfun"
+ case x @ closure(_, "lambda", _, _) => lambdaMethod(x, target)
+ //target.member flatMap (_ => lambdaMethod(x, target)) getOrElse s"${target.name}#$$anonfun"
case x => Some(x)
}
rev
}
// given C$lambda$$g$n for member g and n in 1..N, find the C.accessor$x
// and the C.$anonfun$x it forwards to.
- def labdaMethod(lambda: String, target: Target): Option[String] = {
+ def lambdaMethod(lambda: String, target: Target): Option[String] = {
import scala.tools.asm.ClassReader
import scala.tools.asm.Opcodes.INVOKESTATIC
import scala.tools.asm.tree.{ ClassNode, MethodInsnNode }
diff --git a/src/repl/scala/tools/nsc/interpreter/Pasted.scala b/src/repl/scala/tools/nsc/interpreter/Pasted.scala
index f5db3d9e3a..5f388eb15b 100644
--- a/src/repl/scala/tools/nsc/interpreter/Pasted.scala
+++ b/src/repl/scala/tools/nsc/interpreter/Pasted.scala
@@ -16,17 +16,21 @@ package interpreter
* the same result.
*/
abstract class Pasted {
+ def interpret(line: String): Unit
def ContinueString: String
def PromptString: String
- def interpret(line: String): Unit
+ def AltPromptString: String = "scala> "
+
+ private val testBoth = PromptString != AltPromptString
+ private val spacey = " \t".toSet
- def matchesPrompt(line: String) = matchesString(line, PromptString)
+ def matchesPrompt(line: String) = matchesString(line, PromptString) || testBoth && matchesString(line, AltPromptString)
def matchesContinue(line: String) = matchesString(line, ContinueString)
def running = isRunning
private def matchesString(line: String, target: String): Boolean = (
(line startsWith target) ||
- (line.nonEmpty && " \t".toSet(line.head) && matchesString(line.tail, target))
+ (line.nonEmpty && spacey(line.head) && matchesString(line.tail, target))
)
private def stripString(line: String, target: String) = line indexOf target match {
case -1 => line
@@ -39,7 +43,9 @@ abstract class Pasted {
private class PasteAnalyzer(val lines: List[String]) {
val referenced = lines flatMap (resReference findAllIn _.trim.stripPrefix("res")) toSet
- val cmds = lines reduceLeft append split PromptString filterNot (_.trim == "") toList
+ val ActualPromptString = lines find matchesPrompt map (s =>
+ if (matchesString(s, PromptString)) PromptString else AltPromptString) getOrElse PromptString
+ val cmds = lines reduceLeft append split ActualPromptString filterNot (_.trim == "") toList
/** If it's a prompt or continuation line, strip the formatting bits and
* assemble the code. Otherwise ship it off to be analyzed for res references
@@ -67,10 +73,10 @@ abstract class Pasted {
*/
def fixResRefs(code: String, line: String) = line match {
case resCreation(resName) if referenced(resName) =>
- code.lastIndexOf(PromptString) match {
+ code.lastIndexOf(ActualPromptString) match {
case -1 => code
case idx =>
- val (str1, str2) = code splitAt (idx + PromptString.length)
+ val (str1, str2) = code splitAt (idx + ActualPromptString.length)
str2 match {
case resAssign(`resName`) => code
case _ => "%sval %s = { %s }".format(str1, resName, str2)
@@ -79,10 +85,10 @@ abstract class Pasted {
case _ => code
}
- def run() {
+ def run(): Unit = {
println("// Replaying %d commands from transcript.\n" format cmds.size)
cmds foreach { cmd =>
- print(PromptString)
+ print(ActualPromptString)
interpret(cmd)
}
}
diff --git a/src/repl/scala/tools/nsc/interpreter/ReplProps.scala b/src/repl/scala/tools/nsc/interpreter/ReplProps.scala
index 8c4faf7278..df65e9974d 100644
--- a/src/repl/scala/tools/nsc/interpreter/ReplProps.scala
+++ b/src/repl/scala/tools/nsc/interpreter/ReplProps.scala
@@ -6,12 +6,13 @@
package scala.tools.nsc
package interpreter
+import Properties.shellPromptString
import scala.sys._
import Prop._
class ReplProps {
private def bool(name: String) = BooleanProp.keyExists(name)
- private def int(name: String) = IntProp(name)
+ private def int(name: String) = Prop[Int](name)
// This property is used in TypeDebugging. Let's recycle it.
val colorOk = bool("scala.color")
@@ -21,6 +22,14 @@ class ReplProps {
val trace = bool("scala.repl.trace")
val power = bool("scala.repl.power")
+ // Handy system prop for shell prompt, or else pick it up from compiler.properties
+ val promptString = Prop[String]("scala.repl.prompt").option getOrElse (if (info) "%nscala %s> " else shellPromptString)
+ val prompt = {
+ import scala.io.AnsiColor.{ MAGENTA, RESET }
+ val p = promptString format Properties.versionNumberString
+ if (colorOk) s"$MAGENTA$p$RESET" else p
+ }
+
/** CSV of paged,across to enable pagination or `-x` style
* columns, "across" instead of down the column. Since
* pagination turns off columnar output, these flags are
diff --git a/src/scaladoc/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala b/src/scaladoc/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala
index b541cf721b..320a8e23b2 100644
--- a/src/scaladoc/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala
+++ b/src/scaladoc/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala
@@ -383,7 +383,7 @@ class DotDiagramGenerator(settings: doc.Settings, dotRunner: DotRunner) extends
if (dotId.count(_ == '|') == 1) {
val Array(klass, id) = dotId.toString.split("\\|")
/* Sometimes dot "forgets" to add the image -- that's very annoying, but it seems pretty random, and simple
- * tests like excute 20K times and diff the output don't trigger the bug -- so it's up to us to place the image
+ * tests like execute 20K times and diff the output don't trigger the bug -- so it's up to us to place the image
* back in the node */
val kind = getKind(klass)
if (kind != "")
diff --git a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala
index 03d71f15a3..3cbcbc433e 100644
--- a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala
+++ b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala
@@ -478,7 +478,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
override lazy val comment = {
def nonRootTemplate(sym: Symbol): Option[DocTemplateImpl] =
if (sym eq RootPackage) None else findTemplateMaybe(sym)
- /* Variable precendence order for implicitly added members: Take the variable defifinitions from ...
+ /* Variable precendence order for implicitly added members: Take the variable definitions from ...
* 1. the target of the implicit conversion
* 2. the definition template (owner)
* 3. the current template