Skip to content

Capture contextual typer timings suitable for flamegraphs #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 42 additions & 7 deletions src/compiler/scala/tools/nsc/Global.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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("<macro>;")
writer.write(macroo.fullName)
}
if (implicitPt != NoSymbol) {
writer.write(";")
writer.write("<implicit>;")
writer.write(implicitPt.fullName)
}
writer.write(" ")
writer.write(String.valueOf(stats.duration))
writer.write("\n")
}
} finally {
writer.close()
}
}
}



advancePhase()
}
Expand Down Expand Up @@ -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) = {
Expand All @@ -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))
Expand Down
9 changes: 7 additions & 2 deletions src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/scala/tools/nsc/typechecker/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion src/compiler/scala/tools/nsc/typechecker/Macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
18 changes: 8 additions & 10 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
1 change: 0 additions & 1 deletion src/library/scala/collection/immutable/StringOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
7 changes: 7 additions & 0 deletions src/reflect/scala/reflect/internal/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
*/
Expand Down
2 changes: 2 additions & 0 deletions src/reflect/scala/reflect/internal/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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])
}
44 changes: 43 additions & 1 deletion src/reflect/scala/reflect/internal/util/Statistics.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,56 @@ 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 = {
enabled = currentSettings.YstatisticsEnabled
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 */
Expand Down