diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 2638bd54ba65..f716d21dc65f 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -10,12 +10,13 @@ package nsc import java.io.{File, FileNotFoundException, IOException} import java.net.URL import java.nio.charset.{Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException} + import scala.collection.{immutable, mutable} import io.{AbstractFile, Path, SourceReader} import reporters.Reporter import util.{ClassPath, returning} import scala.reflect.ClassTag -import scala.reflect.internal.util.{BatchSourceFile, NoSourceFile, ScalaClassLoader, ScriptSourceFile, SourceFile, StatisticsStatics} +import scala.reflect.internal.util.{BatchSourceFile, NoSourceFile, ScalaClassLoader, ScriptSourceFile, SourceFile, Statistics, StatisticsStatics} import scala.reflect.internal.pickling.PickleBuffer import symtab.{Flags, SymbolTable, SymbolTrackers} import symtab.classfile.Pickler @@ -26,9 +27,11 @@ import typechecker._ import transform.patmat.PatternMatching import transform._ import backend.{JavaPlatform, ScalaPrimitives} -import backend.jvm.{GenBCode, BackendStats} +import backend.jvm.{BackendStats, GenBCode} +import scala.collection.mutable.ArrayBuffer import scala.concurrent.Future import scala.language.postfixOps +import scala.reflect.io.Path import scala.tools.nsc.ast.{TreeGen => AstTreeGen} import scala.tools.nsc.classpath._ import scala.tools.nsc.profile.Profiler @@ -1477,8 +1480,41 @@ class Global(var currentSettings: Settings, reporter0: Reporter) runCheckers() // output collected statistics - if (settings.YstatisticsEnabled && settings.Ystatistics.contains(phase.name)) + if (settings.YstatisticsEnabled && settings.Ystatistics.contains(phase.name)) { printStatisticsFor(phase) + if (phase == typerPhase) { + import java.nio.file._ + val writer = Files.newBufferedWriter(java.nio.file.Paths.get("/tmp/output.csv")) + try { + for ((key @ statistics.StatsKey(sym, macroo, implicitPt), stats) <- statistics.statsMap if sym != NoSymbol) { + val ownerChain = sym.ownerChain.toArray + var i = ownerChain.length - 1 + while (i >= 0) { + writer.write(ownerChain(i).name.toString) + if (i != 0) writer.write(";") + i -= 1 + } + if (macroo != NoSymbol) { + writer.write(";") + writer.write(";") + writer.write(macroo.fullName) + } + if (implicitPt != NoSymbol) { + writer.write(";") + writer.write(";") + writer.write(implicitPt.fullName) + } + writer.write(" ") + writer.write(String.valueOf(stats.duration)) + writer.write("\n") + } + } finally { + writer.close() + } + } + } + + advancePhase() } @@ -1580,9 +1616,8 @@ class Global(var currentSettings: Settings, reporter0: Reporter) private val hotCounters = List(statistics.retainedCount, statistics.retainedByType, statistics.nodeByType) private val parserStats = { - import statistics.treeNodeCount - if (settings.YhotStatisticsEnabled) treeNodeCount :: hotCounters - else List(treeNodeCount) + if (settings.YhotStatisticsEnabled) statistics.treeNodeCount :: hotCounters + else List(statistics.treeNodeCount) } final def printStatisticsFor(phase: Phase) = { @@ -1599,7 +1634,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter) } } - val quants: Iterable[statistics.Quantity] = + val quants: Iterable[Statistics#Quantity] = if (phase.name == "parser") parserStats else if (settings.YhotStatisticsEnabled) statistics.allQuantities else statistics.allQuantities.filterNot(q => hotCounters.contains(q)) diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala index 85ea78c912a7..9e8a3fb759a3 100644 --- a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala @@ -213,7 +213,12 @@ abstract class SymbolLoaders { }) } - override def complete(root: Symbol) { + override def complete(root: Symbol): Unit = { + statistics.withContext(true, root, NoSymbol, NoSymbol) { + completeInternal(root) + } + } + private def completeInternal(root: Symbol) { try { val start = java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) val currentphase = phase @@ -225,7 +230,7 @@ abstract class SymbolLoaders { setSource(root.companionSymbol) // module -> class, class -> module } catch { - case ex @ (_: IOException | _: MissingRequirementError) => + case ex@(_: IOException | _: MissingRequirementError) => ok = false signalError(root, ex) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 9d2196a56720..766363b9760f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -80,6 +80,11 @@ trait Implicits { * @return A search result */ def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean, pos: Position): SearchResult = { + statistics.withContext(hasLength(context.openImplicits, 0) && hasLength(openMacros, 0), context.owner, NoSymbol, pt.typeSymbol) { + inferImplicit1(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent, pos) + } + } + private def inferImplicit1(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean, pos: Position): SearchResult = { // Note that the isInvalidConversionTarget seems to make a lot more sense right here, before all the // work is performed, than at the point where it presently exists. val shouldPrint = printTypings && !context.undetparams.isEmpty diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index faadf07235eb..e1ce2bbbc549 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -754,7 +754,12 @@ trait Macros extends MacroRuntimes with Traces with Helpers { /** Expands a term macro used in apply role as `M(2)(3)` in `val x = M(2)(3)`. * @see DefMacroExpander */ - def macroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = pluginsMacroExpand(typer, expandee, mode, pt) + def macroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = { + val addContext = hasLength(openMacros, 0) && hasLength(typer.context.openImplicits, 0) + statistics.withContext(addContext, typer.context.owner, expandee.symbol, NoSymbol){ + pluginsMacroExpand(typer, expandee, mode, pt) + } + } /** Default implementation of `macroExpand`. * Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsMacroExpand for more details) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index ee128b2fc3fb..0578bf49a35f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1820,7 +1820,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } - def typedClassDef(cdef: ClassDef): Tree = { + def typedClassDef(cdef: ClassDef): Tree = statistics.withContext(true, cdef.symbol, NoSymbol, NoSymbol) { val clazz = cdef.symbol val typedMods = typedModifiers(cdef.mods) assert(clazz != NoSymbol, cdef) @@ -1856,7 +1856,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper .setType(NoType) } - def typedModuleDef(mdef: ModuleDef): Tree = { + def typedModuleDef(mdef: ModuleDef): Tree = statistics.withContext(true, mdef.symbol, NoSymbol, NoSymbol) { // initialize all constructors of the linked class: the type completer (Namer.methodSig) // might add default getters to this object. example: "object T; class T(x: Int = 1)" val linkedClass = companionSymbolOf(mdef.symbol, context) @@ -2032,7 +2032,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def typedModifiers(mods: Modifiers): Modifiers = mods.copy(annotations = Nil) setPositions mods.positions - def typedValDef(vdef: ValDef): ValDef = { + def typedValDef(vdef: ValDef): ValDef = statistics.withContext(true, vdef.symbol, NoSymbol, NoSymbol) { val sym = vdef.symbol val valDefTyper = { val maybeConstrCtx = @@ -2258,9 +2258,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper failStruct(ddef.tpt.pos, "a user-defined value class", where = "Result type") } - def typedDefDef(ddef: DefDef): DefDef = { - // an accessor's type completer may mutate a type inside `ddef` (`== context.unit.synthetics(ddef.symbol)`) - // concretely: it sets the setter's parameter type or the getter's return type (when derived from a valdef with empty tpt) + def typedDefDef(ddef: DefDef): DefDef = statistics.withContext(true, ddef.symbol, NoSymbol, NoSymbol) { val meth = ddef.symbol.initialize reenterTypeParams(ddef.tparams) @@ -2293,9 +2291,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper var rhs1 = if (ddef.name == nme.CONSTRUCTOR && !ddef.symbol.hasStaticFlag) { // need this to make it possible to generate static ctors if (!meth.isPrimaryConstructor && - (!meth.owner.isClass || - meth.owner.isModuleClass || - meth.owner.isAnonOrRefinementClass)) + (!meth.owner.isClass || + meth.owner.isModuleClass || + meth.owner.isAnonOrRefinementClass)) InvalidConstructorDefError(ddef) typed(ddef.rhs) } else if (meth.isMacro) { @@ -2320,7 +2318,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper rhs1 = checkDead(rhs1) if (!isPastTyper && meth.owner.isClass && - meth.paramss.exists(ps => ps.exists(_.hasDefault) && isRepeatedParamType(ps.last.tpe))) + meth.paramss.exists(ps => ps.exists(_.hasDefault) && isRepeatedParamType(ps.last.tpe))) StarWithDefaultError(meth) if (!isPastTyper) { diff --git a/src/library/scala/collection/immutable/StringOps.scala b/src/library/scala/collection/immutable/StringOps.scala index 77333badf97b..cffaaf49dab3 100644 --- a/src/library/scala/collection/immutable/StringOps.scala +++ b/src/library/scala/collection/immutable/StringOps.scala @@ -27,7 +27,6 @@ import mutable.StringBuilder * @define coll string */ final class StringOps(override val repr: String) extends AnyVal with StringLike[String] { - override protected[this] def thisCollection: WrappedString = new WrappedString(repr) override protected[this] def toCollection(repr: String): WrappedString = new WrappedString(repr) diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index c892db898724..713106a60f58 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -1236,6 +1236,13 @@ trait Symbols extends api.Symbols { self: SymbolTable => def next = { val r = current; current = current.owner; r } } + @tailrec final def hasTermOwner: Boolean = this != NoSymbol && { + val owner = this.owner + if (owner.hasPackageFlag) false + else if (owner.isTerm) true + else owner.hasTermOwner + } + /** Same as `ownerChain contains sym` but more efficient, and * with a twist for refinement classes (see RefinementClassSymbol.) */ diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index bec839b85606..cae16c37abf1 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -4839,4 +4839,6 @@ trait TypesStats { val typerefBaseTypeSeqCount = newSubCounter(" of which for typerefs", baseTypeSeqCount) val singletonBaseTypeSeqCount = newSubCounter(" of which for singletons", baseTypeSeqCount) val typeOpsStack = newTimerStack() + val completionStack = newTimerStack() + class PerSymbolStats(stats: List[Quantity]) } diff --git a/src/reflect/scala/reflect/internal/util/Statistics.scala b/src/reflect/scala/reflect/internal/util/Statistics.scala index e4a3f6f64ffb..ecb5819bd7a8 100644 --- a/src/reflect/scala/reflect/internal/util/Statistics.scala +++ b/src/reflect/scala/reflect/internal/util/Statistics.scala @@ -9,7 +9,6 @@ import java.util.concurrent.atomic.{AtomicInteger, AtomicLong} import scala.runtime.LongRef abstract class Statistics(val symbolTable: SymbolTable, settings: MutableSettings) { - initFromSettings(settings) def initFromSettings(currentSettings: MutableSettings): Unit = { @@ -17,6 +16,49 @@ abstract class Statistics(val symbolTable: SymbolTable, settings: MutableSetting hotEnabled = currentSettings.YhotStatisticsEnabled } + type Sym = SymbolTable#Symbol + case class StatsKey(owner: Sym, macroo: Sym, implicitPt: Sym) + + final class StackedStats { + private var startNanos = System.nanoTime() + var duration: Long = 0 + var running = true + def stop(): Unit = { + assert(running) + duration += System.nanoTime() - startNanos + running = false + } + def start(): Unit = { + startNanos = System.nanoTime() + running = true + } + } + + private val TopKey = StatsKey(symbolTable.NoSymbol, symbolTable.NoSymbol, symbolTable.NoSymbol) + private var statsStack = mutable.ArrayStack[(StatsKey, StackedStats)]((TopKey, new StackedStats)) + val statsMap = mutable.AnyRefMap[StatsKey, StackedStats](statsStack.head) + private def currentStats: StackedStats = statsStack.top._2 + + def start(owner: Sym, macroo: Sym, implicitPt: Sym): Unit = { + currentStats.stop() + val key = StatsKey(owner, macroo, implicitPt) + val stats = statsMap.getOrNull(key) match { + case null => val x = new StackedStats; statsMap(key) = x; x + case existing => existing.start(); existing + } + statsStack.push((key, stats)) + } + def end(): Unit = { + statsStack.pop()._2.stop() + currentStats.start() + } + @inline final def withContext[T](condition: Boolean, owner: Sym, macroo: Sym, implicitPt: Sym)(f: => T): T = { + def implicitOrMacroOnStack = statsStack.exists(x => x._1.implicitPt != symbolTable.NoSymbol || x._1.macroo != symbolTable.NoSymbol) + val shouldStart = StatisticsStatics.areSomeColdStatsEnabled() && enabled && condition && !implicitOrMacroOnStack + if (shouldStart) start(owner, macroo, implicitPt) + try { f } finally {if (shouldStart) end()} + } + type TimerSnapshot = (Long, Long) /** If enabled, increment counter by one */