@@ -12,7 +12,9 @@ import typer.Typer
12
12
import typer .ImportInfo .withRootImports
13
13
import Decorators ._
14
14
import io .AbstractFile
15
- import Phases .unfusedPhases
15
+ import Phases .{unfusedPhases , Phase }
16
+
17
+ import sbt .interfaces .ProgressCallback
16
18
17
19
import util ._
18
20
import reporting .{Suppression , Action , Profile , ActiveProfile , NoProfile }
@@ -32,6 +34,9 @@ import scala.collection.mutable
32
34
import scala .util .control .NonFatal
33
35
import scala .io .Codec
34
36
37
+ import Run .Progress
38
+ import scala .compiletime .uninitialized
39
+
35
40
/** A compiler run. Exports various methods to compile source files */
36
41
class Run (comp : Compiler , ictx : Context ) extends ImplicitRunInfo with ConstraintRunInfo {
37
42
@@ -155,14 +160,51 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
155
160
}
156
161
157
162
/** The source files of all late entered symbols, as a set */
158
- private var lateFiles = mutable.Set [AbstractFile ]()
163
+ private val lateFiles = mutable.Set [AbstractFile ]()
159
164
160
165
/** A cache for static references to packages and classes */
161
166
val staticRefs = util.EqHashMap [Name , Denotation ](initialCapacity = 1024 )
162
167
163
168
/** Actions that need to be performed at the end of the current compilation run */
164
169
private var finalizeActions = mutable.ListBuffer [() => Unit ]()
165
170
171
+ private var _progress : Progress | Null = null // Set if progress reporting is enabled
172
+
173
+ /** Only safe to call if progress is being tracked. */
174
+ private inline def trackProgress (using Context )(inline op : Context ?=> Progress => Unit ): Unit =
175
+ val local = _progress
176
+ if local != null then
177
+ op(using ctx)(local)
178
+
179
+ def doBeginUnit (unit : CompilationUnit )(using Context ): Unit =
180
+ trackProgress : progress =>
181
+ progress.informUnitStarting(unit)
182
+
183
+ def doAdvanceUnit ()(using Context ): Unit =
184
+ trackProgress : progress =>
185
+ progress.unitc += 1 // trace that we completed a unit in the current phase
186
+ progress.refreshProgress()
187
+
188
+ def doAdvanceLate ()(using Context ): Unit =
189
+ trackProgress : progress =>
190
+ progress.latec += 1 // trace that we completed a late compilation
191
+ progress.refreshProgress()
192
+
193
+ private def doEnterPhase (currentPhase : Phase )(using Context ): Unit =
194
+ trackProgress : progress =>
195
+ progress.enterPhase(currentPhase)
196
+
197
+ private def doAdvancePhase (currentPhase : Phase , wasRan : Boolean )(using Context ): Unit =
198
+ trackProgress : progress =>
199
+ progress.unitc = 0 // reset unit count in current phase
200
+ progress.seen += 1 // trace that we've seen a phase
201
+ if wasRan then
202
+ // add an extra traversal now that we completed a phase
203
+ progress.traversalc += 1
204
+ else
205
+ // no phase was ran, remove a traversal from expected total
206
+ progress.runnablePhases -= 1
207
+
166
208
/** Will be set to true if any of the compiled compilation units contains
167
209
* a pureFunctions language import.
168
210
*/
@@ -233,13 +275,15 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
233
275
if ctx.settings.YnoDoubleBindings .value then
234
276
ctx.base.checkNoDoubleBindings = true
235
277
236
- def runPhases (using Context ) = {
278
+ def runPhases (allPhases : Array [ Phase ])( using Context ) = {
237
279
var lastPrintedTree : PrintedTree = NoPrintedTree
238
280
val profiler = ctx.profiler
239
281
var phasesWereAdjusted = false
240
282
241
- for (phase <- ctx.base.allPhases)
242
- if (phase.isRunnable)
283
+ for phase <- allPhases do
284
+ doEnterPhase(phase)
285
+ val phaseWillRun = phase.isRunnable
286
+ if phaseWillRun then
243
287
Stats .trackTime(s " phase time ms/ $phase" ) {
244
288
val start = System .currentTimeMillis
245
289
val profileBefore = profiler.beforePhase(phase)
@@ -260,14 +304,21 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
260
304
if ! Feature .ccEnabledSomewhere then
261
305
ctx.base.unlinkPhaseAsDenotTransformer(Phases .checkCapturesPhase.prev)
262
306
ctx.base.unlinkPhaseAsDenotTransformer(Phases .checkCapturesPhase)
263
-
307
+ end if
308
+ end if
309
+ end if
310
+ doAdvancePhase(phase, wasRan = phaseWillRun)
311
+ end for
264
312
profiler.finished()
265
313
}
266
314
267
315
val runCtx = ctx.fresh
268
316
runCtx.setProfiler(Profiler ())
269
317
unfusedPhases.foreach(_.initContext(runCtx))
270
- runPhases(using runCtx)
318
+ val fusedPhases = runCtx.base.allPhases
319
+ runCtx.withProgressCallback: cb =>
320
+ _progress = Progress (cb, this , fusedPhases.length)
321
+ runPhases(allPhases = fusedPhases)(using runCtx)
271
322
if (! ctx.reporter.hasErrors)
272
323
Rewrites .writeBack()
273
324
suppressions.runFinished(hasErrors = ctx.reporter.hasErrors)
@@ -293,10 +344,9 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
293
344
.withRootImports
294
345
295
346
def process ()(using Context ) =
296
- ctx.typer.lateEnterUnit(doTypeCheck =>
297
- if typeCheck then
298
- if compiling then finalizeActions += doTypeCheck
299
- else doTypeCheck()
347
+ ctx.typer.lateEnterUnit(typeCheck)(doTypeCheck =>
348
+ if compiling then finalizeActions += doTypeCheck
349
+ else doTypeCheck()
300
350
)
301
351
302
352
process()(using unitCtx)
@@ -399,7 +449,66 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
399
449
}
400
450
401
451
object Run {
452
+
453
+ /** Computes the next MegaPhase for the given phase.*/
454
+ def nextMegaPhase (phase : Phase )(using Context ): Phase = phase.megaPhase.next.megaPhase
455
+
456
+ private class Progress (cb : ProgressCallback , private val run : Run , val initialPhases : Int ):
457
+ private [Run ] var runnablePhases : Int = initialPhases // track how many phases we expect to run
458
+ private [Run ] var unitc : Int = 0 // current unit count in the current phase
459
+ private [Run ] var latec : Int = 0 // current late unit count
460
+ private [Run ] var traversalc : Int = 0 // completed traversals over all files
461
+ private [Run ] var seen : Int = 0 // how many phases we've seen so far
462
+
463
+ private var currPhase : Phase = uninitialized // initialized by enterPhase
464
+ private var currPhaseName : String = uninitialized // initialized by enterPhase
465
+ private var nextPhaseName : String = uninitialized // initialized by enterPhase
466
+
467
+ private def phaseNameFor (phase : Phase ): String =
468
+ if phase.exists then phase.phaseName
469
+ else " <end>"
470
+
471
+ private [Run ] def enterPhase (newPhase : Phase )(using Context ): Unit =
472
+ if newPhase ne currPhase then
473
+ currPhase = newPhase
474
+ currPhaseName = phaseNameFor(newPhase)
475
+ nextPhaseName = phaseNameFor(Run .nextMegaPhase(newPhase))
476
+ if seen > 0 then
477
+ refreshProgress()
478
+
479
+
480
+ /** Counts the number of completed full traversals over files, plus the number of units in the current phase */
481
+ private def currentProgress ()(using Context ): Int =
482
+ traversalc * run.files.size + unitc + latec
483
+
484
+ /** Total progress is computed as the sum of
485
+ * - the number of traversals we expect to make over all files
486
+ * - the number of late compilations
487
+ */
488
+ private def totalProgress ()(using Context ): Int =
489
+ runnablePhases * run.files.size + run.lateFiles.size
490
+
491
+ private def requireInitialized (): Unit =
492
+ require((currPhase : Phase | Null ) != null , " enterPhase was not called" )
493
+
494
+ private [Run ] def informUnitStarting (unit : CompilationUnit )(using Context ): Unit =
495
+ requireInitialized()
496
+ cb.informUnitStarting(currPhaseName, unit)
497
+
498
+ private [Run ] def refreshProgress ()(using Context ): Unit =
499
+ requireInitialized()
500
+ cb.progress(currentProgress(), totalProgress(), currPhaseName, nextPhaseName)
501
+
402
502
extension (run : Run | Null )
503
+ def beginUnit (unit : CompilationUnit )(using Context ): Unit =
504
+ if run != null then run.doBeginUnit(unit)
505
+
506
+ def advanceUnit ()(using Context ): Unit =
507
+ if run != null then run.doAdvanceUnit()
508
+
509
+ def advanceLate ()(using Context ): Unit =
510
+ if run != null then run.doAdvanceLate()
511
+
403
512
def enrichedErrorMessage : Boolean = if run == null then false else run.myEnrichedErrorMessage
404
513
def enrichErrorMessage (errorMessage : String )(using Context ): String =
405
514
if run == null then
0 commit comments