diff options
Diffstat (limited to 'src')
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 |