diff options
71 files changed, 1171 insertions, 454 deletions
diff --git a/bincompat-backward.whitelist.conf b/bincompat-backward.whitelist.conf index bb94f4be6c..af80bedf5b 100644 --- a/bincompat-backward.whitelist.conf +++ b/bincompat-backward.whitelist.conf @@ -12,7 +12,10 @@ filter { { matchName="scala.reflect.runtime.SymbolLoaders#TopClassCompleter.this" problemName=IncompatibleMethTypeProblem + }, + { + matchName="scala.sys.process.ProcessImpl#CompoundProcess.getExitValue" + problemName=DirectMissingMethodProblem } - ] } diff --git a/bincompat-forward.whitelist.conf b/bincompat-forward.whitelist.conf index 705fa031ab..541268e50e 100644 --- a/bincompat-forward.whitelist.conf +++ b/bincompat-forward.whitelist.conf @@ -12,6 +12,14 @@ filter { { matchName="scala.reflect.runtime.SymbolLoaders#TopClassCompleter.this" problemName=IncompatibleMethTypeProblem + }, + { + matchName="scala.sys.process.ProcessImpl#CompoundProcess.futureValue" + problemName=DirectMissingMethodProblem + }, + { + matchName="scala.sys.process.ProcessImpl#CompoundProcess.futureThread" + problemName=DirectMissingMethodProblem } ] } diff --git a/scripts/jobs/integrate/bootstrap b/scripts/jobs/integrate/bootstrap index ed1e05251a..7c045ae918 100755 --- a/scripts/jobs/integrate/bootstrap +++ b/scripts/jobs/integrate/bootstrap @@ -430,7 +430,7 @@ removeExistingBuilds() { local scalaLangModules=`curl -s $storageApiUrl/org/scala-lang | jq -r '.children | .[] | "org/scala-lang" + .uri' | grep -v actors-migration` for module in $scalaLangModules; do - local artifacts=`curl -s $storageApiUrl/$module | jq -r ".children | .[] | select(.uri | contains(\"$SCALA_VER\")) | .uri"` + local artifacts=`curl -s $storageApiUrl/$module | jq -r ".children | .[] | select(.uri | endswith(\"$SCALA_VER\")) | .uri"` for artifact in $artifacts; do echo "Deleting $releaseTempRepoUrl$module$artifact" curl -s --netrc-file $netrcFile -X DELETE $releaseTempRepoUrl$module$artifact diff --git a/spec/01-lexical-syntax.md b/spec/01-lexical-syntax.md index e4764c10dc..78f1a1a408 100644 --- a/spec/01-lexical-syntax.md +++ b/spec/01-lexical-syntax.md @@ -55,7 +55,7 @@ plainid ::= upper idrest | varid | op id ::= plainid - | ‘`’ stringLiteral ‘`’ + | ‘`’ { charNoBackQuoteOrNewline | UnicodeEscape | charEscapeSeq } ‘`’ idrest ::= {letter | digit} [‘_’ op] ``` diff --git a/spec/13-syntax-summary.md b/spec/13-syntax-summary.md index dd042824f4..be5cc1324e 100644 --- a/spec/13-syntax-summary.md +++ b/spec/13-syntax-summary.md @@ -38,7 +38,7 @@ plainid ::= upper idrest | varid | op id ::= plainid - | ‘`’ stringLiteral ‘`’ + | ‘`’ { charNoBackQuoteOrNewline | UnicodeEscape | charEscapeSeq } ‘`’ idrest ::= {letter | digit} [‘_’ op] integerLiteral ::= (decimalNumeral | hexNumeral) [‘L’ | ‘l’] diff --git a/spec/_layouts/default.yml b/spec/_layouts/default.yml index 06d8c1c118..983d4e56ae 100644 --- a/spec/_layouts/default.yml +++ b/spec/_layouts/default.yml @@ -15,9 +15,9 @@ } }); </script> - <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script> + <script type="text/javascript" src="//cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script> <script src="//code.jquery.com/jquery-2.1.3.min.js"></script> - <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.2/styles/default.min.css"> + <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.2/styles/default.min.css"> <!-- need to use include to see value of page.chapter variable --> <style type="text/css"> {% include numbering.css %} diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index a7880c72d7..e58d2d3b43 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -95,6 +95,9 @@ class Global(var currentSettings: Settings, var reporter: Reporter) type ThisPlatform = JavaPlatform { val global: Global.this.type } lazy val platform: ThisPlatform = new GlobalPlatform + /* A hook for the REPL to add a classpath entry containing products of previous runs to inliner's bytecode repository*/ + // Fixes SI-8779 + def optimizerClassPath(base: ClassPath): ClassPath = base def classPath: ClassPath = platform.classPath diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 151926b8e7..121091fe4f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -123,10 +123,19 @@ abstract class BTypes { * has the method. */ val indyLambdaImplMethods: mutable.AnyRefMap[InternalName, mutable.LinkedHashSet[asm.Handle]] = recordPerRunCache(mutable.AnyRefMap()) - def addIndyLambdaImplMethod(hostClass: InternalName, handle: Seq[asm.Handle]): Unit = { + def addIndyLambdaImplMethod(hostClass: InternalName, handle: Seq[asm.Handle]): Seq[asm.Handle] = { + if (handle.isEmpty) Nil else { + val set = indyLambdaImplMethods.getOrElseUpdate(hostClass, mutable.LinkedHashSet()) + val added = handle.filterNot(set) + set ++= handle + added + } + } + def removeIndyLambdaImplMethod(hostClass: InternalName, handle: Seq[asm.Handle]): Unit = { if (handle.nonEmpty) - indyLambdaImplMethods.getOrElseUpdate(hostClass, mutable.LinkedHashSet()) ++= handle + indyLambdaImplMethods.getOrElseUpdate(hostClass, mutable.LinkedHashSet()) --= handle } + def getIndyLambdaImplMethods(hostClass: InternalName): Iterable[asm.Handle] = { indyLambdaImplMethods.getOrNull(hostClass) match { case null => Nil diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index edb75514e8..f7ee36c1ba 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -37,7 +37,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val coreBTypes = new CoreBTypesProxy[this.type](this) import coreBTypes._ - val byteCodeRepository: ByteCodeRepository[this.type] = new ByteCodeRepository(global.classPath, this) + val byteCodeRepository: ByteCodeRepository[this.type] = new ByteCodeRepository(global.optimizerClassPath(global.classPath), this) val localOpt: LocalOpt[this.type] = new LocalOpt(this) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala index 72a371cabc..e6ae073a2a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala @@ -185,53 +185,61 @@ object BackendReporting { def name: String def descriptor: String - def calleeMethodSig = BackendReporting.methodSignature(calleeDeclarationClass, name, descriptor) - - override def toString = this match { - case IllegalAccessInstruction(_, _, _, callsiteClass, instruction) => - s"The callee $calleeMethodSig contains the instruction ${AsmUtils.textify(instruction)}" + - s"\nthat would cause an IllegalAccessError when inlined into class $callsiteClass." - - case IllegalAccessCheckFailed(_, _, _, callsiteClass, instruction, cause) => - s"Failed to check if $calleeMethodSig can be safely inlined to $callsiteClass without causing an IllegalAccessError. Checking instruction ${AsmUtils.textify(instruction)} failed:\n" + cause + /** Either the callee or the callsite is annotated @inline */ + def annotatedInline: Boolean - case MethodWithHandlerCalledOnNonEmptyStack(_, _, _, callsiteClass, callsiteName, callsiteDesc) => - s"""The operand stack at the callsite in ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)} contains more values than the - |arguments expected by the callee $calleeMethodSig. These values would be discarded - |when entering an exception handler declared in the inlined method.""".stripMargin - - case SynchronizedMethod(_, _, _) => - s"Method $calleeMethodSig cannot be inlined because it is synchronized." + def calleeMethodSig = BackendReporting.methodSignature(calleeDeclarationClass, name, descriptor) - case StrictfpMismatch(_, _, _, callsiteClass, callsiteName, callsiteDesc) => - s"""The callsite method ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)} - |does not have the same strictfp mode as the callee $calleeMethodSig. + override def toString = { + val annotWarn = if (annotatedInline) " is annotated @inline but" else "" + val warning = s"$calleeMethodSig$annotWarn could not be inlined:\n" + val reason = this match { + case CalleeNotFinal(_, _, _, _) => + s"The method is not final and may be overridden." + case IllegalAccessInstruction(_, _, _, _, callsiteClass, instruction) => + s"The callee $calleeMethodSig contains the instruction ${AsmUtils.textify(instruction)}" + + s"\nthat would cause an IllegalAccessError when inlined into class $callsiteClass." + + case IllegalAccessCheckFailed(_, _, _, _, callsiteClass, instruction, cause) => + s"Failed to check if $calleeMethodSig can be safely inlined to $callsiteClass without causing an IllegalAccessError. Checking instruction ${AsmUtils.textify(instruction)} failed:\n" + cause + + case MethodWithHandlerCalledOnNonEmptyStack(_, _, _, _, callsiteClass, callsiteName, callsiteDesc) => + s"""The operand stack at the callsite in ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)} contains more values than the + |arguments expected by the callee $calleeMethodSig. These values would be discarded + |when entering an exception handler declared in the inlined method.""".stripMargin + + case SynchronizedMethod(_, _, _, _) => + s"Method $calleeMethodSig cannot be inlined because it is synchronized." + + case StrictfpMismatch(_, _, _, _, callsiteClass, callsiteName, callsiteDesc) => + s"""The callsite method ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)} + |does not have the same strictfp mode as the callee $calleeMethodSig. """.stripMargin - case ResultingMethodTooLarge(_, _, _, callsiteClass, callsiteName, callsiteDesc) => - s"""The size of the callsite method ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)} - |would exceed the JVM method size limit after inlining $calleeMethodSig. + case ResultingMethodTooLarge(_, _, _, _, callsiteClass, callsiteName, callsiteDesc) => + s"""The size of the callsite method ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)} + |would exceed the JVM method size limit after inlining $calleeMethodSig. """.stripMargin + } + warning + reason } - def emitWarning(settings: ScalaSettings): Boolean = this match { - case _: IllegalAccessInstruction | _: MethodWithHandlerCalledOnNonEmptyStack | _: SynchronizedMethod | _: StrictfpMismatch | _: ResultingMethodTooLarge => - settings.optWarnings.contains(settings.optWarningsChoices.anyInlineFailed) - - case IllegalAccessCheckFailed(_, _, _, _, _, cause) => - cause.emitWarning(settings) + def emitWarning(settings: ScalaSettings): Boolean = { + settings.optWarnings.contains(settings.optWarningsChoices.anyInlineFailed) || + annotatedInline && settings.optWarningEmitAtInlineFailed } } - case class IllegalAccessInstruction(calleeDeclarationClass: InternalName, name: String, descriptor: String, + case class CalleeNotFinal(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean) extends CannotInlineWarning + case class IllegalAccessInstruction(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean, callsiteClass: InternalName, instruction: AbstractInsnNode) extends CannotInlineWarning - case class IllegalAccessCheckFailed(calleeDeclarationClass: InternalName, name: String, descriptor: String, + case class IllegalAccessCheckFailed(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean, callsiteClass: InternalName, instruction: AbstractInsnNode, cause: OptimizerWarning) extends CannotInlineWarning - case class MethodWithHandlerCalledOnNonEmptyStack(calleeDeclarationClass: InternalName, name: String, descriptor: String, + case class MethodWithHandlerCalledOnNonEmptyStack(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean, callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning - case class SynchronizedMethod(calleeDeclarationClass: InternalName, name: String, descriptor: String) extends CannotInlineWarning - case class StrictfpMismatch(calleeDeclarationClass: InternalName, name: String, descriptor: String, + case class SynchronizedMethod(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean) extends CannotInlineWarning + case class StrictfpMismatch(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean, callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning - case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: String, descriptor: String, + case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: String, descriptor: String, annotatedInline: Boolean, callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning // TODO: this should be a subtype of CannotInlineWarning 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 e0fd77bb54..9c0dfb0ee2 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -137,7 +137,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { Callee( callee = method, calleeDeclarationClass = declarationClassBType, - safeToInline = safeToInline, + isStaticallyResolved = isStaticallyResolved, sourceFilePath = sourceFilePath, annotatedInline = annotatedInline, annotatedNoInline = annotatedNoInline, @@ -256,7 +256,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { /** * Just a named tuple used as return type of `analyzeCallsite`. */ - private case class CallsiteInfo(safeToInline: Boolean, sourceFilePath: Option[String], + private case class CallsiteInfo(isStaticallyResolved: Boolean, sourceFilePath: Option[String], annotatedInline: Boolean, annotatedNoInline: Boolean, samParamTypes: IntMap[ClassBType], warning: Option[CalleeInfoWarning]) @@ -293,7 +293,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { // TODO: type analysis can render more calls statically resolved. Example: // new A.f // can be inlined, the receiver type is known to be exactly A. val isStaticallyResolved: Boolean = { - isNonVirtualCall(call) || // SD-86: super calls (invokespecial) can be inlined + isNonVirtualCall(call) || // SD-86: super calls (invokespecial) can be inlined -- TODO: check if that's still needed, and if it's correct: scala-dev#143 methodInlineInfo.effectivelyFinal || receiverType.info.orThrow.inlineInfo.isEffectivelyFinal // (1) } @@ -301,22 +301,13 @@ class CallGraph[BT <: BTypes](val btypes: BT) { val warning = calleeDeclarationClassBType.info.orThrow.inlineInfo.warning.map( MethodInlineInfoIncomplete(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, _)) - // (1) For invocations of final trait methods, the callee isStaticallyResolved but also - // abstract. Such a callee is not safe to inline - it needs to be re-written to the - // static impl method first (safeToRewrite). CallsiteInfo( - safeToInline = - inlinerHeuristics.canInlineFromSource(calleeSourceFilePath) && - isStaticallyResolved && // (1) - !isAbstract && - !BytecodeUtils.isConstructor(calleeMethodNode) && - !BytecodeUtils.isNativeMethod(calleeMethodNode) && - !BytecodeUtils.hasCallerSensitiveAnnotation(calleeMethodNode), - sourceFilePath = calleeSourceFilePath, - annotatedInline = methodInlineInfo.annotatedInline, - annotatedNoInline = methodInlineInfo.annotatedNoInline, - samParamTypes = samParamTypes(calleeMethodNode, receiverType), - warning = warning) + isStaticallyResolved = isStaticallyResolved, + sourceFilePath = calleeSourceFilePath, + annotatedInline = methodInlineInfo.annotatedInline, + annotatedNoInline = methodInlineInfo.annotatedNoInline, + samParamTypes = samParamTypes(calleeMethodNode, receiverType), + warning = warning) case None => val warning = MethodInlineInfoMissing(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, calleeDeclarationClassBType.info.orThrow.inlineInfo.warning) @@ -353,6 +344,10 @@ class CallGraph[BT <: BTypes](val btypes: BT) { */ val inlinedClones = mutable.Set.empty[ClonedCallsite] + // an annotation at the callsite takes precedence over an annotation at the definition site + def isInlineAnnotated = annotatedInline || (callee.get.annotatedInline && !annotatedNoInline) + def isNoInlineAnnotated = annotatedNoInline || (callee.get.annotatedNoInline && !annotatedInline) + override def toString = "Invocation of" + s" ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}" + @@ -378,8 +373,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { * virtual calls, an override of the callee might be invoked. Also, * the callee can be abstract. * @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 isStaticallyResolved True if the callee cannot be overridden * @param annotatedInline True if the callee is annotated @inline * @param annotatedNoInline True if the callee is annotated @noinline * @param samParamTypes A map from parameter positions to SAM parameter types @@ -387,11 +381,17 @@ class CallGraph[BT <: BTypes](val btypes: BT) { * gathering the information about this callee. */ final case class Callee(callee: MethodNode, calleeDeclarationClass: btypes.ClassBType, - safeToInline: Boolean, sourceFilePath: Option[String], + isStaticallyResolved: Boolean, sourceFilePath: Option[String], annotatedInline: Boolean, annotatedNoInline: Boolean, samParamTypes: IntMap[btypes.ClassBType], calleeInfoWarning: Option[CalleeInfoWarning]) { override def toString = s"Callee($calleeDeclarationClass.${callee.name})" + + def canInlineFromSource = inlinerHeuristics.canInlineFromSource(sourceFilePath) + def isAbstract = isAbstractMethod(callee) + def isSpecialMethod = isConstructor(callee) || isNativeMethod(callee) || hasCallerSensitiveAnnotation(callee) + + def safeToInline = isStaticallyResolved && canInlineFromSource && !isAbstract && !isSpecialMethod } /** diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala index 35ee5ba13d..2fca8991ab 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala @@ -359,7 +359,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { Callee( callee = bodyMethodNode, calleeDeclarationClass = bodyDeclClassType, - safeToInline = inlinerHeuristics.canInlineFromSource(sourceFilePath), + isStaticallyResolved = true, sourceFilePath = sourceFilePath, annotatedInline = false, annotatedNoInline = false, @@ -392,7 +392,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { // (x: T) => ??? has return type Nothing$, and an ATHROW is added (see fixLoadedNothingOrNullValue). unreachableCodeEliminated -= ownerMethod - if (hasAdaptedImplMethod(closureInit) && inliner.canInlineBody(bodyMethodCallsite).isEmpty) + if (hasAdaptedImplMethod(closureInit) && inliner.canInlineCallsite(bodyMethodCallsite).isEmpty) inliner.inlineCallsite(bodyMethodCallsite) } 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 64638ca34d..b9f593a4d8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -25,49 +25,110 @@ class Inliner[BT <: BTypes](val btypes: BT) { import inlinerHeuristics._ import backendUtils._ - case class InlineLog(request: InlineRequest, sizeBefore: Int, sizeAfter: Int, sizeInlined: Int, warning: Option[CannotInlineWarning]) - var inlineLog: List[InlineLog] = Nil + sealed trait InlineLog { + def request: InlineRequest + } + final case class InlineLogSuccess(request: InlineRequest, sizeBefore: Int, sizeInlined: Int) extends InlineLog { + var downstreamLog: mutable.Buffer[InlineLog] = mutable.ListBuffer.empty + } + final case class InlineLogFail(request: InlineRequest, warning: CannotInlineWarning) extends InlineLog + final case class InlineLogRollback(request: InlineRequest, warnings: List[CannotInlineWarning]) extends InlineLog + + object InlineLog { + private def shouldLog(request: InlineRequest): Boolean = { + def logEnabled = compilerSettings.YoptLogInline.isSetByUser + def matchesName = { + val prefix = compilerSettings.YoptLogInline.value match { + case "_" => "" + case p => p + } + val name: String = request.callsite.callsiteClass.internalName + "." + request.callsite.callsiteMethod.name + name startsWith prefix + } + logEnabled && (upstream != null || (isTopLevel && matchesName)) + } - def runInliner(): Unit = { - for (request <- collectAndOrderInlineRequests) { - val Right(callee) = request.callsite.callee // collectAndOrderInlineRequests returns callsites with a known callee + // indexed by callsite method + private val logs = mutable.Map.empty[MethodNode, mutable.LinkedHashSet[InlineLog]] - // TODO: if the request has downstream requests, create a snapshot to which we could roll back in case some downstream callsite cannot be inlined - // (Needs to revert modifications to the callee method, but also the call graph) - // (This assumes that inlining a request only makes sense if its downstream requests are satisfied - sync with heuristics!) + private var upstream: InlineLogSuccess = _ + private var isTopLevel = true - val warnings = inline(request) - for (warning <- warnings) { - if ((callee.annotatedInline && btypes.compilerSettings.optWarningEmitAtInlineFailed) || warning.emitWarning(compilerSettings)) { - val annotWarn = if (callee.annotatedInline) " is annotated @inline but" else "" - val msg = s"${BackendReporting.methodSignature(callee.calleeDeclarationClass.internalName, callee.callee)}$annotWarn could not be inlined:\n$warning" - backendReporting.inlinerWarning(request.callsite.callsitePosition, msg) - } + def withInlineLogging[T](request: InlineRequest)(inlineRequest: => Unit)(inlinePost: => T): T = { + def doInlinePost(): T = { + val savedIsTopLevel = isTopLevel + isTopLevel = false + try inlinePost + finally isTopLevel = savedIsTopLevel + } + if (shouldLog(request)) { + val sizeBefore = request.callsite.callsiteMethod.instructions.size + inlineRequest + val log = InlineLogSuccess(request, sizeBefore, request.callsite.callee.get.callee.instructions.size) + apply(log) + + val savedUpstream = upstream + upstream = log + try doInlinePost() + finally upstream = savedUpstream + } else { + inlineRequest + doInlinePost() } } - if (compilerSettings.YoptLogInline.isSetByUser) { - val methodPrefix = { val p = compilerSettings.YoptLogInline.value; if (p == "_") "" else p } - val byCallsiteMethod = inlineLog.groupBy(_.request.callsite.callsiteMethod).toList.sortBy(_._2.head.request.callsite.callsiteClass.internalName) - for ((m, mLogs) <- byCallsiteMethod) { - val initialSize = mLogs.minBy(_.sizeBefore).sizeBefore - val firstLog = mLogs.head - val methodName = s"${firstLog.request.callsite.callsiteClass.internalName}.${m.name}" - if (methodName.startsWith(methodPrefix)) { - println(s"Inlining into $methodName (initially $initialSize instructions, ultimately ${m.instructions.size}):") - val byCallee = mLogs.groupBy(_.request.callsite.callee.get).toList.sortBy(_._2.length).reverse - for ((c, cLogs) <- byCallee) { - val first = cLogs.head - if (first.warning.isEmpty) { - val num = if (cLogs.tail.isEmpty) "" else s" ${cLogs.length} times" - println(s" - Inlined ${c.calleeDeclarationClass.internalName}.${c.callee.name} (${first.sizeInlined} instructions)$num: ${first.request.reason}") - } else - println(s" - Failed to inline ${c.calleeDeclarationClass.internalName}.${c.callee.name} (${first.request.reason}): ${first.warning.get}") - } - println() - } + def apply(log: => InlineLog): Unit = if (shouldLog(log.request)) { + if (upstream != null) upstream.downstreamLog += log + else { + val methodLogs = logs.getOrElseUpdate(log.request.callsite.callsiteMethod, mutable.LinkedHashSet.empty) + methodLogs += log + } + } + + def entryString(log: InlineLog, indent: Int = 0): String = { + val callee = log.request.callsite.callee.get + val calleeString = callee.calleeDeclarationClass.internalName + "." + callee.callee.name + val indentString = " " * indent + log match { + case s @ InlineLogSuccess(_, sizeBefore, sizeInlined) => + val self = s"${indentString}inlined $calleeString. Before: $sizeBefore ins, inlined: $sizeInlined ins." + if (s.downstreamLog.isEmpty) self + else s.downstreamLog.iterator.map(entryString(_, indent + 2)).mkString(self + "\n", "\n", "") + + case InlineLogFail(_, w) => + s"${indentString}failed $calleeString. ${w.toString.replace('\n', ' ')}" + + case InlineLogRollback(_, _) => + s"${indentString}rolling back, nested inline failed." + } + } + + def print(): Unit = if (compilerSettings.YoptLogInline.isSetByUser) { + val byClassAndMethod: List[(InternalName, mutable.Map[MethodNode, mutable.LinkedHashSet[InlineLog]])] = { + logs. + groupBy(_._2.head.request.callsite.callsiteClass.internalName). + toList.sortBy(_._1) + } + for { + (c, methodLogs) <- byClassAndMethod + (m, mLogs) <- methodLogs.toList.sortBy(_._1.name) + mLog <- mLogs // insertion order + } { + println(s"Inline into $c.${m.name}: ${entryString(mLog)}") + } + } + } + + def runInliner(): Unit = { + for (request <- collectAndOrderInlineRequests) { + val Right(callee) = request.callsite.callee // collectAndOrderInlineRequests returns callsites with a known callee + val warnings = inline(request) + for (warning <- warnings) { + if (warning.emitWarning(compilerSettings)) + backendReporting.inlinerWarning(request.callsite.callsitePosition, warning.toString) } } + InlineLog.print() } /** @@ -221,26 +282,79 @@ class Inliner[BT <: BTypes](val btypes: BT) { impl(post, mainCallsite) } + class UndoLog(active: Boolean = true) { + import java.util.{ ArrayList => JArrayList } + + private var actions = List.empty[() => Unit] + private var methodStateSaved = false + + def apply(a: => Unit): Unit = if (active) actions = (() => a) :: actions + def rollback(): Unit = if (active) actions.foreach(_.apply()) + + def saveMethodState(methodNode: MethodNode): Unit = if (active && !methodStateSaved) { + methodStateSaved = true + val currentInstructions = methodNode.instructions.toArray + val currentLocalVariables = new JArrayList(methodNode.localVariables) + val currentTryCatchBlocks = new JArrayList(methodNode.tryCatchBlocks) + val currentMaxLocals = methodNode.maxLocals + val currentMaxStack = methodNode.maxStack + + apply { + // `methodNode.instructions.clear()` doesn't work: it keeps the `prev` / `next` / `index` of + // instruction nodes. `instructions.removeAll(true)` would work, but is not public. + methodNode.instructions.iterator.asScala.toList.foreach(methodNode.instructions.remove) + for (i <- currentInstructions) methodNode.instructions.add(i) + + methodNode.localVariables.clear() + methodNode.localVariables.addAll(currentLocalVariables) + + methodNode.tryCatchBlocks.clear() + methodNode.tryCatchBlocks.addAll(currentTryCatchBlocks) + + methodNode.maxLocals = currentMaxLocals + methodNode.maxStack = currentMaxStack + } + } + } + + val NoUndoLogging = new UndoLog(active = false) /** * Inline the callsite of an inlining request and its post-inlining requests. * * @return An inliner warning for each callsite that could not be inlined. */ - def inline(request: InlineRequest): List[CannotInlineWarning] = canInlineBody(request.callsite) match { - case Some(w) => - if (compilerSettings.YoptLogInline.isSetByUser) { - val size = request.callsite.callsiteMethod.instructions.size - inlineLog ::= InlineLog(request, size, size, 0, Some(w)) + def inline(request: InlineRequest, undo: UndoLog = NoUndoLogging): List[CannotInlineWarning] = { + def doInline(undo: UndoLog, callRollback: Boolean = false): List[CannotInlineWarning] = { + InlineLog.withInlineLogging(request) { + inlineCallsite(request.callsite, undo) + } { + val postRequests = request.post.flatMap(adaptPostRequestForMainCallsite(_, request.callsite)) + val warnings = postRequests.flatMap(inline(_, undo)) + if (callRollback && warnings.nonEmpty) { + undo.rollback() + InlineLog(InlineLogRollback(request, warnings)) + } + warnings } - List(w) - case None => - val sizeBefore = request.callsite.callsiteMethod.instructions.size - inlineCallsite(request.callsite) - if (compilerSettings.YoptLogInline.isSetByUser) - inlineLog ::= InlineLog(request, sizeBefore, request.callsite.callsiteMethod.instructions.size, request.callsite.callee.get.callee.instructions.size, None) - val postRequests = request.post.flatMap(adaptPostRequestForMainCallsite(_, request.callsite)) - postRequests flatMap inline + } + + def inlinedByPost(insns: List[AbstractInsnNode]): Boolean = + insns.nonEmpty && insns.forall(ins => request.post.exists(_.callsite.callsiteInstruction == ins)) + + canInlineCallsite(request.callsite) match { + case None => + doInline(undo) + + case Some((_, illegalAccessInsns)) if inlinedByPost(illegalAccessInsns) => + // speculatively inline, roll back if an illegalAccessInsn cannot be eliminated + if (undo == NoUndoLogging) doInline(new UndoLog(), callRollback = true) + else doInline(undo) + + case Some((w, _)) => + InlineLog(InlineLogFail(request, w)) + List(w) + } } /** @@ -253,7 +367,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { * @return A map associating instruction nodes of the callee with the corresponding cloned * instruction in the callsite method. */ - def inlineCallsite(callsite: Callsite): Unit = { + def inlineCallsite(callsite: Callsite, undo: UndoLog = NoUndoLogging): Unit = { import callsite.{callsiteClass, callsiteMethod, callsiteInstruction, receiverKnownNotNull, callsiteStackHeight} val Right(callsiteCallee) = callsite.callee import callsiteCallee.{callee, calleeDeclarationClass, sourceFilePath} @@ -380,6 +494,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { clonedInstructions.insert(postCallLabel, retVarLoad) } + undo.saveMethodState(callsiteMethod) + callsiteMethod.instructions.insert(callsiteInstruction, clonedInstructions) callsiteMethod.instructions.remove(callsiteInstruction) @@ -406,7 +522,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, math.max(stackHeightAtNullCheck, maxStackOfInlinedCode)) - addIndyLambdaImplMethod(callsiteClass.internalName, targetHandles) + val added = addIndyLambdaImplMethod(callsiteClass.internalName, targetHandles) + undo { removeIndyLambdaImplMethod(callsiteClass.internalName, added) } callGraph.addIfMissing(callee, calleeDeclarationClass) @@ -426,8 +543,13 @@ class Inliner[BT <: BTypes](val btypes: BT) { argInfos = argInfos, callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight ) - originalCallsite.inlinedClones += ClonedCallsite(newCallsite, callsite) + val clonedCallsite = ClonedCallsite(newCallsite, callsite) + originalCallsite.inlinedClones += clonedCallsite callGraph.addCallsite(newCallsite) + undo { + originalCallsite.inlinedClones -= clonedCallsite + callGraph.removeCallsite(newCallsite.callsiteInstruction, newCallsite.callsiteMethod) + } } callGraph.closureInstantiations(callee).valuesIterator foreach { originalClosureInit => @@ -440,10 +562,14 @@ class Inliner[BT <: BTypes](val btypes: BT) { capturedArgInfos) originalClosureInit.inlinedClones += newClosureInit callGraph.addClosureInstantiation(newClosureInit) + undo { + callGraph.removeClosureInstantiation(newClosureInit.lambdaMetaFactoryCall.indy, newClosureInit.ownerMethod) + } } // Remove the elided invocation from the call graph callGraph.removeCallsite(callsiteInstruction, callsiteMethod) + undo { callGraph.addCallsite(callsite) } // Inlining a method body can render some code unreachable, see example above in this method. unreachableCodeEliminated -= callsiteMethod @@ -467,10 +593,10 @@ class Inliner[BT <: BTypes](val btypes: BT) { if (isSynchronizedMethod(callee)) { // Could be done by locking on the receiver, wrapping the inlined code in a try and unlocking // in finally. But it's probably not worth the effort, scala never emits synchronized methods. - Some(SynchronizedMethod(calleeDeclarationClass.internalName, callee.name, callee.desc)) + Some(SynchronizedMethod(calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated)) } else if (isStrictfpMethod(callsiteMethod) != isStrictfpMethod(callee)) { Some(StrictfpMismatch( - calleeDeclarationClass.internalName, callee.name, callee.desc, + calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated, callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) } else None @@ -486,9 +612,14 @@ class Inliner[BT <: BTypes](val btypes: BT) { * we don't query it while traversing the call graph and selecting callsites to inline - it might * rule out callsites that can be inlined just fine. * - * @return `Some(message)` if inlining cannot be performed, `None` otherwise + * Returns + * - `None` if the callsite can be inlined + * - `Some((message, Nil))` if there was an issue performing the access checks, for example + * because of a missing classfile + * - `Some((message, instructions))` if inlining `instructions` into the callsite method would + * cause an IllegalAccessError */ - def canInlineBody(callsite: Callsite): Option[CannotInlineWarning] = { + def canInlineCallsite(callsite: Callsite): Option[(CannotInlineWarning, List[AbstractInsnNode])] = { import callsite.{callsiteInstruction, callsiteMethod, callsiteClass, callsiteStackHeight} val Right(callsiteCallee) = callsite.callee import callsiteCallee.{callee, calleeDeclarationClass} @@ -519,23 +650,30 @@ class Inliner[BT <: BTypes](val btypes: BT) { } if (codeSizeOKForInlining(callsiteMethod, callee)) { - Some(ResultingMethodTooLarge( - calleeDeclarationClass.internalName, callee.name, callee.desc, - callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) + val warning = ResultingMethodTooLarge( + calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated, + callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc) + Some((warning, Nil)) } else if (!callee.tryCatchBlocks.isEmpty && stackHasNonParameters) { - Some(MethodWithHandlerCalledOnNonEmptyStack( - calleeDeclarationClass.internalName, callee.name, callee.desc, - callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) - } else findIllegalAccess(callee.instructions, calleeDeclarationClass, callsiteClass) map { - case (illegalAccessIns, None) => - IllegalAccessInstruction( - calleeDeclarationClass.internalName, callee.name, callee.desc, - callsiteClass.internalName, illegalAccessIns) - - case (illegalAccessIns, Some(warning)) => - IllegalAccessCheckFailed( - calleeDeclarationClass.internalName, callee.name, callee.desc, - callsiteClass.internalName, illegalAccessIns, warning) + val warning = MethodWithHandlerCalledOnNonEmptyStack( + calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated, + callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc) + Some((warning, Nil)) + } else findIllegalAccess(callee.instructions, calleeDeclarationClass, callsiteClass) match { + case Right(Nil) => + None + + case Right(illegalAccessInsns) => + val warning = IllegalAccessInstruction( + calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated, + callsiteClass.internalName, illegalAccessInsns.head) + Some((warning, illegalAccessInsns)) + + case Left((illegalAccessIns, cause)) => + val warning = IllegalAccessCheckFailed( + calleeDeclarationClass.internalName, callee.name, callee.desc, callsite.isInlineAnnotated, + callsiteClass.internalName, illegalAccessIns, cause) + Some((warning, Nil)) } } @@ -624,13 +762,14 @@ 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. + * Returns + * - `Right(Nil)` if all instructions can be safely inlined + * - `Right(insns)` if inlining any of `insns` would cause a [[java.lang.IllegalAccessError]] + * when inlined into the `destinationClass` + * - `Left((insn, warning))` if validity of some instruction could not be checked because an + * error occurred */ - def findIllegalAccess(instructions: InsnList, calleeDeclarationClass: ClassBType, destinationClass: ClassBType): Option[(AbstractInsnNode, Option[OptimizerWarning])] = { + def findIllegalAccess(instructions: InsnList, calleeDeclarationClass: ClassBType, destinationClass: ClassBType): Either[(AbstractInsnNode, OptimizerWarning), List[AbstractInsnNode]] = { /** * Check if `instruction` can be transplanted to `destinationClass`. * @@ -759,17 +898,15 @@ class Inliner[BT <: BTypes](val btypes: BT) { } val it = instructions.iterator.asScala - @tailrec def find: Option[(AbstractInsnNode, Option[OptimizerWarning])] = { - if (!it.hasNext) None // all instructions are legal - else { - val i = it.next() - isLegal(i) match { - case Left(warning) => Some((i, Some(warning))) // checking isLegal for i failed - case Right(false) => Some((i, None)) // an illegal instruction was found - case _ => find - } + val illegalAccess = mutable.ListBuffer.empty[AbstractInsnNode] + while (it.hasNext) { + val i = it.next() + isLegal(i) match { + case Left(warning) => return Left((i, warning)) // checking isLegal for i failed + case Right(false) => illegalAccess += i // an illegal instruction was found + case _ => } } - find + Right(illegalAccess.toList) } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala index 79e74f3eb7..929e8b5ca4 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala @@ -7,17 +7,18 @@ package scala.tools.nsc package backend.jvm package opt +import scala.annotation.tailrec import scala.collection.JavaConverters._ import scala.tools.asm.Opcodes -import scala.tools.asm.tree.{MethodInsnNode, MethodNode} +import scala.tools.asm.tree.{AbstractInsnNode, MethodInsnNode, MethodNode} import scala.tools.nsc.backend.jvm.BTypes.InternalName -import scala.tools.nsc.backend.jvm.BackendReporting.OptimizerWarning +import scala.tools.nsc.backend.jvm.BackendReporting.{CalleeNotFinal, OptimizerWarning} class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { import bTypes._ import callGraph._ - case class InlineRequest(callsite: Callsite, post: List[InlineRequest], reason: String) { + final case class InlineRequest(callsite: Callsite, post: List[InlineRequest], reason: String) { // invariant: all post inline requests denote callsites in the callee of the main callsite for (pr <- post) assert(pr.callsite.callsiteMethod == callsite.callee.get.callee, s"Callsite method mismatch: main $callsite - post ${pr.callsite}") } @@ -41,30 +42,18 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { compilingMethods.map(methodNode => { var requests = Set.empty[InlineRequest] callGraph.callsites(methodNode).valuesIterator foreach { - case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, sourceFilePath, calleeAnnotatedInline, _, _, callsiteWarning)), _, _, _, pos, _, _) => + case callsite @ Callsite(_, _, _, Right(Callee(callee, _, _, _, _, _, _, callsiteWarning)), _, _, _, pos, _, _) => inlineRequest(callsite, requests) match { case Some(Right(req)) => requests += req - case Some(Left(w)) => - if ((calleeAnnotatedInline && bTypes.compilerSettings.optWarningEmitAtInlineFailed) || w.emitWarning(compilerSettings)) { - val annotWarn = if (calleeAnnotatedInline) " is annotated @inline but" else "" - val msg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)}$annotWarn could not be inlined:\n$w" - backendReporting.inlinerWarning(callsite.callsitePosition, msg) + + case Some(Left(w)) => + if (w.emitWarning(compilerSettings)) { + backendReporting.inlinerWarning(callsite.callsitePosition, w.toString) } case None => - if (canInlineFromSource(sourceFilePath) && calleeAnnotatedInline && !callsite.annotatedNoInline && bTypes.compilerSettings.optWarningEmitAtInlineFailed) { - // if the callsite is annotated @inline, we report an inline warning even if the underlying - // reason is, for example, mixed compilation (which has a separate -opt-warning flag). - def initMsg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)} is annotated @inline but cannot be inlined" - def warnMsg = callsiteWarning.map(" Possible reason:\n" + _).getOrElse("") - if (!safeToInline) - backendReporting.inlinerWarning(pos, s"$initMsg: the method is not final and may be overridden." + warnMsg) - else - backendReporting.inlinerWarning(pos, s"$initMsg." + warnMsg) - } else if (callsiteWarning.isDefined && callsiteWarning.get.emitWarning(compilerSettings)) { - // when annotatedInline is false, and there is some warning, the callsite metadata is possibly incomplete. + if (callsiteWarning.isDefined && callsiteWarning.get.emitWarning(compilerSettings)) backendReporting.inlinerWarning(pos, s"there was a problem determining if method ${callee.name} can be inlined: \n"+ callsiteWarning.get) - } } case Callsite(ins, _, _, Left(warning), _, _, _, pos, _, _) => @@ -75,6 +64,42 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { }).filterNot(_._2.isEmpty).toMap } + private def isTraitStaticSuperAccessorName(s: String) = s.endsWith("$") + + private def isTraitSuperAccessor(method: MethodNode, owner: ClassBType): Boolean = { + owner.isInterface == Right(true) && BytecodeUtils.isStaticMethod(method) && isTraitStaticSuperAccessorName(method.name) + } + + private def findCall(method: MethodNode, such: MethodInsnNode => Boolean): Option[MethodInsnNode] = { + @tailrec def noMoreInvoke(insn: AbstractInsnNode): Boolean = { + insn == null || (!insn.isInstanceOf[MethodInsnNode] && noMoreInvoke(insn.getNext)) + } + @tailrec def find(insn: AbstractInsnNode): Option[MethodInsnNode] = { + if (insn == null) None + else insn match { + case mi: MethodInsnNode => + if (such(mi) && noMoreInvoke(insn.getNext)) Some(mi) + else None + case _ => + find(insn.getNext) + } + } + find(method.instructions.getFirst) + } + private def superAccessorInvocation(method: MethodNode): Option[MethodInsnNode] = + findCall(method, mi => mi.itf && mi.getOpcode == Opcodes.INVOKESTATIC && isTraitStaticSuperAccessorName(mi.name)) + + private def isMixinForwarder(method: MethodNode, owner: ClassBType): Boolean = { + owner.isInterface == Right(false) && + !BytecodeUtils.isStaticMethod(method) && + superAccessorInvocation(method).nonEmpty + } + + private def isTraitSuperAccessorOrMixinForwarder(method: MethodNode, owner: ClassBType): Boolean = { + isTraitSuperAccessor(method, owner) || isMixinForwarder(method, owner) + } + + /** * Returns the inline request for a callsite if the callsite should be inlined according to the * current heuristics (`-Yopt-inline-heuristics`). @@ -90,81 +115,89 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { * `Some(Right)` if the callsite should be and can be inlined */ def inlineRequest(callsite: Callsite, selectedRequestsForCallee: Set[InlineRequest]): Option[Either[OptimizerWarning, InlineRequest]] = { - val callee = callsite.callee.get - def requestIfCanInline(callsite: Callsite, reason: String): Either[OptimizerWarning, InlineRequest] = inliner.earlyCanInlineCheck(callsite) match { - case Some(w) => Left(w) - case None => - val callee = callsite.callee.get - val postInlineRequest: List[InlineRequest] = callee.calleeDeclarationClass.isInterface match { - case Right(true) => - // Treat the pair of trait interface method and static method as one for the purposes of inlining: - // if we inline invokeinterface, invoke the invokestatic, too. - val calls = callee.callee.instructions.iterator().asScala.filter(BytecodeUtils.isCall).take(2).toList - calls match { - case List(x: MethodInsnNode) if x.getOpcode == Opcodes.INVOKESTATIC && x.name == (callee.callee.name + "$") => - callGraph.addIfMissing(callee.callee, callee.calleeDeclarationClass) - val maybeNodeToCallsite1 = callGraph.findCallSite(callee.callee, x) - maybeNodeToCallsite1.toList.flatMap(x => requestIfCanInline(x, reason).right.toOption) - case _ => - Nil - - } - case _ => Nil - } - - Right(InlineRequest(callsite, postInlineRequest, reason)) - + def requestIfCanInline(callsite: Callsite, reason: String): Option[Either[OptimizerWarning, InlineRequest]] = { + val callee = callsite.callee.get + if (!callee.safeToInline) { + if (callsite.isInlineAnnotated && callee.canInlineFromSource) { + // By default, we only emit inliner warnings for methods annotated @inline. However, we don't + // want to be unnecessarily noisy with `-opt-warnings:_`: for example, the inliner heuristic + // would attempty to inline `Function1.apply$sp$II`, as it's higher-order (the receiver is + // a function), and it's concrete (forwards to `apply`). But because it's non-final, it cannot + // be inlined. So we only create warnings here for methods annotated @inline. + Some(Left(CalleeNotFinal( + callee.calleeDeclarationClass.internalName, + callee.callee.name, + callee.callee.desc, + callsite.isInlineAnnotated))) + } else None + } else inliner.earlyCanInlineCheck(callsite) match { + case Some(w) => Some(Left(w)) + case None => + val postInlineRequest: List[InlineRequest] = { + val postCall = + if (isTraitSuperAccessor(callee.callee, callee.calleeDeclarationClass)) { + // scala-dev#259: when inlining a trait super accessor, also inline the callsite to the default method + val implName = callee.callee.name.dropRight(1) + findCall(callee.callee, mi => mi.itf && mi.getOpcode == Opcodes.INVOKESPECIAL && mi.name == implName) + } else { + // scala-dev#259: when inlining a mixin forwarder, also inline the callsite to the static super accessor + superAccessorInvocation(callee.callee) + } + postCall.flatMap(call => { + callGraph.addIfMissing(callee.callee, callee.calleeDeclarationClass) + val maybeCallsite = callGraph.findCallSite(callee.callee, call) + maybeCallsite.flatMap(requestIfCanInline(_, reason).flatMap(_.right.toOption)) + }).toList + } + Some(Right(InlineRequest(callsite, postInlineRequest, reason))) + } } - compilerSettings.YoptInlineHeuristics.value match { - case "everything" => - if (callee.safeToInline) { + // scala-dev#259: don't inline into static accessors and mixin forwarders + if (isTraitSuperAccessorOrMixinForwarder(callsite.callsiteMethod, callsite.callsiteClass)) None + else { + val callee = callsite.callee.get + compilerSettings.YoptInlineHeuristics.value match { + case "everything" => val reason = if (compilerSettings.YoptLogInline.isSetByUser) "the inline strategy is \"everything\"" else null - Some(requestIfCanInline(callsite, reason)) - } - else None + requestIfCanInline(callsite, reason) - case "at-inline-annotated" => - if (callee.safeToInline && callee.annotatedInline) { - val reason = if (compilerSettings.YoptLogInline.isSetByUser) { - val what = if (callee.safeToInline) "callee" else "callsite" + case "at-inline-annotated" => + def reason = if (!compilerSettings.YoptLogInline.isSetByUser) null else { + val what = if (callee.annotatedInline) "callee" else "callsite" s"the $what is annotated `@inline`" - } else null - Some(requestIfCanInline(callsite, reason)) - } - else None + } + if (callsite.isInlineAnnotated && !callsite.isNoInlineAnnotated) requestIfCanInline(callsite, reason) + else None - case "default" => - if (callee.safeToInline && !callee.annotatedNoInline && !callsite.annotatedNoInline) { - def shouldInlineHO = callee.samParamTypes.nonEmpty && (callee.samParamTypes exists { - case (index, _) => callsite.argInfos.contains(index) - }) - if (callee.annotatedInline || callsite.annotatedInline || shouldInlineHO) { - val reason = if (compilerSettings.YoptLogInline.isSetByUser) { - if (callee.annotatedInline || callsite.annotatedInline) { - val what = if (callee.safeToInline) "callee" else "callsite" - s"the $what is annotated `@inline`" - } else { - val paramNames = Option(callee.callee.parameters).map(_.asScala.map(_.name).toVector) - def param(i: Int) = { - def syn = s"<param $i>" - paramNames.fold(syn)(v => v.applyOrElse(i, (_: Int) => syn)) - } - def samInfo(i: Int, sam: String, arg: String) = s"the argument for parameter (${param(i)}: $sam) is a $arg" - val argInfos = for ((i, sam) <- callee.samParamTypes; info <- callsite.argInfos.get(i)) yield { - val argKind = info match { - case FunctionLiteral => "function literal" - case ForwardedParam(_) => "parameter of the callsite method" - } - samInfo(i, sam.internalName.split('/').last, argKind) + case "default" => + def reason = if (!compilerSettings.YoptLogInline.isSetByUser) null else { + if (callsite.isInlineAnnotated) { + val what = if (callee.annotatedInline) "callee" else "callsite" + s"the $what is annotated `@inline`" + } else { + val paramNames = Option(callee.callee.parameters).map(_.asScala.map(_.name).toVector) + def param(i: Int) = { + def syn = s"<param $i>" + paramNames.fold(syn)(v => v.applyOrElse(i, (_: Int) => syn)) + } + def samInfo(i: Int, sam: String, arg: String) = s"the argument for parameter (${param(i)}: $sam) is a $arg" + val argInfos = for ((i, sam) <- callee.samParamTypes; info <- callsite.argInfos.get(i)) yield { + val argKind = info match { + case FunctionLiteral => "function literal" + case ForwardedParam(_) => "parameter of the callsite method" } - s"the callee is a higher-order method, ${argInfos.mkString(", ")}" + samInfo(i, sam.internalName.split('/').last, argKind) } - } else null - Some(requestIfCanInline(callsite, reason)) + s"the callee is a higher-order method, ${argInfos.mkString(", ")}" + } } + def shouldInlineHO = callee.samParamTypes.nonEmpty && (callee.samParamTypes exists { + case (index, _) => callsite.argInfos.contains(index) + }) + if (!callsite.isNoInlineAnnotated && (callsite.isInlineAnnotated || shouldInlineHO)) requestIfCanInline(callsite, reason) else None - } else None + } } } diff --git a/src/compiler/scala/tools/nsc/classpath/VirtualDirectoryClassPath.scala b/src/compiler/scala/tools/nsc/classpath/VirtualDirectoryClassPath.scala index 8df0c3743d..6fefaf0da0 100644 --- a/src/compiler/scala/tools/nsc/classpath/VirtualDirectoryClassPath.scala +++ b/src/compiler/scala/tools/nsc/classpath/VirtualDirectoryClassPath.scala @@ -1,9 +1,11 @@ package scala.tools.nsc.classpath import scala.tools.nsc.util.ClassRepresentation -import scala.reflect.io.{Path, PlainFile, VirtualDirectory, AbstractFile} +import scala.reflect.io.{AbstractFile, Path, PlainFile, VirtualDirectory} import FileUtils._ import java.net.URL + +import scala.reflect.internal.util.AbstractFileClassLoader import scala.tools.nsc.util.ClassPath case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath with DirectoryLookup[ClassFileEntryImpl] with NoSourcePaths { @@ -11,7 +13,7 @@ case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath wi protected def emptyFiles: Array[AbstractFile] = Array.empty protected def getSubDir(packageDirName: String): Option[AbstractFile] = - Option(dir.lookupName(packageDirName, directory = true)) + Option(AbstractFileClassLoader.lookupPath(dir)(packageDirName.split('/'), directory = true)) protected def listChildren(dir: AbstractFile, filter: Option[AbstractFile => Boolean] = None): Array[F] = filter match { case Some(f) => dir.iterator.filter(f).toArray case _ => dir.toArray @@ -27,10 +29,8 @@ case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath wi override def findClass(className: String): Option[ClassRepresentation] = findClassFile(className) map ClassFileEntryImpl def findClassFile(className: String): Option[AbstractFile] = { - val relativePath = FileUtils.dirPath(className) - val classFile = new PlainFile(Path(s"$dir/$relativePath.class")) - if (classFile.exists) Some(classFile) - else None + val relativePath = FileUtils.dirPath(className) + ".class" + Option(AbstractFileClassLoader.lookupPath(dir)(relativePath split '/', directory = false)) } private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage) diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 25475515aa..92accaf9dd 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -1278,5 +1278,4 @@ abstract class Erasure extends InfoTransform } private class TypeRefAttachment(val tpe: TypeRef) - class TypeParamVarargsAttachment(val typeParamRef: Type) } diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index c171050bbd..84f47c1caa 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -1049,7 +1049,8 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } } debuglog(s"specialized overload $om for ${overriding.name.decode} in ${pp(env)}: ${om.info}") - if (overriding.isAbstractOverride) om.setFlag(ABSOVERRIDE) + om.setFlag(overriding.flags & (ABSOVERRIDE | SYNCHRONIZED)) + om.withAnnotations(overriding.annotations.filter(_.symbol == ScalaStrictFPAttr)) typeEnv(om) = env addConcreteSpecMethod(overriding) if (overriding.isDeferred) { // abstract override diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index d8fa7b58e8..ea3c7da014 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -27,6 +27,8 @@ import scala.reflect.internal.util.ListOfNil * - for every repeated Scala parameter `x: T*' --> x: Seq[T]. * - for every repeated Java parameter `x: T...' --> x: Array[T], except: * if T is an unbounded abstract type, replace --> x: Array[Object] + * - for every method defining repeated parameters annotated with @varargs, generate + * a synthetic Java-style vararg method * - for every argument list that corresponds to a repeated Scala parameter * (a_1, ..., a_n) => (Seq(a_1, ..., a_n)) * - for every argument list that corresponds to a repeated Java parameter @@ -44,6 +46,8 @@ import scala.reflect.internal.util.ListOfNil * def liftedTry$1 = try { x_i } catch { .. } * meth(x_1, .., liftedTry$1(), .. ) * } + * - remove calls to elidable methods and replace their bodies with NOPs when elide-below + * requires it */ /*</export> */ abstract class UnCurry extends InfoTransform @@ -577,7 +581,13 @@ abstract class UnCurry extends InfoTransform case None => literalRhsIfConst } ) - addJavaVarargsForwarders(dd, flatdd) + // Only class members can reasonably be called from Java due to name mangling. + // Additionally, the Uncurry info transformer only adds a forwarder symbol to class members, + // since the other symbols are not part of the ClassInfoType (see reflect.internal.transform.UnCurry) + if (dd.symbol.owner.isClass) + addJavaVarargsForwarders(dd, flatdd) + else + flatdd case tree: Try => if (tree.catches exists (cd => !treeInfo.isCatchCase(cd))) @@ -739,68 +749,32 @@ abstract class UnCurry extends InfoTransform if (!hasRepeated) reporter.error(dd.symbol.pos, "A method without repeated parameters cannot be annotated with the `varargs` annotation.") } - /* Called during post transform, after the method argument lists have been flattened. - * It looks for the method in the `repeatedParams` map, and generates a Java-style + /** + * Called during post transform, after the method argument lists have been flattened. + * It looks for the forwarder symbol in the symbol attachments and generates a Java-style * varargs forwarder. + * + * @note The Java-style varargs method symbol is generated in the Uncurry info transformer. If the + * symbol can't be found this method reports a warning and carries on. + * @see [[scala.reflect.internal.transform.UnCurry]] */ private def addJavaVarargsForwarders(dd: DefDef, flatdd: DefDef): DefDef = { if (!dd.symbol.hasAnnotation(VarargsClass) || !enteringUncurry(mexists(dd.symbol.paramss)(sym => definitions.isRepeatedParamType(sym.tpe)))) return flatdd - val forwSym = currentClass.newMethod(dd.name.toTermName, dd.pos, VARARGS | SYNTHETIC | flatdd.symbol.flags & ~DEFERRED) - - val isRepeated = enteringUncurry(dd.symbol.info.paramss.flatten.map(sym => definitions.isRepeatedParamType(sym.tpe))) - - val oldPs = flatdd.symbol.paramss.head - - // see comment in method toArrayType below - val arrayTypesMappedToObject = mutable.Map.empty[Symbol, Type] - - val forwTpe = { - val (oldTps, tps) = dd.symbol.tpe match { - case PolyType(oldTps, _) => - val newTps = oldTps.map(_.cloneSymbol(forwSym)) - (oldTps, newTps) - - case _ => (Nil, Nil) - } - - def toArrayType(tp: Type, newParam: Symbol): Type = { - val arg = elementType(SeqClass, tp) - val elem = if (arg.typeSymbol.isTypeParameterOrSkolem && !(arg <:< AnyRefTpe)) { - // To prevent generation of an `Object` parameter from `Array[T]` parameter later - // as this would crash the Java compiler which expects an `Object[]` array for varargs - // e.g. def foo[T](a: Int, b: T*) - // becomes def foo[T](a: Int, b: Array[Object]) - // instead of def foo[T](a: Int, b: Array[T]) ===> def foo[T](a: Int, b: Object) - // - // In order for the forwarder method to type check we need to insert a cast: - // def foo'[T'](a: Int, b: Array[Object]) = foo[T'](a, wrapRefArray(b).asInstanceOf[Seq[T']]) - // The target element type for that cast (T') is stored in the `arrayTypesMappedToObject` map. - val originalArg = arg.substSym(oldTps, tps) - arrayTypesMappedToObject(newParam) = originalArg - // Store the type parameter that was replaced by Object to emit the correct generic signature - newParam.updateAttachment(new erasure.TypeParamVarargsAttachment(originalArg)) - ObjectTpe - } else - arg - arrayType(elem) + val forwSym: Symbol = { + currentClass.info // make sure the info is up to date, so the varargs forwarder symbol has been generated + flatdd.symbol.attachments.get[VarargsSymbolAttachment] match { + case Some(VarargsSymbolAttachment(sym)) => sym + case None => + reporter.warning(dd.pos, s"Could not generate Java varargs forwarder for ${flatdd.symbol}. Please file a bug.") + return flatdd } - - val ps = map2(oldPs, isRepeated)((oldParam, isRep) => { - val newParam = oldParam.cloneSymbol(forwSym) - val tp = if (isRep) toArrayType(oldParam.tpe, newParam) else oldParam.tpe - newParam.setInfo(tp) - }) - - val resTp = dd.symbol.tpe_*.finalResultType.substSym(oldPs, ps) - val mt = MethodType(ps, resTp) - val r = if (tps.isEmpty) mt else PolyType(tps, mt) - r.substSym(oldTps, tps) } - forwSym.setInfo(forwTpe) - val newPs = forwTpe.params + val newPs = forwSym.tpe.params + val isRepeated = enteringUncurry(dd.symbol.info.paramss.flatten.map(sym => definitions.isRepeatedParamType(sym.tpe))) + val oldPs = flatdd.symbol.paramss.head val theTyper = typer.atOwner(dd, currentClass) val forwTree = theTyper.typedPos(dd.pos) { @@ -809,8 +783,8 @@ abstract class UnCurry extends InfoTransform else { val parTp = elementType(ArrayClass, param.tpe) val wrap = gen.mkWrapArray(Ident(param), parTp) - arrayTypesMappedToObject.get(param) match { - case Some(tp) => gen.mkCast(wrap, seqType(tp)) + param.attachments.get[TypeParamVarargsAttachment] match { + case Some(TypeParamVarargsAttachment(tp)) => gen.mkCast(wrap, seqType(tp)) case _ => wrap } } @@ -821,13 +795,12 @@ abstract class UnCurry extends InfoTransform } // check if the method with that name and those arguments already exists in the template - currentClass.info.member(forwSym.name).alternatives.find(s => s != forwSym && s.tpe.matches(forwSym.tpe)) match { - case Some(s) => reporter.error(dd.symbol.pos, - "A method with a varargs annotation produces a forwarder method with the same signature " - + s.tpe + " as an existing method.") + enteringUncurry(currentClass.info.member(forwSym.name).alternatives.find(s => s != forwSym && s.tpe.matches(forwSym.tpe))) match { + case Some(s) => + reporter.error(dd.symbol.pos, + s"A method with a varargs annotation produces a forwarder method with the same signature ${s.tpe} as an existing method.") case None => // enter symbol into scope - currentClass.info.decls enter forwSym addNewMember(forwTree) } diff --git a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala index 4ae97ce281..cb3759e5fa 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala @@ -682,7 +682,7 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis { private[TreesAndTypesDomain] def uniqueTpForTree(t: Tree): Type = { def freshExistentialSubtype(tp: Type): Type = { // SI-8611 tp.narrow is tempting, but unsuitable. See `testRefinedTypeSI8611` for an explanation. - NoSymbol.freshExistential("").setInfo(TypeBounds.upper(tp)).tpe + NoSymbol.freshExistential("", 0).setInfo(TypeBounds.upper(tp)).tpe } if (!t.symbol.isStable) { diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index c73ea54c3d..8f0625e58c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -60,7 +60,7 @@ trait Contexts { self: Analyzer => private lazy val allImportInfos = mutable.Map[CompilationUnit, List[ImportInfo]]() withDefaultValue Nil - def warnUnusedImports(unit: CompilationUnit) = { + def warnUnusedImports(unit: CompilationUnit) = if (!unit.isJava) { for (imps <- allImportInfos.remove(unit)) { for (imp <- imps.reverse.distinct) { val used = allUsedSelectors(imp) diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala index 1df3449ce6..cd0c292d90 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala @@ -123,7 +123,7 @@ trait PatternTypers { } private def boundedArrayType(bound: Type): Type = { - val tparam = context.owner freshExistential "" setInfo (TypeBounds upper bound) + val tparam = context.owner.freshExistential("", 0) setInfo (TypeBounds upper bound) newExistentialType(tparam :: Nil, arrayType(tparam.tpe_*)) } diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 715ba0d4f3..669a018f10 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -1189,7 +1189,8 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") case Nil => entered.isEmpty && matchCount > 0 case head :: tail => val enteredAlternatives = Set(entered, entered.capitalize) - head.inits.filter(_.length <= entered.length).exists(init => + val n = (head, entered).zipped.count {case (c, e) => c == e || (c.isUpper && c == e.toUpper)} + head.take(n).inits.exists(init => enteredAlternatives.exists(entered => lenientMatch(entered.stripPrefix(init), tail, matchCount + (if (init.isEmpty) 0 else 1)) ) diff --git a/src/library/scala/collection/mutable/HashMap.scala b/src/library/scala/collection/mutable/HashMap.scala index eab4202353..11ff1f0893 100644 --- a/src/library/scala/collection/mutable/HashMap.scala +++ b/src/library/scala/collection/mutable/HashMap.scala @@ -72,6 +72,37 @@ extends AbstractMap[A, B] else Some(e.value) } + override def getOrElseUpdate(key: A, defaultValue: => B): B = { + val i = index(elemHashCode(key)) + val entry = findEntry(key, i) + if (entry != null) entry.value + else addEntry(createNewEntry(key, defaultValue), i) + } + + /* inlined HashTable.findEntry0 to preserve its visibility */ + private[this] def findEntry(key: A, h: Int): Entry = { + var e = table(h).asInstanceOf[Entry] + while (notFound(key, e)) + e = e.next + e + } + private[this] def notFound(key: A, e: Entry): Boolean = (e != null) && !elemEquals(e.key, key) + + /* inlined HashTable.addEntry0 to preserve its visibility */ + private[this] def addEntry(e: Entry, h: Int): B = { + if (tableSize >= threshold) addEntry(e) + else addEntry0(e, h) + e.value + } + + /* extracted to make addEntry inlinable */ + private[this] def addEntry0(e: Entry, h: Int) { + e.next = table(h).asInstanceOf[Entry] + table(h) = e + tableSize += 1 + nnSizeMapAdd(h) + } + override def put(key: A, value: B): Option[B] = { val e = findOrAddEntry(key, value) if (e eq null) None diff --git a/src/library/scala/concurrent/SyncVar.scala b/src/library/scala/concurrent/SyncVar.scala index 5fabf553bd..0e534a9b22 100644 --- a/src/library/scala/concurrent/SyncVar.scala +++ b/src/library/scala/concurrent/SyncVar.scala @@ -91,7 +91,7 @@ class SyncVar[A] { // [Heather] the reason why: it doesn't take into consideration // whether or not the SyncVar is already defined. So, set has been // deprecated in order to eventually be able to make "setting" private - @deprecated("use `put` instead, as `set` is potentially error-prone", "2.10.0") + @deprecated("use `put` to ensure a value cannot be overwritten without a corresponding `take`", "2.10.0") // NOTE: Used by SBT 0.13.0-M2 and below def set(x: A): Unit = setVal(x) @@ -111,7 +111,7 @@ class SyncVar[A] { // [Heather] the reason why: it doesn't take into consideration // whether or not the SyncVar is already defined. So, unset has been // deprecated in order to eventually be able to make "unsetting" private - @deprecated("use `take` instead, as `unset` is potentially error-prone", "2.10.0") + @deprecated("use `take` to ensure a value is never discarded", "2.10.0") // NOTE: Used by SBT 0.13.0-M2 and below def unset(): Unit = synchronized { isDefined = false diff --git a/src/library/scala/inline.scala b/src/library/scala/inline.scala index f6d7c7569e..f188ccab07 100644 --- a/src/library/scala/inline.scala +++ b/src/library/scala/inline.scala @@ -23,7 +23,7 @@ package scala * def t2 = f2(1) // not inlined * def t3 = f3(1) // may be inlined (heuristics) * def t4 = f1(1): @noinline // not inlined (override at callsite) - * def t5 = f2(1): @inline // not inlined (cannot override the @noinline at f2's definition) + * def t5 = f2(1): @inline // inlined if possible (override at callsite) * def t6 = f3(1): @inline // inlined if possible * def t7 = f3(1): @noinline // not inlined * } diff --git a/src/library/scala/noinline.scala b/src/library/scala/noinline.scala index 0cd5ef9f64..6c21ed667d 100644 --- a/src/library/scala/noinline.scala +++ b/src/library/scala/noinline.scala @@ -23,7 +23,7 @@ package scala * def t2 = f2(1) // not inlined * def t3 = f3(1) // may be inlined (heuristics) * def t4 = f1(1): @noinline // not inlined (override at callsite) - * def t5 = f2(1): @inline // not inlined (cannot override the @noinline at f2's definition) + * def t5 = f2(1): @inline // inlined if possible (override at callsite) * def t6 = f3(1): @inline // inlined if possible * def t7 = f3(1): @noinline // not inlined * } diff --git a/src/library/scala/sys/process/ProcessBuilderImpl.scala b/src/library/scala/sys/process/ProcessBuilderImpl.scala index eef140c16a..0df2e648e0 100644 --- a/src/library/scala/sys/process/ProcessBuilderImpl.scala +++ b/src/library/scala/sys/process/ProcessBuilderImpl.scala @@ -53,12 +53,14 @@ private[process] trait ProcessBuilderImpl { override def run(io: ProcessIO): Process = { val success = new SyncVar[Boolean] - success put false - val t = Spawn({ - runImpl(io) - success.put(true) - }, io.daemonizeThreads) - + def go(): Unit = { + var ok = false + try { + runImpl(io) + ok = true + } finally success.put(ok) + } + val t = Spawn(go(), io.daemonizeThreads) new ThreadProcess(t, success) } } diff --git a/src/library/scala/sys/process/ProcessImpl.scala b/src/library/scala/sys/process/ProcessImpl.scala index 6da0dee056..8a0002b316 100644 --- a/src/library/scala/sys/process/ProcessImpl.scala +++ b/src/library/scala/sys/process/ProcessImpl.scala @@ -86,17 +86,20 @@ private[process] trait ProcessImpl { private[process] abstract class CompoundProcess extends BasicProcess { def isAlive() = processThread.isAlive() def destroy() = destroyer() - def exitValue() = getExitValue._2() getOrElse scala.sys.error("No exit code: process destroyed.") - def start() = getExitValue + def exitValue() = futureValue() getOrElse scala.sys.error("No exit code: process destroyed.") + def start() = { futureThread ;() } - protected lazy val (processThread, getExitValue, destroyer) = { + protected lazy val (processThread, (futureThread, futureValue), destroyer) = { val code = new SyncVar[Option[Int]]() - code.put(None) - val thread = Spawn(code.put(runAndExitValue())) + val thread = Spawn { + var value: Option[Int] = None + try value = runAndExitValue() + finally code.put(value) + } ( thread, - Future { thread.join(); code.get }, + Future(code.get), // thread.join() () => thread.interrupt() ) } @@ -215,13 +218,15 @@ private[process] trait ProcessImpl { } /** A thin wrapper around a java.lang.Process. `ioThreads` are the Threads created to do I/O. - * The implementation of `exitValue` waits until these threads die before returning. */ + * The implementation of `exitValue` waits until these threads die before returning. + */ private[process] class DummyProcess(action: => Int) extends Process { - private[this] val exitCode = Future(action) - override def isAlive() = exitCode._1.isAlive() - override def exitValue() = exitCode._2() + private[this] val (thread, value) = Future(action) + override def isAlive() = thread.isAlive() + override def exitValue() = value() override def destroy() { } } + /** A thin wrapper around a java.lang.Process. `outputThreads` are the Threads created to read from the * output and error streams of the process. `inputThread` is the Thread created to write to the input stream of * the process. @@ -245,11 +250,8 @@ private[process] trait ProcessImpl { } } private[process] final class ThreadProcess(thread: Thread, success: SyncVar[Boolean]) extends Process { - override def isAlive() = thread.isAlive() - override def exitValue() = { - thread.join() - if (success.get) 0 else 1 - } - override def destroy() { thread.interrupt() } + override def isAlive() = thread.isAlive() + override def exitValue() = if (success.get) 0 else 1 // thread.join() + override def destroy() = thread.interrupt() } } diff --git a/src/reflect/scala/reflect/internal/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala index 78f360409d..fd8f51cfb1 100644 --- a/src/reflect/scala/reflect/internal/StdAttachments.scala +++ b/src/reflect/scala/reflect/internal/StdAttachments.scala @@ -78,4 +78,7 @@ trait StdAttachments { case object OuterArgCanBeElided extends PlainAttachment case object UseInvokeSpecial extends PlainAttachment + + /** An attachment carrying information between uncurry and erasure */ + case class TypeParamVarargsAttachment(val typeParamRef: Type) } diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 56b6dc078d..5077169525 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -34,9 +34,13 @@ trait Symbols extends api.Symbols { self: SymbolTable => def recursionTable = _recursionTable def recursionTable_=(value: immutable.Map[Symbol, Int]) = _recursionTable = value + @deprecated("Global existential IDs no longer used", "2.12.1") private var existentialIds = 0 + @deprecated("Global existential IDs no longer used", "2.12.1") protected def nextExistentialId() = { existentialIds += 1; existentialIds } - protected def freshExistentialName(suffix: String) = newTypeName("_" + nextExistentialId() + suffix) + @deprecated("Use overload that accepts an id", "2.12.1") + protected def freshExistentialName(suffix: String): TypeName = freshExistentialName(suffix, nextExistentialId()) + protected def freshExistentialName(suffix: String, id: Int): TypeName = newTypeName("_" + id + suffix) // Set the fields which point companions at one another. Returns the module. def connectModuleToClass(m: ModuleSymbol, moduleClass: ClassSymbol): ModuleSymbol = { @@ -440,8 +444,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => def newGADTSkolem(name: TypeName, origin: Symbol, info: Type): TypeSkolem = newTypeSkolemSymbol(name, origin, origin.pos, origin.flags & ~(EXISTENTIAL | PARAM) | GADT_SKOLEM_FLAGS) setInfo info + @deprecated("Use overload that accepts an id", "2.12.1") final def freshExistential(suffix: String): TypeSymbol = newExistential(freshExistentialName(suffix), pos) + final def freshExistential(suffix: String, id: Int): TypeSymbol = + newExistential(freshExistentialName(suffix, id), pos) /** Type skolems are type parameters ''seen from the inside'' * Assuming a polymorphic method m[T], its type is a PolyType which has a TypeParameter diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index f8679616d1..5e1bf37b80 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -4487,6 +4487,7 @@ trait Types debuglog(s"transposed irregular matrix!? tps=$tps argss=$argss") NoType case Some(argsst) => + var capturedParamIds = 0 val args = map2(sym.typeParams, argsst) { (tparam, as0) => val as = as0.distinct if (as.size == 1) as.head @@ -4508,8 +4509,10 @@ trait Types else { // Martin: I removed this, because incomplete. Not sure there is a good way to fix it. For the moment we // just err on the conservative side, i.e. with a bound that is too high. // if(!(tparam.info.bounds contains tparam)) //@M can't deal with f-bounds, see #2251 + capturedParamIds += 1 + val capturedParamId = capturedParamIds - val qvar = commonOwner(as) freshExistential "" setInfo TypeBounds(g, l) + val qvar = commonOwner(as).freshExistential("", capturedParamId) setInfo TypeBounds(g, l) capturedParams += qvar qvar.tpe } diff --git a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala index ba4f2bec4b..08219c0634 100644 --- a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala +++ b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala @@ -512,6 +512,8 @@ private[internal] trait TypeMaps { && isBaseClassOfEnclosingClass(sym.owner) ) + private var capturedThisIds= 0 + private def nextCapturedThisId() = { capturedThisIds += 1; capturedThisIds } /** Creates an existential representing a type parameter which appears * in the prefix of a ThisType. */ @@ -519,7 +521,7 @@ private[internal] trait TypeMaps { capturedParams find (_.owner == clazz) match { case Some(p) => p.tpe case _ => - val qvar = clazz freshExistential nme.SINGLETON_SUFFIX setInfo singletonBounds(pre) + val qvar = clazz.freshExistential(nme.SINGLETON_SUFFIX, nextCapturedThisId()) setInfo singletonBounds(pre) _capturedParams ::= qvar debuglog(s"Captured This(${clazz.fullNameString}) seen from $seenFromPrefix: ${qvar.defString}") qvar.tpe diff --git a/src/reflect/scala/reflect/internal/transform/UnCurry.scala b/src/reflect/scala/reflect/internal/transform/UnCurry.scala index a50084f40d..3918723b5c 100644 --- a/src/reflect/scala/reflect/internal/transform/UnCurry.scala +++ b/src/reflect/scala/reflect/internal/transform/UnCurry.scala @@ -4,6 +4,7 @@ package internal package transform import Flags._ +import scala.collection.mutable trait UnCurry { @@ -11,6 +12,12 @@ trait UnCurry { import global._ import definitions._ + /** + * The synthetic Java vararg method symbol corresponding to a Scala vararg method + * annotated with @varargs. + */ + case class VarargsSymbolAttachment(varargMethod: Symbol) + /** Note: changing tp.normalize to tp.dealias in this method leads to a single * test failure: run/t5688.scala, where instead of the expected output * Vector(ta, tb, tab) @@ -65,18 +72,74 @@ trait UnCurry { def apply(tp0: Type): Type = { val tp = expandAlias(tp0) tp match { - case ClassInfoType(parents, decls, clazz) => + case ClassInfoType(parents, decls, clazz) if !clazz.isJavaDefined => val parents1 = parents mapConserve uncurry - if (parents1 eq parents) tp - else ClassInfoType(parents1, decls, clazz) // @MAT normalize in decls?? + val varargOverloads = mutable.ListBuffer.empty[Symbol] + + // Not using `hasAnnotation` here because of dreaded cyclic reference errors: + // it may happen that VarargsClass has not been initialized yet and we get here + // while processing one of its superclasses (such as java.lang.Object). Since we + // don't need the more precise `matches` semantics, we only check the symbol, which + // is anyway faster and safer + for (decl <- decls if decl.annotations.exists(_.symbol == VarargsClass)) { + if (mexists(decl.paramss)(sym => definitions.isRepeatedParamType(sym.tpe))) { + varargOverloads += varargForwarderSym(clazz, decl, exitingPhase(phase)(decl.info)) + } + } + if ((parents1 eq parents) && varargOverloads.isEmpty) tp + else { + val newDecls = decls.cloneScope + varargOverloads.foreach(newDecls.enter) + ClassInfoType(parents1, newDecls, clazz) + } // @MAT normalize in decls?? + case PolyType(_, _) => mapOver(tp) + case _ => tp } } } + private def varargForwarderSym(currentClass: Symbol, origSym: Symbol, newInfo: Type): Symbol = { + val forwSym = origSym.cloneSymbol(currentClass, VARARGS | SYNTHETIC | origSym.flags & ~DEFERRED, origSym.name.toTermName).withoutAnnotations + + // we are using `origSym.info`, which contains the type *before* the transformation + // so we still see repeated parameter types (uncurry replaces them with Seq) + val isRepeated = origSym.info.paramss.flatten.map(sym => definitions.isRepeatedParamType(sym.tpe)) + val oldPs = newInfo.paramss.head + def toArrayType(tp: Type, newParam: Symbol): Type = { + val arg = elementType(SeqClass, tp) + val elem = if (arg.typeSymbol.isTypeParameterOrSkolem && !(arg <:< AnyRefTpe)) { + // To prevent generation of an `Object` parameter from `Array[T]` parameter later + // as this would crash the Java compiler which expects an `Object[]` array for varargs + // e.g. def foo[T](a: Int, b: T*) + // becomes def foo[T](a: Int, b: Array[Object]) + // instead of def foo[T](a: Int, b: Array[T]) ===> def foo[T](a: Int, b: Object) + // + // In order for the forwarder method to type check we need to insert a cast: + // def foo'[T'](a: Int, b: Array[Object]) = foo[T'](a, wrapRefArray(b).asInstanceOf[Seq[T']]) + // The target element type for that cast (T') is stored in the TypeParamVarargsAttachment +// val originalArg = arg.substSym(oldTps, tps) + // Store the type parameter that was replaced by Object to emit the correct generic signature + newParam.updateAttachment(new TypeParamVarargsAttachment(arg)) + ObjectTpe + } else + arg + arrayType(elem) + } + + foreach2(forwSym.paramss.flatten, isRepeated)((p, isRep) => + if (isRep) { + p.setInfo(toArrayType(p.info, p)) + } + ) + + origSym.updateAttachment(VarargsSymbolAttachment(forwSym)) + forwSym + } + /** - return symbol's transformed type, * - if symbol is a def parameter with transformed type T, return () => T * diff --git a/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala b/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala index b5030460b8..3cede1b3c5 100644 --- a/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala +++ b/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala @@ -12,6 +12,20 @@ import java.security.cert.Certificate import java.security.{ ProtectionDomain, CodeSource } import java.util.{ Collections => JCollections, Enumeration => JEnumeration } +object AbstractFileClassLoader { + // should be a method on AbstractFile, but adding in `internal.util._` for now as we're in a minor release + private[scala] final def lookupPath(base: AbstractFile)(pathParts: Seq[String], directory: Boolean): AbstractFile = { + var file: AbstractFile = base + for (dirPart <- pathParts.init) { + file = file.lookupName(dirPart, directory = true) + if (file == null) + return null + } + + file.lookupName(pathParts.last, directory = directory) + } +} + /** A class loader that loads files from a [[scala.reflect.io.AbstractFile]]. * * @author Lex Spoon @@ -25,19 +39,7 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) else s"${name.replace('.', '/')}.class" protected def findAbstractFile(name: String): AbstractFile = { - var file: AbstractFile = root - val pathParts = name split '/' - - for (dirPart <- pathParts.init) { - file = file.lookupName(dirPart, directory = true) - if (file == null) - return null - } - - file.lookupName(pathParts.last, directory = false) match { - case null => null - case file => file - } + AbstractFileClassLoader.lookupPath(root)(name split '/', directory = false) } protected def dirNameToPath(name: String): String = diff --git a/src/reflect/scala/reflect/internal/util/SourceFile.scala b/src/reflect/scala/reflect/internal/util/SourceFile.scala index a2642628a4..64b6972298 100644 --- a/src/reflect/scala/reflect/internal/util/SourceFile.scala +++ b/src/reflect/scala/reflect/internal/util/SourceFile.scala @@ -154,18 +154,23 @@ class BatchSourceFile(val file : AbstractFile, content0: Array[Char]) extends So case _ => false } - def calculateLineIndices(cs: Array[Char]) = { - val buf = new ArrayBuffer[Int] - buf += 0 - for (i <- 0 until cs.length) if (isAtEndOfLine(i)) buf += i + 1 - buf += cs.length // sentinel, so that findLine below works smoother - buf.toArray + private lazy val lineIndices: Array[Int] = { + def calculateLineIndices(cs: Array[Char]) = { + val buf = new ArrayBuffer[Int] + buf += 0 + for (i <- 0 until cs.length) if (isAtEndOfLine(i)) buf += i + 1 + buf += cs.length // sentinel, so that findLine below works smoother + buf.toArray + } + calculateLineIndices(content) } - private lazy val lineIndices: Array[Int] = calculateLineIndices(content) - def lineToOffset(index : Int): Int = lineIndices(index) + def lineToOffset(index: Int): Int = { + val offset = lineIndices(index) + if (offset < length) offset else throw new IndexOutOfBoundsException(index.toString) + } - private var lastLine = 0 + private[this] var lastLine = 0 /** Convert offset to line in this source file. * Lines are numbered from 0. diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index b74ccb9177..dbafbfc6ba 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -47,6 +47,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => this.InlineCallsiteAttachment this.OuterArgCanBeElided this.UseInvokeSpecial + this.TypeParamVarargsAttachment this.noPrint this.typeDebug this.Range @@ -458,6 +459,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => definitions.ScalaValueClassesNoUnit definitions.ScalaValueClasses + uncurry.VarargsSymbolAttachment uncurry.DesugaredParameterType erasure.GenericArray erasure.scalaErasure diff --git a/src/reflect/scala/reflect/runtime/SynchronizedSymbols.scala b/src/reflect/scala/reflect/runtime/SynchronizedSymbols.scala index 237afa082b..4e7ddda54e 100644 --- a/src/reflect/scala/reflect/runtime/SynchronizedSymbols.scala +++ b/src/reflect/scala/reflect/runtime/SynchronizedSymbols.scala @@ -10,7 +10,9 @@ private[reflect] trait SynchronizedSymbols extends internal.Symbols { self: Symb private lazy val atomicIds = new java.util.concurrent.atomic.AtomicInteger(0) override protected def nextId() = atomicIds.incrementAndGet() + @deprecated("Global existential IDs no longer used", "2.12.1") private lazy val atomicExistentialIds = new java.util.concurrent.atomic.AtomicInteger(0) + @deprecated("Global existential IDs no longer used", "2.12.1") override protected def nextExistentialId() = atomicExistentialIds.incrementAndGet() private lazy val _recursionTable = mkThreadLocalStorage(immutable.Map.empty[Symbol, Int]) diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala index 65f2c95f73..99acc34811 100644 --- a/src/repl/scala/tools/nsc/interpreter/IMain.scala +++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala @@ -889,7 +889,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends } class ClassBasedWrapper extends Wrapper { - def preambleHeader = "class %s extends _root_.java.io.Serializable { " + def preambleHeader = "sealed class %s extends _root_.java.io.Serializable { " /** Adds an object that instantiates the outer wrapping class. */ def postamble = s""" diff --git a/src/repl/scala/tools/nsc/interpreter/ReplGlobal.scala b/src/repl/scala/tools/nsc/interpreter/ReplGlobal.scala index cf055e0758..0bb9eb6a0b 100644 --- a/src/repl/scala/tools/nsc/interpreter/ReplGlobal.scala +++ b/src/repl/scala/tools/nsc/interpreter/ReplGlobal.scala @@ -6,6 +6,9 @@ package scala.tools.nsc package interpreter +import scala.tools.nsc.backend.JavaPlatform +import scala.tools.nsc.classpath.{AggregateClassPath, ClassPathFactory} +import scala.tools.nsc.util.ClassPath import typechecker.Analyzer /** A layer on top of Global so I can guarantee some extra @@ -31,4 +34,14 @@ trait ReplGlobal extends Global { new util.AbstractFileClassLoader(virtualDirectory, loader) {} } } + + override def optimizerClassPath(base: ClassPath): ClassPath = { + settings.outputDirs.getSingleOutput match { + case None => base + case Some(out) => + // Make bytecode of previous lines available to the inliner + val replOutClasspath = ClassPathFactory.newClassPath(settings.outputDirs.getSingleOutput.get, settings) + AggregateClassPath.createAggregate(platform.classPath, replOutClasspath) + } + } } diff --git a/test/benchmarks/README.md b/test/benchmarks/README.md index 370d610bc4..6c77b83605 100644 --- a/test/benchmarks/README.md +++ b/test/benchmarks/README.md @@ -5,9 +5,7 @@ that makes use of the [SBT plugin](https://github.com/ktoso/sbt-jmh) for [JMH](h ## Running a benchmark -The benchmarks require first building Scala into `../../build/pack` with `ant`. -If you want to build with `sbt dist/mkPack` instead, -you'll need to change `scalaHome` in this project. +The benchmarks require first building Scala into `../../build/pack`. You'll then need to know the fully-qualified name of the benchmark runner class. The benchmarking classes are organized under `src/main/scala`, @@ -18,8 +16,7 @@ Using this example, one would simply run jmh:runMain scala.collection.mutable.OpenHashMapRunner -in SBT. -SBT should be run _from this directory_. +in SBT, run _from this directory_ (`test/benchmarks`). The JMH results can be found under `target/jmh-results/`. `target` gets deleted on an SBT `clean`, diff --git a/test/benchmarks/build.sbt b/test/benchmarks/build.sbt index fb05fb2c99..ef603e18b3 100644 --- a/test/benchmarks/build.sbt +++ b/test/benchmarks/build.sbt @@ -1,5 +1,5 @@ scalaHome := Some(file("../../build/pack")) -scalaVersion := "2.12.0-dev" +scalaVersion := "2.12.1-dev" scalacOptions ++= Seq("-feature", "-opt:l:classpath") lazy val root = (project in file(".")). @@ -7,5 +7,5 @@ lazy val root = (project in file(".")). settings( name := "test-benchmarks", version := "0.0.1", - libraryDependencies += "org.openjdk.jol" % "jol-core" % "0.4" + libraryDependencies += "org.openjdk.jol" % "jol-core" % "0.6" ) diff --git a/test/benchmarks/project/plugins.sbt b/test/benchmarks/project/plugins.sbt index aa49ad9872..1b79ce888c 100644 --- a/test/benchmarks/project/plugins.sbt +++ b/test/benchmarks/project/plugins.sbt @@ -1,2 +1,2 @@ addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0") -addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.16") +addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.17") diff --git a/test/benchmarks/src/main/scala/scala/collection/immutable/VectorMapBenchmark.scala b/test/benchmarks/src/main/scala/scala/collection/immutable/VectorMapBenchmark.scala new file mode 100644 index 0000000000..61e621dcdf --- /dev/null +++ b/test/benchmarks/src/main/scala/scala/collection/immutable/VectorMapBenchmark.scala @@ -0,0 +1,32 @@ +package scala.collection.immutable + +import org.openjdk.jmh.annotations._ +import org.openjdk.jmh.infra._ +import org.openjdk.jmh.runner.IterationType +import benchmark._ +import java.util.concurrent.TimeUnit + +@BenchmarkMode(Array(Mode.AverageTime)) +@Fork(2) +@Threads(1) +@Warmup(iterations = 10) +@Measurement(iterations = 10) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +class VectorMapBenchmark { + @Param(Array("10", "100", "1000")) + var size: Int = _ + + var values: Vector[Any] = _ + + @Setup(Level.Trial) def initKeys(): Unit = { + values = (0 to size).map(i => (i % 4) match { + case 0 => i.toString + case 1 => i.toChar + case 2 => i.toDouble + case 3 => i.toInt + }).toVector + } + + @Benchmark def groupBy = values.groupBy(_.getClass) +} diff --git a/test/benchmarks/src/main/scala/scala/collection/mutable/HashMapBenchmark.scala b/test/benchmarks/src/main/scala/scala/collection/mutable/HashMapBenchmark.scala new file mode 100644 index 0000000000..3f01d154e9 --- /dev/null +++ b/test/benchmarks/src/main/scala/scala/collection/mutable/HashMapBenchmark.scala @@ -0,0 +1,70 @@ +package scala.collection.mutable + +import org.openjdk.jmh.annotations._ +import org.openjdk.jmh.infra._ +import org.openjdk.jmh.runner.IterationType +import benchmark._ +import java.util.concurrent.TimeUnit + +import scala.collection.mutable + +@BenchmarkMode(Array(Mode.AverageTime)) +@Fork(2) +@Threads(1) +@Warmup(iterations = 10) +@Measurement(iterations = 10) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +class HashMapBenchmark { + @Param(Array("10", "100", "1000")) + var size: Int = _ + + var existingKeys: Array[Any] = _ + var missingKeys: Array[Any] = _ + + @Setup(Level.Trial) def initKeys(): Unit = { + existingKeys = (0 to size).map(i => (i % 4) match { + case 0 => i.toString + case 1 => i.toChar + case 2 => i.toDouble + case 3 => i.toInt + }).toArray + missingKeys = (size to 2 * size).toArray + } + + var map = new mutable.HashMap[Any, Any] + + @Setup(Level.Invocation) def initializeMutable = existingKeys.foreach(v => map.put(v, v)) + + @TearDown(Level.Invocation) def tearDown = map.clear() + + @Benchmark def getOrElseUpdate(bh: Blackhole): Unit = { + var i = 0; + while (i < size) { + bh.consume(map.getOrElseUpdate(existingKeys(i), -1)) + bh.consume(map.getOrElseUpdate(missingKeys(i), -1)) + i += 1 + } + } + + @Benchmark def get(bh: Blackhole): Unit = { + var i = 0; + while (i < size) { + bh.consume(map.get(existingKeys(i), -1)) + bh.consume(map.get(missingKeys(i), -1)) + i += 1 + } + } + + @Benchmark def put(bh: Blackhole): Any = { + var map = new mutable.HashMap[Any, Any] + + var i = 0; + while (i < size) { + map.put(existingKeys(i), i) + i += 1 + } + + map + } +} diff --git a/test/files/jvm/varargs-separate-bytecode.check b/test/files/jvm/varargs-separate-bytecode.check new file mode 100644 index 0000000000..1507cd48c5 --- /dev/null +++ b/test/files/jvm/varargs-separate-bytecode.check @@ -0,0 +1 @@ +Found vararg overload for method create
\ No newline at end of file diff --git a/test/files/jvm/varargs-separate-bytecode/AbstractProps_1.scala b/test/files/jvm/varargs-separate-bytecode/AbstractProps_1.scala new file mode 100644 index 0000000000..5dfb8d1a9e --- /dev/null +++ b/test/files/jvm/varargs-separate-bytecode/AbstractProps_1.scala @@ -0,0 +1,8 @@ +package foo + +import scala.annotation.varargs + +trait AbstractProps { + @varargs + def create(x: String, y: Int*): AbstractProps = null +} diff --git a/test/files/jvm/varargs-separate-bytecode/Props_2.scala b/test/files/jvm/varargs-separate-bytecode/Props_2.scala new file mode 100644 index 0000000000..3fc09586fc --- /dev/null +++ b/test/files/jvm/varargs-separate-bytecode/Props_2.scala @@ -0,0 +1,3 @@ +import foo.AbstractProps + +class Props extends AbstractProps
\ No newline at end of file diff --git a/test/files/jvm/varargs-separate-bytecode/Test.scala b/test/files/jvm/varargs-separate-bytecode/Test.scala new file mode 100644 index 0000000000..a666de7f39 --- /dev/null +++ b/test/files/jvm/varargs-separate-bytecode/Test.scala @@ -0,0 +1,15 @@ +import scala.collection.JavaConverters._ +import scala.tools.asm +import scala.tools.asm.Opcodes +import scala.tools.partest.BytecodeTest + +object Test extends BytecodeTest { + def show: Unit = { + val classNode = loadClassNode("Props") + val methods = classNode.methods.iterator().asScala.filter( m => m.name == "create") + + for (m <- methods if (m.access & Opcodes.ACC_VARARGS) > 0) { + println(s"Found vararg overload for method ${m.name}") + } + } +} diff --git a/test/files/neg/sabin2.check b/test/files/neg/sabin2.check index aa0e8f734c..cd6fde4608 100644 --- a/test/files/neg/sabin2.check +++ b/test/files/neg/sabin2.check @@ -1,6 +1,6 @@ sabin2.scala:22: error: type mismatch; found : Test.Base#T - required: _5.T where val _5: Test.Base + required: _1.T where val _1: Test.Base a.set(b.get()) // Error ^ one error found diff --git a/test/files/neg/sealed-final-neg.check b/test/files/neg/sealed-final-neg.check index e135f38f8b..5e47c69ed8 100644 --- a/test/files/neg/sealed-final-neg.check +++ b/test/files/neg/sealed-final-neg.check @@ -1,7 +1,9 @@ -sealed-final-neg.scala:17: warning: neg1/Foo::bar(I)I is annotated @inline but cannot be inlined: the method is not final and may be overridden. +sealed-final-neg.scala:17: warning: neg1/Foo::bar(I)I is annotated @inline but could not be inlined: +The method is not final and may be overridden. def f = Foo.mkFoo() bar 10 ^ -sealed-final-neg.scala:37: warning: neg2/Foo::bar(I)I is annotated @inline but cannot be inlined: the method is not final and may be overridden. +sealed-final-neg.scala:37: warning: neg2/Foo::bar(I)I is annotated @inline but could not be inlined: +The method is not final and may be overridden. def f = Foo.mkFoo() bar 10 ^ error: No warnings can be incurred under -Xfatal-warnings. diff --git a/test/files/neg/t0764.check b/test/files/neg/t0764.check index 830278e715..0c7cff1e1e 100644 --- a/test/files/neg/t0764.check +++ b/test/files/neg/t0764.check @@ -1,5 +1,5 @@ t0764.scala:13: error: type mismatch; - found : Node{type T = _2.type} where val _2: Node{type T = NextType} + found : Node{type T = _1.type} where val _1: Node{type T = NextType} required: Node{type T = Main.this.AType} (which expands to) Node{type T = Node{type T = NextType}} new Main[AType]( (value: AType).prepend ) diff --git a/test/files/neg/t1010.check b/test/files/neg/t1010.check index 2cc8f9d986..d412d8ac1e 100644 --- a/test/files/neg/t1010.check +++ b/test/files/neg/t1010.check @@ -1,6 +1,6 @@ t1010.scala:14: error: type mismatch; found : MailBox#Message - required: _3.in.Message where val _3: Actor + required: _1.in.Message where val _1: Actor unstable.send(msg) // in.Message becomes unstable.Message, but that's ok since Message is a concrete type member ^ one error found diff --git a/test/files/neg/t5120.check b/test/files/neg/t5120.check index 34d4ebde31..b6a3cb96aa 100644 --- a/test/files/neg/t5120.check +++ b/test/files/neg/t5120.check @@ -6,7 +6,7 @@ t5120.scala:11: error: type mismatch; t5120.scala:25: error: type mismatch; found : Thread required: h.T - (which expands to) _2 + (which expands to) _1 List(str, num).foreach(h => h.f1 = new Thread()) ^ two errors found diff --git a/test/files/neg/t6829.check b/test/files/neg/t6829.check index 274094f791..5ccd531be1 100644 --- a/test/files/neg/t6829.check +++ b/test/files/neg/t6829.check @@ -1,6 +1,6 @@ t6829.scala:35: error: type mismatch; found : AgentSimulation.this.state.type (with underlying type G#State) - required: _9.State + required: _1.State lazy val actions: Map[G#Agent,G#Action] = agents.map(a => a -> a.chooseAction(state)).toMap ^ t6829.scala:45: error: trait AgentSimulation takes type parameters @@ -17,32 +17,32 @@ t6829.scala:49: error: not found: value nextState ^ t6829.scala:50: error: type mismatch; found : s.type (with underlying type Any) - required: _30.State where val _30: G + required: _1.State where val _1: G val r = rewards(agent).r(s,a,s2) ^ t6829.scala:50: error: type mismatch; found : a.type (with underlying type Any) - required: _30.Action where val _30: G + required: _1.Action where val _1: G val r = rewards(agent).r(s,a,s2) ^ t6829.scala:50: error: type mismatch; found : s2.type (with underlying type Any) - required: _30.State where val _30: G + required: _1.State where val _1: G val r = rewards(agent).r(s,a,s2) ^ t6829.scala:51: error: type mismatch; found : s.type (with underlying type Any) - required: _25.State + required: _1.State agent.learn(s,a,s2,r): G#Agent ^ t6829.scala:51: error: type mismatch; found : a.type (with underlying type Any) - required: _25.Action + required: _1.Action agent.learn(s,a,s2,r): G#Agent ^ t6829.scala:51: error: type mismatch; found : s2.type (with underlying type Any) - required: _25.State + required: _1.State agent.learn(s,a,s2,r): G#Agent ^ t6829.scala:53: error: not found: value nextState diff --git a/test/files/run/reflection-mem-typecheck.scala b/test/files/run/reflection-mem-typecheck.scala deleted file mode 100644 index 93ec1c937a..0000000000 --- a/test/files/run/reflection-mem-typecheck.scala +++ /dev/null @@ -1,28 +0,0 @@ -import scala.tools.partest.MemoryTest - -trait A { type T <: A } -trait B { type T <: B } - -object Test extends MemoryTest { - lazy val tb = { - import scala.reflect.runtime.universe._ - import scala.reflect.runtime.{currentMirror => cm} - import scala.tools.reflect.ToolBox - cm.mkToolBox() - } - - // I'm not sure this is a great way to test for memory leaks, - // since we're also testing how good the JVM's GC is, and this is not easily reproduced between machines/over time - override def maxDelta = 12 - override def calcsPerIter = 8 - override def calc() { - var snippet = """ - trait A { type T <: A } - trait B { type T <: B } - def foo[T](x: List[T]) = x - foo(List(new A {}, new B {})) - """.trim - snippet = snippet + "\n" + (List.fill(50)(snippet.split("\n").last) mkString "\n") - tb.typecheck(tb.parse(snippet)) - } -}
\ No newline at end of file diff --git a/test/files/run/repl-inline.check b/test/files/run/repl-inline.check new file mode 100644 index 0000000000..db729a67dd --- /dev/null +++ b/test/files/run/repl-inline.check @@ -0,0 +1,11 @@ +warning: there was one deprecation warning (since 2.11.0); re-run with -deprecation for details +callerOfCaller: String +g: String +h: String +g: String +h: String +callerOfCaller: String +g: String +h: String +g: String +h: String diff --git a/test/files/run/repl-inline.scala b/test/files/run/repl-inline.scala new file mode 100644 index 0000000000..260ed28a4f --- /dev/null +++ b/test/files/run/repl-inline.scala @@ -0,0 +1,27 @@ +import scala.tools.nsc._ + +object Test { + val testCode = + """ +def callerOfCaller = Thread.currentThread.getStackTrace.drop(2).head.getMethodName +def g = callerOfCaller +def h = g +assert(h == "g", h) +@inline def g = callerOfCaller +def h = g +assert(h == "h", h) + """ + + def main(args: Array[String]) { + def test(f: Settings => Unit): Unit = { + val settings = new Settings() + settings.processArgumentString("-opt:l:classpath") + f(settings) + settings.usejavacp.value = true + val repl = new interpreter.IMain(settings) + testCode.linesIterator.foreach(repl.interpret(_)) + } + test(_ => ()) + test(_.Yreplclassbased.value = true) + } +} diff --git a/test/files/run/t5125b.check b/test/files/run/t5125b.check index ddbf908f04..29b438a2d6 100644 --- a/test/files/run/t5125b.check +++ b/test/files/run/t5125b.check @@ -5,3 +5,6 @@ public void C2.f(scala.collection.Seq) public void C2$C3.f(java.lang.String[]) public void C2$C3.f(scala.collection.Seq) public void C4.f(scala.collection.Seq) +private void C5.f(int,int[]) +private void C5.f(int,scala.collection.Seq) +public void C5.f(scala.collection.Seq) diff --git a/test/files/run/t5125b.scala b/test/files/run/t5125b.scala index 149c49e213..60ab1d9792 100644 --- a/test/files/run/t5125b.scala +++ b/test/files/run/t5125b.scala @@ -23,6 +23,17 @@ class C4 { } } +class C5 { + def f(values: String*) = println("Calling C5.f(): " + values) + @scala.annotation.varargs + private def f(v: Int, values: Int*) = println("Calling C5.f(): " + values) + + def method(): Unit = { + @scala.annotation.varargs + def f(values: String*) = println("Calling C5.<locally>.f(): " + values) + } +} + object Test extends App { def check(c: Class[_]) { val methodName = "f" @@ -34,4 +45,5 @@ object Test extends App { check(classOf[C2]) check(classOf[C2#C3]) check(classOf[C4]) + check(classOf[C5]) } diff --git a/test/files/run/t7747-repl.check b/test/files/run/t7747-repl.check index 621a70205e..ab37da5722 100644 --- a/test/files/run/t7747-repl.check +++ b/test/files/run/t7747-repl.check @@ -246,12 +246,12 @@ scala> case class Bingo() defined class Bingo scala> List(BippyBups(), PuppyPups(), Bingo()) // show -class $read extends _root_.java.io.Serializable { +sealed class $read extends _root_.java.io.Serializable { def <init>() = { super.<init>; () }; - class $iw extends _root_.java.io.Serializable { + sealed class $iw extends _root_.java.io.Serializable { def <init>() = { super.<init>; () @@ -262,7 +262,7 @@ class $read extends _root_.java.io.Serializable { import $line45.$read.INSTANCE.$iw.$iw.PuppyPups; import $line46.$read.INSTANCE.$iw.$iw.Bingo; import $line46.$read.INSTANCE.$iw.$iw.Bingo; - class $iw extends _root_.java.io.Serializable { + sealed class $iw extends _root_.java.io.Serializable { def <init>() = { super.<init>; () diff --git a/test/files/run/t9814.scala b/test/files/run/t9814.scala new file mode 100644 index 0000000000..3aef3928f6 --- /dev/null +++ b/test/files/run/t9814.scala @@ -0,0 +1,28 @@ +import java.lang.reflect.Modifier + +import scala.annotation.strictfp + +class Foo extends (() => Unit) { + def apply(): Unit = synchronized { + // we're in a specialized subclass + assert(Thread.currentThread.getStackTrace.apply(1).getMethodName == "apply$mcV$sp") + assert(Thread.holdsLock(this)) + } +} + +class Bar extends (() => Unit) { + @strictfp def apply(): Unit = synchronized { + // we're in a specialized subclass + assert(Thread.currentThread.getStackTrace.apply(1).getMethodName == "apply$mcV$sp") + assert(Thread.holdsLock(this)) + } +} + +object Test { + def main(args: Array[String]): Unit = { + new Foo().apply() + + val m = classOf[Bar].getDeclaredMethod("apply$mcV$sp") + assert(Modifier.isStrict(m.getModifiers)) + } +} diff --git a/test/junit/scala/reflect/internal/util/SourceFileTest.scala b/test/junit/scala/reflect/internal/util/SourceFileTest.scala index cad23eba14..2f2029ad2d 100644 --- a/test/junit/scala/reflect/internal/util/SourceFileTest.scala +++ b/test/junit/scala/reflect/internal/util/SourceFileTest.scala @@ -5,6 +5,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import scala.tools.testing.AssertUtil._ + @RunWith(classOf[JUnit4]) class SourceFileTest { def lineContentOf(code: String, offset: Int) = @@ -57,4 +59,21 @@ class SourceFileTest { assertEquals("def", lineContentOf("abc\r\ndef", 8)) assertEquals("def", lineContentOf("abc\r\ndef\r\n", 9)) } + + @Test def si9885_lineToOffset(): Unit = { + val text = "a\nb\nc\n" + val f = new BatchSourceFile("batch", text) + assertThrows[IndexOutOfBoundsException] { + f.lineToOffset(3) + } + assertEquals(4, f.lineToOffset(2)) + + val p = Position.offset(f, text.length - 1) + val q = Position.offset(f, f.lineToOffset(p.line - 1)) + assertEquals(p.line, q.line) + assertEquals(p.column, q.column + 1) + assertThrows[IndexOutOfBoundsException] { + Position.offset(f, f.lineToOffset(p.line)) + } + } } diff --git a/test/junit/scala/sys/process/t7350.scala b/test/junit/scala/sys/process/PipedProcessTest.scala index 9fdcac8ccc..53f053e9aa 100644 --- a/test/junit/scala/sys/process/t7350.scala +++ b/test/junit/scala/sys/process/PipedProcessTest.scala @@ -12,6 +12,7 @@ import scala.concurrent.ExecutionContext.Implicits.global import scala.util.control.Exception.ignoring // Each test normally ends in a moment, but for failure cases, waits until one second. +// SI-7350, SI-8768 @RunWith(classOf[JUnit4]) class PipedProcessTest { diff --git a/test/junit/scala/sys/process/ProcessTest.scala b/test/junit/scala/sys/process/ProcessTest.scala new file mode 100644 index 0000000000..f6d779c2c8 --- /dev/null +++ b/test/junit/scala/sys/process/ProcessTest.scala @@ -0,0 +1,25 @@ +package scala.sys.process + +import java.io.ByteArrayInputStream +// should test from outside the package to ensure implicits work +//import scala.sys.process._ +import scala.util.Properties._ + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import org.junit.Assert.assertEquals + +@RunWith(classOf[JUnit4]) +class ProcessTest { + private def testily(body: => Unit) = if (!isWin) body + @Test def t10007(): Unit = testily { + val res = ("cat" #< new ByteArrayInputStream("lol".getBytes)).!! + assertEquals("lol\n", res) + } + // test non-hanging + @Test def t10055(): Unit = testily { + val res = ("cat" #< ( () => -1 ) ).! + assertEquals(0, res) + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala index 80fbba133e..a74e73afc9 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -72,7 +72,7 @@ class CallGraphTest extends BytecodeTesting { | @noinline def f5 = try { 0 } catch { case _: Throwable => 1 } | @noinline final def f6 = try { 0 } catch { case _: Throwable => 1 } | - | @inline @noinline def f7 = try { 0 } catch { case _: Throwable => 1 } + | @inline @noinline def f7 = try { 0 } catch { case _: Throwable => 1 } // no warning, @noinline takes precedence |} |class D extends C { | @inline override def f1 = try { 0 } catch { case _: Throwable => 1 } @@ -91,18 +91,17 @@ class CallGraphTest extends BytecodeTesting { // The callGraph.callsites map is indexed by instructions of those ClassNodes. val ok = Set( - "D::f1()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // only one warning for D.f1: C.f1 is not annotated @inline - "C::f3()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // only one warning for C.f3: D.f3 does not have @inline (and it would also be safe to inline) - "C::f7()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // two warnings (the error message mentions C.f7 even if the receiver type is D, because f7 is inherited from C) - "operand stack at the callsite in Test::t1(LC;)I contains more values", - "operand stack at the callsite in Test::t2(LD;)I contains more values") + "D::f1()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.", // only one warning for D.f1: C.f1 is not annotated @inline + "C::f3()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.", // only one warning for C.f3: D.f3 does not have @inline (and it would also be safe to inline) + "C::f4()I is annotated @inline but could not be inlined:\nThe operand stack at the callsite in Test::t1(LC;)I contains more values", + "C::f4()I is annotated @inline but could not be inlined:\nThe operand stack at the callsite in Test::t2(LD;)I contains more values") var msgCount = 0 val checkMsg = (m: StoreReporter#Info) => { msgCount += 1 ok exists (m.msg contains _) } val List(cCls, cMod, dCls, testCls) = compile(code, checkMsg) - assert(msgCount == 6, msgCount) + assert(msgCount == 4, msgCount) val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = getAsmMethods(cCls, _.startsWith("f")) val List(df1, df3) = getAsmMethods(dCls, _.startsWith("f")) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala index 95b47f7d04..b1aa27fd27 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala @@ -35,9 +35,9 @@ class InlineWarningTest extends BytecodeTesting { """.stripMargin var count = 0 val warns = Set( - "C::m1()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", - "T::m2()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", - "D::m2()I is annotated @inline but cannot be inlined: the method is not final and may be overridden") + "C::m1()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.", + "T::m2()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.", + "D::m2()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.") compileToBytes(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)}) assert(count == 4, count) } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala index 3e0b889e9c..bf9da0f48f 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala @@ -20,7 +20,7 @@ class InlinerIllegalAccessTest extends BytecodeTesting { import global.genBCode.bTypes._ def addToRepo(cls: List[ClassNode]): Unit = for (c <- cls) byteCodeRepository.add(c, None) - def assertEmpty(ins: Option[AbstractInsnNode]) = for (i <- ins) + def assertEmpty(ins: List[AbstractInsnNode]) = for (i <- ins) throw new AssertionError(textify(i)) @Test @@ -28,7 +28,7 @@ class InlinerIllegalAccessTest extends BytecodeTesting { val code = """package a { | private class C { // the Scala compiler makes all classes public - | def f1 = new C // NEW a/C + | def f1 = new C // NEW a/C, INVOKESPECIAL a/C.<init> ()V | def f2 = new Array[C](0) // ANEWARRAY a/C | def f3 = new Array[Array[C]](0) // ANEWARRAY [La/C; | } @@ -46,9 +46,9 @@ class InlinerIllegalAccessTest extends BytecodeTesting { val methods = cClass.methods.asScala.filter(_.name(0) == 'f').toList - def check(classNode: ClassNode, test: Option[AbstractInsnNode] => Unit) = { + def check(classNode: ClassNode, test: List[AbstractInsnNode] => Unit) = { for (m <- methods) - test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(cClass.name), classBTypeFromParsedClassfile(classNode.name)).map(_._1)) + test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(cClass.name), classBTypeFromParsedClassfile(classNode.name)).right.get) } check(cClass, assertEmpty) @@ -65,7 +65,11 @@ class InlinerIllegalAccessTest extends BytecodeTesting { check(cClass, assertEmpty) check(dClass, assertEmpty) // accessing a private class in the same package is OK check(eClass, { - case Some(ti: TypeInsnNode) if Set("a/C", "[La/C;")(ti.desc) => () + case (ti: TypeInsnNode) :: is if Set("a/C", "[La/C;")(ti.desc) => + is match { + case List(mi: MethodInsnNode) => assert(mi.owner == "a/C" && mi.name == "<init>") + case Nil => + } // MatchError otherwise }) } @@ -141,12 +145,12 @@ class InlinerIllegalAccessTest extends BytecodeTesting { val List(rbD, rcD, rfD, rgD) = dCl.methods.asScala.toList.filter(_.name(0) == 'r').sortBy(_.name) - def check(method: MethodNode, decl: ClassNode, dest: ClassNode, test: Option[AbstractInsnNode] => Unit): Unit = { - test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(decl.name), classBTypeFromParsedClassfile(dest.name)).map(_._1)) + def check(method: MethodNode, decl: ClassNode, dest: ClassNode, test: List[AbstractInsnNode] => Unit): Unit = { + test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(decl.name), classBTypeFromParsedClassfile(dest.name)).right.get) } - val cOrDOwner = (_: Option[AbstractInsnNode] @unchecked) match { - case Some(mi: MethodInsnNode) if Set("a/C", "a/D")(mi.owner) => () + val cOrDOwner = (_: List[AbstractInsnNode] @unchecked) match { + case List(mi: MethodInsnNode) if Set("a/C", "a/D")(mi.owner) => () // MatchError otherwise } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala index 5362585642..9b1609a130 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala @@ -31,7 +31,7 @@ class InlinerSeparateCompilationTest { |} """.stripMargin - val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + val warn = "T::f()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden." val List(c, o, oMod, t) = compileClassesSeparately(List(codeA, codeB), args + " -opt-warnings", _.msg contains warn) assertInvoke(getMethod(c, "t1"), "T", "f") assertNoInvoke(getMethod(c, "t2")) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala index a844c20a7f..90a938be35 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -67,7 +67,7 @@ class InlinerTest extends BytecodeTesting { def canInlineTest(code: String, mod: ClassNode => Unit = _ => ()): Option[OptimizerWarning] = { val cs = gMethAndFCallsite(code, mod)._2 - inliner.earlyCanInlineCheck(cs) orElse inliner.canInlineBody(cs) + inliner.earlyCanInlineCheck(cs) orElse inliner.canInlineCallsite(cs).map(_._1) } def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): MethodNode = { @@ -199,8 +199,8 @@ class InlinerTest extends BytecodeTesting { val List(c, d) = compile(code) val hMeth = getAsmMethod(d, "h") val gCall = getCallsite(hMeth, "g") - val r = inliner.canInlineBody(gCall) - assert(r.nonEmpty && r.get.isInstanceOf[IllegalAccessInstruction], r) + val r = inliner.canInlineCallsite(gCall) + assert(r.nonEmpty && r.get._1.isInstanceOf[IllegalAccessInstruction], r) } @Test @@ -340,7 +340,7 @@ class InlinerTest extends BytecodeTesting { val fMeth = getAsmMethod(c, "f") val call = getCallsite(fMeth, "lowestOneBit") - val warning = inliner.canInlineBody(call) + val warning = inliner.canInlineCallsite(call) assert(warning.isEmpty, warning) inliner.inline(InlineRequest(call, Nil, null)) @@ -475,7 +475,7 @@ class InlinerTest extends BytecodeTesting { | def t2 = this.f |} """.stripMargin - val warn = "::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + val warn = "::f()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden." var count = 0 val List(c, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn}) assert(count == 2, count) @@ -513,7 +513,7 @@ class InlinerTest extends BytecodeTesting { | def t3(t: T) = t.f // no inlining here |} """.stripMargin - val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + val warn = "T::f()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden." var count = 0 val List(c, oMirror, oModule, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn}) assert(count == 1, count) @@ -617,7 +617,7 @@ class InlinerTest extends BytecodeTesting { |} """.stripMargin - val warning = "T1::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + val warning = "T1::f()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden." var count = 0 val List(ca, cb, t1, t2a, t2b) = compile(code, allowMessage = i => {count += 1; i.msg contains warning}) assert(count == 4, count) // see comments, f is not inlined 4 times @@ -698,7 +698,7 @@ class InlinerTest extends BytecodeTesting { | def t1(c: C) = c.foo |} """.stripMargin - val warn = "C::foo()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + val warn = "C::foo()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden." var c = 0 compile(code, allowMessage = i => {c += 1; i.msg contains warn}) assert(c == 1, c) @@ -762,7 +762,7 @@ class InlinerTest extends BytecodeTesting { |} """.stripMargin - val List(c, t, u) = compile(code, allowMessage = _.msg contains "i()I is annotated @inline but cannot be inlined") + val List(c, t, u) = compile(code, allowMessage = _.msg contains "::i()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.") val m1 = getMethod(c, "m1") assertInvoke(m1, "T", "a") assertInvoke(m1, "T", "b") @@ -969,7 +969,7 @@ class InlinerTest extends BytecodeTesting { val gCall = getCallsite(hMeth, "g") val hCall = getCallsite(iMeth, "h") - val warning = inliner.canInlineBody(gCall) + val warning = inliner.canInlineCallsite(gCall) assert(warning.isEmpty, warning) inliner.inline(InlineRequest(hCall, @@ -1053,7 +1053,7 @@ class InlinerTest extends BytecodeTesting { | def t1 = f1(1) // inlined | def t2 = f2(1) // not inlined | def t3 = f1(1): @noinline // not inlined - | def t4 = f2(1): @inline // not inlined (cannot override the def-site @noinline) + | def t4 = f2(1): @inline // inlined | def t5 = f3(1): @inline // inlined | def t6 = f3(1): @noinline // not inlined | @@ -1067,7 +1067,7 @@ class InlinerTest extends BytecodeTesting { assertNoInvoke(getMethod(c, "t1")) assertInvoke(getMethod(c, "t2"), "C", "f2") assertInvoke(getMethod(c, "t3"), "C", "f1") - assertInvoke(getMethod(c, "t4"), "C", "f2") + assertNoInvoke(getMethod(c, "t4")) assertNoInvoke(getMethod(c, "t5")) assertInvoke(getMethod(c, "t6"), "C", "f3") assertNoInvoke(getMethod(c, "t7")) @@ -1469,8 +1469,8 @@ class InlinerTest extends BytecodeTesting { |class C extends T1 with T2 """.stripMargin val List(c, t1, t2) = compile(code, allowMessage = _ => true) - // the forwarder C.f is inlined, so there's no invocation - assertSameSummary(getMethod(c, "f"), List(ICONST_1, IRETURN)) + // we never inline into mixin forwarders, see scala-dev#259 + assertInvoke(getMethod(c, "f"), "T2", "f$") } @Test @@ -1622,4 +1622,120 @@ class InlinerTest extends BytecodeTesting { ("oneLastMethodWithVeryVeryLongNam_yetAnotherMethodWithVeryVeryLong_oneMoreMethodWithVeryVeryLongNam_anotherMethodWithVeryVeryLongNam_param",10), ("oneLastMethodWithVeryVery_yetAnotherMethodWithVeryV_oneMoreMethodWithVeryVery_anotherMethodWithVeryVery_methodWithVeryVeryLongNam_param",11))) } + + @Test + def sd259(): Unit = { + // - trait methods are not inlined into their static super accessors, and also not into mixin forwarders. + // - inlining an invocation of a mixin forwarder also inlines the static accessor and the trait method body. + val code = + """trait T { + | def m1a = 1 + | final def m1b = 1 + | + | @inline def m2a = 2 + | @inline final def m2b = 2 + | + | def m3a(f: Int => Int) = f(1) + | final def m3b(f: Int => Int) = f(1) + |} + |final class A extends T + |class C { + | def t1(t: T) = t.m1a + | def t2(t: T) = t.m1b + | def t3(t: T) = t.m2a + | def t4(t: T) = t.m2b + | def t5(t: T) = t.m3a(x => x) + | def t6(t: T) = t.m3b(x => x) + | + | def t7(a: A) = a.m1a + | def t8(a: A) = a.m1b + | def t9(a: A) = a.m2a + | def t10(a: A) = a.m2b + | def t11(a: A) = a.m3a(x => x) + | def t12(a: A) = a.m3b(x => x) + |} + """.stripMargin + val warn = "T::m2a()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden." + var count = 0 + val List(a, c, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn}) + assert(count == 1) + + assertInvoke(getMethod(t, "m1a$"), "T", "m1a") + assertInvoke(getMethod(t, "m1b$"), "T", "m1b") + assertInvoke(getMethod(t, "m2a$"), "T", "m2a") + assertInvoke(getMethod(t, "m2b$"), "T", "m2b") + assertInvoke(getMethod(t, "m3a$"), "T", "m3a") + assertInvoke(getMethod(t, "m3b$"), "T", "m3b") + + assertInvoke(getMethod(a, "m1a"), "T", "m1a$") + assertInvoke(getMethod(a, "m1b"), "T", "m1b$") + assertInvoke(getMethod(a, "m2a"), "T", "m2a$") + assertInvoke(getMethod(a, "m2b"), "T", "m2b$") + assertInvoke(getMethod(a, "m3a"), "T", "m3a$") + assertInvoke(getMethod(a, "m3b"), "T", "m3b$") + + assertInvoke(getMethod(c, "t1"), "T", "m1a") + assertInvoke(getMethod(c, "t2"), "T", "m1b") + + assertInvoke(getMethod(c, "t3"), "T", "m2a") // could not inline + assertNoInvoke(getMethod(c, "t4")) + + assertInvoke(getMethod(c, "t5"), "T", "m3a") // could not inline + assertInvoke(getMethod(c, "t6"), "C", "$anonfun$t6$1") // both forwarders inlined, closure eliminated + + assertInvoke(getMethod(c, "t7"), "A", "m1a") + assertInvoke(getMethod(c, "t8"), "A", "m1b") + + assertNoInvoke(getMethod(c, "t9")) + assertNoInvoke(getMethod(c, "t10")) + + assertInvoke(getMethod(c, "t11"), "C", "$anonfun$t11$1") // both forwarders inlined, closure eliminated + assertInvoke(getMethod(c, "t12"), "C", "$anonfun$t12$1") // both forwarders inlined, closure eliminated + } + + @Test + def sd259b(): Unit = { + val code = + """trait T { + | def get = 1 + | @inline final def m = try { get } catch { case _: Throwable => 1 } + |} + |class A extends T + |class C { + | def t(a: A) = 1 + a.m // cannot inline a try block onto a non-empty stack + |} + """.stripMargin + val warn = + """T::m()I is annotated @inline but could not be inlined: + |The operand stack at the callsite in C::t(LA;)I contains more values than the + |arguments expected by the callee T::m()I. These values would be discarded + |when entering an exception handler declared in the inlined method.""".stripMargin + val List(a, c, t) = compile(code, allowMessage = _.msg contains warn) + + // inlinig of m$ is rolled back, because <invokespecial T.m> is not legal in class C. + assertInvoke(getMethod(c, "t"), "T", "m$") + } + + @Test + def sd259c(): Unit = { + val code = + """trait T { + | def bar = 1 + | @inline final def m = { + | def impl = bar // private, non-static method + | impl + | } + |} + |class A extends T + |class C { + | def t(a: A) = a.m + |} + """.stripMargin + val warn = + """T::m()I is annotated @inline but could not be inlined: + |The callee T::m()I contains the instruction INVOKESPECIAL T.impl$1 ()I + |that would cause an IllegalAccessError when inlined into class C.""".stripMargin + val List(a, c, t) = compile(code, allowMessage = _.msg contains warn) + assertInvoke(getMethod(c, "t"), "T", "m$") + } } diff --git a/test/junit/scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala b/test/junit/scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala new file mode 100644 index 0000000000..234f575b79 --- /dev/null +++ b/test/junit/scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2014 Contributor. All rights reserved. + */ +package scala.tools.nsc.classpath + +import org.junit.Assert._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +import scala.reflect.io.VirtualDirectory + + +@RunWith(classOf[JUnit4]) +class VirtualDirectoryClassPathTest { + + @Test + def virtualDirectoryClassPath_findClassFile(): Unit = { + val base = new VirtualDirectory("base", None) + val p1 = base subdirectoryNamed "p1" + val p1_Test_class = p1.fileNamed("Test.class") + val p2 = base subdirectoryNamed "p2" + val p3 = p2 subdirectoryNamed "p3" + val p4 = p3 subdirectoryNamed "p4" + val p4_Test1_class = p4.fileNamed("Test.class") + val classPath = VirtualDirectoryClassPath(base) + + assertEquals(Some(p1_Test_class), classPath.findClassFile("p1/Test")) + + assertEquals(None, classPath.findClassFile("p1/DoesNotExist")) + assertEquals(None, classPath.findClassFile("DoesNotExist")) + assertEquals(None, classPath.findClassFile("p2")) + assertEquals(None, classPath.findClassFile("p2/DoesNotExist")) + assertEquals(None, classPath.findClassFile("p4/DoesNotExist")) + + assertEquals(List("p1", "p2"), classPath.packages("").toList.map(_.name).sorted) + assertEquals(List(), classPath.packages("p1").toList.map(_.name).sorted) + assertEquals(List("p2.p3"), classPath.packages("p2").toList.map(_.name).sorted) + assertEquals(List("p2.p3.p4"), classPath.packages("p2.p3").toList.map(_.name).sorted) + } +} diff --git a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala index 78ebb7cf9c..a216b319a8 100644 --- a/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala +++ b/test/junit/scala/tools/nsc/interpreter/CompletionTest.scala @@ -1,10 +1,11 @@ package scala.tools.nsc.interpreter -import java.io.{StringWriter, PrintWriter} +import java.io.{PrintWriter, StringWriter} import org.junit.Assert.assertEquals import org.junit.Test +import scala.reflect.internal.util.BatchSourceFile import scala.tools.nsc.Settings class CompletionTest { @@ -174,6 +175,24 @@ class CompletionTest { checkExact(completer, "case class D(a: Int, b: Int) { this.a")("a", "asInstanceOf") } + @Test + def replGeneratedCodeDeepPackages(): Unit = { + val intp = newIMain() + val completer = new PresentationCompilerCompleter(intp) + intp.compileSources(new BatchSourceFile("<paste>", "package p1.p2.p3; object Ping { object Pong }")) + checkExact(completer, "p1.p2.p")("p3") + checkExact(completer, "p1.p2.p3.P")("Ping") + checkExact(completer, "p1.p2.p3.Ping.Po")("Pong") + } + + @Test + def performanceOfLenientMatch(): Unit = { + val intp = newIMain() + val completer = new PresentationCompilerCompleter(intp) + val ident: String = "thisIsAReallyLongMethodNameWithManyManyManyManyChunks" + checkExact(completer, s"($ident: Int) => tia")(ident) + } + def checkExact(completer: PresentationCompilerCompleter, before: String, after: String = "")(expected: String*): Unit = { assertEquals(expected.toSet, completer.complete(before, after).candidates.toSet) } |