Skip to content

Commit c17a164

Browse files
committed
Add option to print info about top complex methods
Rename options to -V group Add -Vprofile-details option to print info about most complex methods.
1 parent 98bafba commit c17a164

File tree

6 files changed

+99
-34
lines changed

6 files changed

+99
-34
lines changed

compiler/src/dotty/tools/dotc/Run.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,10 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
206206
compiling = true
207207

208208
profile =
209-
if ctx.settings.Yprofile.value || !ctx.settings.YprofileSortedBy.value.isEmpty
210-
then ActiveProfile()
209+
if ctx.settings.Vprofile.value
210+
|| !ctx.settings.VprofileSortedBy.value.isEmpty
211+
|| ctx.settings.VprofileDetails.value != 0
212+
then ActiveProfile(ctx.settings.VprofileDetails.value.max(0).min(1000))
211213
else NoProfile
212214

213215
// If testing pickler, make sure to stop after pickling phase:

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ private sealed trait VerboseSettings:
146146
val Xprint: Setting[List[String]] = PhasesSetting("-Vprint", "Print out program after", aliases = List("-Xprint"))
147147
val XshowPhases: Setting[Boolean] = BooleanSetting("-Vphases", "List compiler phases.", aliases = List("-Xshow-phases"))
148148

149+
val Vprofile: Setting[Boolean] = BooleanSetting("-Vprofile", "Show information about sizes and compiletime complexity.")
150+
val VprofileSortedBy = ChoiceSetting("-Vprofile-sorted-by", "key", "Show information about sizes and compiletime complexity sorted by given column name", List("name", "path", "lines", "tokens", "tasty", "complexity"), "")
151+
val VprofileDetails = IntSetting("-Vprofile-details", "List tasty sizes of a given number of most complex methods", 0)
152+
149153
/** -W "Warnings" settings
150154
*/
151155
private sealed trait WarningSettings:
@@ -333,8 +337,5 @@ private sealed trait YSettings:
333337
val YinstrumentDefs: Setting[Boolean] = BooleanSetting("-Yinstrument-defs", "Add instrumentation code that counts method calls; needs -Yinstrument to be set, too.")
334338

335339
val YforceInlineWhileTyping: Setting[Boolean] = BooleanSetting("-Yforce-inline-while-typing", "Make non-transparent inline methods inline when typing. Emulates the old inlining behavior of 3.0.0-M3.")
336-
337-
val Yprofile: Setting[Boolean] = BooleanSetting("-Yprofile", "Show information about sizes and compiletime complexity.")
338-
val YprofileSortedBy = ChoiceSetting("-Yprofile-sorted-by", "key", "Show information about sizes and compiletime complexity sorted by given column name", List("name", "path", "lines", "tokens", "tasty", "complexity"), "")
339340
end YSettings
340341

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import StdNames.nme
1717
import transform.SymUtils._
1818
import config.Config
1919
import collection.mutable
20+
import reporting.{Profile, NoProfile}
2021
import dotty.tools.tasty.TastyFormat.ASTsSection
2122

2223

@@ -43,6 +44,8 @@ class TreePickler(pickler: TastyPickler) {
4344
*/
4445
private val docStrings = util.EqHashMap[untpd.MemberDef, Comment]()
4546

47+
private var profile: Profile = NoProfile
48+
4649
def treeAnnots(tree: untpd.MemberDef): List[Tree] =
4750
val ts = annotTrees.lookup(tree)
4851
if ts == null then Nil else ts.toList
@@ -324,6 +327,7 @@ class TreePickler(pickler: TastyPickler) {
324327
assert(symRefs(sym) == NoAddr, sym)
325328
registerDef(sym)
326329
writeByte(tag)
330+
val addr = currentAddr
327331
withLength {
328332
pickleName(sym.name)
329333
pickleParams
@@ -334,6 +338,8 @@ class TreePickler(pickler: TastyPickler) {
334338
pickleTreeUnlessEmpty(rhs)
335339
pickleModifiers(sym, mdef)
336340
}
341+
if sym.is(Method) && sym.owner.isClass then
342+
profile.recordMethodSize(sym, currentAddr.index - addr.index, mdef.span)
337343
for
338344
docCtx <- ctx.docCtx
339345
comment <- docCtx.docstring(sym)
@@ -769,6 +775,7 @@ class TreePickler(pickler: TastyPickler) {
769775
// ---- main entry points ---------------------------------------
770776

771777
def pickle(trees: List[Tree])(using Context): Unit = {
778+
profile = Profile.current
772779
trees.foreach(tree => if (!tree.isEmpty) pickleTree(tree))
773780
def missing = forwardSymRefs.keysIterator
774781
.map(sym => i"${sym.showLocated} (line ${sym.srcPos.line}) #${sym.id}")

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ object Parsers {
172172

173173
class Parser(source: SourceFile)(using Context) extends ParserCommon(source) {
174174

175-
val in: Scanner = new Scanner(source)
175+
val in: Scanner = new Scanner(source, profile = Profile.current)
176176
// in.debugTokenStream = true // uncomment to see the token stream of the standard scanner, but not syntax highlighting
177177

178178
/** This is the general parse entry point.

compiler/src/dotty/tools/dotc/parsing/Scanners.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ object Scanners {
162162
errorButContinue("trailing separator is not allowed", offset + litBuf.length - 1)
163163
}
164164

165-
class Scanner(source: SourceFile, override val startFrom: Offset = 0)(using Context) extends ScannerCommon(source) {
165+
class Scanner(source: SourceFile, override val startFrom: Offset = 0, profile: Profile = NoProfile)(using Context) extends ScannerCommon(source) {
166166
val keepComments = !ctx.settings.YdropComments.value
167167

168168
/** A switch whether operators at the start of lines can be infix operators */
@@ -190,8 +190,6 @@ object Scanners {
190190
case _ => true
191191
}
192192

193-
private val profile = if this.isInstanceOf[LookaheadScanner] then NoProfile else Profile.current
194-
195193
if (rewrite) {
196194
val s = ctx.settings
197195
val rewriteTargets = List(s.newSyntax, s.oldSyntax, s.indent, s.noindent)

compiler/src/dotty/tools/dotc/reporting/Profile.scala

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,87 +4,143 @@ package reporting
44

55
import core.*
66
import Contexts.{Context, ctx}
7+
import Symbols.{Symbol, NoSymbol}
78
import collection.mutable
89
import util.{EqHashMap, NoSourcePosition}
10+
import util.Spans.{Span, NoSpan}
11+
import Decorators.i
12+
import parsing.Scanners.Scanner
13+
import io.AbstractFile
914

1015
abstract class Profile:
1116
def unitProfile(unit: CompilationUnit): Profile.Info
1217
def recordNewLine()(using Context): Unit
1318
def recordNewToken()(using Context): Unit
1419
def recordTasty(size: Int)(using Context): Unit
20+
def recordMethodSize(meth: Symbol, size: Int, span: Span)(using Context): Unit
1521
def printSummary()(using Context): Unit
1622

1723
object Profile:
1824
def current(using Context): Profile =
1925
val run = ctx.run
2026
if run == null then NoProfile else run.profile
2127

22-
private val TastyFactor = 50
28+
inline val TastyChunkSize = 50
2329

24-
class Info:
30+
case class MethodInfo(meth: Symbol, size: Int, span: Span)
31+
object NoInfo extends MethodInfo(NoSymbol, 0, NoSpan)
32+
33+
class Info(details: Int):
2534
var lineCount: Int = 0
2635
var tokenCount: Int = 0
2736
var tastySize: Int = 0
28-
def complexity: Float = tastySize.toFloat/lineCount/TastyFactor
37+
def complexity: Float = (tastySize/TastyChunkSize).toFloat/lineCount
38+
val leading: Array[MethodInfo] = Array.fill[MethodInfo](details)(NoInfo)
39+
40+
def recordMethodSize(meth: Symbol, size: Int, span: Span): Unit =
41+
var i = leading.length
42+
while i > 0 && leading(i - 1).size < size do
43+
if i < leading.length then leading(i) = leading(i - 1)
44+
i -= 1
45+
if i < leading.length then
46+
leading(i) = MethodInfo(meth, size, span)
47+
end Info
2948
end Profile
3049

31-
class ActiveProfile extends Profile:
50+
class ActiveProfile(details: Int) extends Profile:
3251

3352
private val pinfo = new EqHashMap[CompilationUnit, Profile.Info]
3453

35-
private val junkInfo = new Profile.Info
54+
private val junkInfo = new Profile.Info(0)
3655

3756
private def curInfo(using Context): Profile.Info =
3857
val unit: CompilationUnit | Null = ctx.compilationUnit
3958
if unit == null then junkInfo else unitProfile(unit)
4059

4160
def unitProfile(unit: CompilationUnit): Profile.Info =
42-
pinfo.getOrElseUpdate(unit, new Profile.Info)
61+
pinfo.getOrElseUpdate(unit, new Profile.Info(details))
4362

4463
def recordNewLine()(using Context): Unit =
4564
curInfo.lineCount += 1
4665
def recordNewToken()(using Context): Unit =
4766
curInfo.tokenCount += 1
4867
def recordTasty(size: Int)(using Context): Unit =
4968
curInfo.tastySize += size
69+
def recordMethodSize(meth: Symbol, size: Int, span: Span)(using Context): Unit =
70+
curInfo.recordMethodSize(meth, size, span)
5071

5172
def printSummary()(using Context): Unit =
5273
val units =
5374
val rawUnits = pinfo.keysIterator.toArray
54-
ctx.settings.YprofileSortedBy.value match
75+
ctx.settings.VprofileSortedBy.value match
5576
case "name" => rawUnits.sortBy(_.source.file.name)
5677
case "path" => rawUnits.sortBy(_.source.file.path)
5778
case "lines" => rawUnits.sortBy(unitProfile(_).lineCount)
5879
case "tokens" => rawUnits.sortBy(unitProfile(_).tokenCount)
5980
case "complexity" => rawUnits.sortBy(unitProfile(_).complexity)
6081
case _ => rawUnits.sortBy(unitProfile(_).tastySize)
6182

62-
val nameWidth = units.map(_.source.file.name.length).max.max(10).min(50)
63-
val layout = s"%-${nameWidth}s %6s %8s %8s %s %s"
64-
report.echo(layout.format("Source file", "Lines", "Tokens", "Tasty", " Complexity/Line", "Directory"))
83+
def printHeader(sourceNameWidth: Int, methNameWidth: Int = 0): String =
84+
val prefix =
85+
if methNameWidth > 0
86+
then s"%-${sourceNameWidth}s %-${methNameWidth}s".format("Sourcefile", "Method")
87+
else s"%-${sourceNameWidth}s".format("Sourcefile")
88+
val layout = s"%-${prefix.length}s %6s %8s %7s %s %s"
89+
report.echo(layout.format(prefix, "Lines", "Tokens", "Tasty", " Complexity/Line", "Directory"))
90+
layout
6591

66-
def printInfo(name: String, info: Profile.Info, path: String) =
92+
def printInfo(layout: String, name: String, info: Profile.Info, path: String) =
6793
val complexity = info.complexity
6894
val explanation =
6995
if complexity < 1 then "low "
7096
else if complexity < 5 then "moderate"
7197
else if complexity < 25 then "high "
7298
else "extreme "
73-
val complexityStr = s"${"%6.2f".format(info.complexity)} $explanation"
7499
report.echo(layout.format(
75-
name, info.lineCount, info.tokenCount, info.tastySize, complexityStr, path))
76-
77-
val agg = new Profile.Info
78-
for unit <- units do
79-
val info = unitProfile(unit)
80-
val file = unit.source.file
81-
printInfo(file.name, info, file.container.path)
82-
agg.lineCount += info.lineCount
83-
agg.tokenCount += info.tokenCount
84-
agg.tastySize += info.tastySize
85-
if units.length > 1 then
86-
report.echo(s"${"-"*nameWidth}------------------------------------------")
87-
printInfo("Total", agg, "")
100+
name, info.lineCount, info.tokenCount, info.tastySize/Profile.TastyChunkSize,
101+
s"${"%6.2f".format(complexity)} $explanation", path))
102+
103+
def safeMax(xs: Array[Int]) = xs.max.max(10).min(50)
104+
105+
def printAndAggregateSourceInfos(): Profile.Info =
106+
val sourceNameWidth = safeMax(units.map(_.source.file.name.length))
107+
val layout = printHeader(sourceNameWidth)
108+
val agg = new Profile.Info(details)
109+
for unit <- units do
110+
val file = unit.source.file
111+
val info = unitProfile(unit)
112+
printInfo(layout, file.name, info, file.container.path)
113+
agg.lineCount += info.lineCount
114+
agg.tokenCount += info.tokenCount
115+
agg.tastySize += info.tastySize
116+
for Profile.MethodInfo(meth, size, span) <- info.leading do
117+
agg.recordMethodSize(meth, size, span)
118+
if units.length > 1 then
119+
report.echo(s"${"-" * sourceNameWidth}------------------------------------------")
120+
printInfo(layout, "Total", agg, "")
121+
agg
122+
123+
def printDetails(agg: Profile.Info): Unit =
124+
val sourceNameWidth = safeMax(agg.leading.map(_.meth.source.name.length))
125+
val methNameWidth = safeMax(agg.leading.map(_.meth.name.toString.length))
126+
report.echo("\nMost complex methods:")
127+
val layout = printHeader(sourceNameWidth, methNameWidth)
128+
for
129+
Profile.MethodInfo(meth, size, span) <- agg.leading.reverse
130+
unit <- units.find(_.source.eq(meth.source))
131+
do
132+
val methProfile = new ActiveProfile(0)
133+
val methCtx = ctx.fresh.setCompilationUnit(unit)
134+
val s = Scanner(meth.source, span.start, methProfile)(using methCtx)
135+
while s.offset < span.end do s.nextToken()
136+
val info = methProfile.unitProfile(unit)
137+
info.tastySize = size
138+
val file = meth.source.file
139+
val header = s"%-${sourceNameWidth}s %-${methNameWidth}s".format(file.name, meth.name)
140+
printInfo(layout, header, info, file.container.path)
141+
142+
val agg = printAndAggregateSourceInfos()
143+
if details > 0 then printDetails(agg)
88144
end printSummary
89145
end ActiveProfile
90146

@@ -93,4 +149,5 @@ object NoProfile extends Profile:
93149
def recordNewLine()(using Context): Unit = ()
94150
def recordNewToken()(using Context): Unit = ()
95151
def recordTasty(size: Int)(using Context): Unit = ()
152+
def recordMethodSize(meth: Symbol, size: Int, span: Span)(using Context): Unit = ()
96153
def printSummary()(using Context): Unit = ()

0 commit comments

Comments
 (0)