Skip to content

Commit 97422d9

Browse files
committed
Address feedback. Adjust semantics.
Now in backend we block on the composition of the Zinc API callbacks and tasty being written. We also now conditionally signal that we are "done" writing tasty, based on if any units were suspended. This works in line with the Zinc default, which will ignore the early output anyway under the presence of macros (user can override this). Also move async tasty holder to the Run, as it is now context dependent on suspending. TODO: give the user an option to optimise performance by preventing definition of such problematic macros (which would also avoid suspensions).
1 parent a1a2c10 commit 97422d9

File tree

11 files changed

+323
-157
lines changed

11 files changed

+323
-157
lines changed

compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ import dotty.tools.io.JarArchive
2020

2121
import scala.language.unsafeNulls
2222

23-
23+
/** !!! This file is now copied in `dotty.tools.io.FileWriters` in a more general way that does not rely upon
24+
* `PostProcessorFrontendAccess`, this should probably be changed to wrap that class instead.
25+
*
26+
* Until then, any changes to this file should be copied to `dotty.tools.io.FileWriters` as well.
27+
*/
2428
class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess) {
2529
type NullableFile = AbstractFile | Null
2630
import frontendAccess.{compilerSettings, backendReporting}

compiler/src/dotty/tools/backend/jvm/GenBCode.scala

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import scala.collection.mutable
1212
import scala.compiletime.uninitialized
1313
import java.util.concurrent.TimeoutException
1414

15-
import scala.concurrent.duration.given
15+
import scala.concurrent.duration.Duration
1616
import scala.concurrent.Await
1717

1818
class GenBCode extends Phase { self =>
@@ -94,20 +94,15 @@ class GenBCode extends Phase { self =>
9494
try
9595
val result = super.runOn(units)
9696
generatedClassHandler.complete()
97-
for holder <- ctx.asyncTastyPromise do
98-
try
99-
val asyncState = Await.result(holder.promise.future, 5.seconds)
100-
for reporter <- asyncState.pending do
101-
reporter.relayReports(frontendAccess.backendReporting)
102-
catch
103-
case _: TimeoutException =>
104-
report.error(
105-
"""Timeout (5s) in backend while waiting for async writing of TASTy files to -Yearly-tasty-output,
106-
| this may be a bug in the compiler.
107-
|
108-
|Alternatively consider turning off pipelining for this project.""".stripMargin
109-
)
110-
end for
97+
try
98+
for
99+
async <- ctx.run.nn.asyncTasty
100+
bufferedReporter <- async.sync()
101+
do
102+
bufferedReporter.relayReports(frontendAccess.backendReporting)
103+
catch
104+
case ex: Exception =>
105+
report.error(s"exception from future: $ex, (${Option(ex.getCause())})")
111106
result
112107
finally
113108
// frontendAccess and postProcessor are created lazilly, clean them up only if they were initialized

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,15 @@ class CompilationUnit protected (val source: SourceFile, val info: CompilationUn
9898
depRecorder.clear()
9999
if !suspended then
100100
suspended = true
101-
ctx.run.nn.suspendedUnits += this
101+
val currRun = ctx.run.nn
102+
currRun.suspendedUnits += this
103+
val isInliningPhase = ctx.phase == Phases.inliningPhase
102104
if ctx.settings.XprintSuspension.value then
103-
ctx.run.nn.suspendedHints += (this -> hint)
104-
if ctx.phase == Phases.inliningPhase then
105+
currRun.suspendedHints += (this -> (hint, isInliningPhase))
106+
if isInliningPhase then
105107
suspendedAtInliningPhase = true
108+
else
109+
currRun.suspendedAtTyperPhase = true
106110
throw CompilationUnit.SuspendException()
107111

108112
private var myAssignmentSpans: Map[Int, List[Span]] | Null = null

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

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ class Driver {
5454
if (ctx.settings.XprintSuspension.value)
5555
val suspendedHints = run.suspendedHints.toList
5656
report.echo(i"compiling suspended $suspendedUnits%, %")
57-
for (unit, hint) <- suspendedHints do
58-
report.echo(s" $unit: $hint")
57+
for (unit, (hint, atInlining)) <- suspendedHints do
58+
report.echo(s" $unit at ${if atInlining then "inlining" else "typer"}: $hint")
5959
val run1 = compiler.newRun
60-
run1.compileSuspendedUnits(suspendedUnits)
60+
run1.compileSuspendedUnits(suspendedUnits, !run.suspendedAtTyperPhase)
6161
finish(compiler, run1)(using MacroClassLoader.init(ctx.fresh))
6262

6363
protected def initCtx: Context = (new ContextBase).initialCtx
@@ -66,13 +66,6 @@ class Driver {
6666

6767
protected def command: CompilerCommand = ScalacCommand
6868

69-
private def setupAsyncTasty(ictx: FreshContext): Unit = inContext(ictx):
70-
ictx.settings.YearlyTastyOutput.value match
71-
case earlyOut if earlyOut.isDirectory && earlyOut.exists =>
72-
ictx.setInitialAsyncTasty()
73-
case _ =>
74-
() // do nothing
75-
7669
/** Setup context with initialized settings from CLI arguments, then check if there are any settings that
7770
* would change the default behaviour of the compiler.
7871
*
@@ -89,7 +82,6 @@ class Driver {
8982
Positioned.init(using ictx)
9083

9184
inContext(ictx) {
92-
setupAsyncTasty(ictx)
9385
if !ctx.settings.YdropComments.value || ctx.settings.YreadComments.value then
9486
ictx.setProperty(ContextDoc, new ContextDocstrings)
9587
val fileNamesOrNone = command.checkUsage(summary, sourcesRequired)(using ctx.settings)(using ctx.settingsState)

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import scala.io.Codec
3737
import Run.Progress
3838
import scala.compiletime.uninitialized
3939
import dotty.tools.dotc.transform.MegaPhase
40+
import dotty.tools.dotc.transform.Pickler.AsyncTastyHolder
4041

4142
/** A compiler run. Exports various methods to compile source files */
4243
class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with ConstraintRunInfo {
@@ -130,7 +131,10 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
130131
myUnits = us
131132

132133
var suspendedUnits: mutable.ListBuffer[CompilationUnit] = mutable.ListBuffer()
133-
var suspendedHints: mutable.Map[CompilationUnit, String] = mutable.HashMap()
134+
var suspendedHints: mutable.Map[CompilationUnit, (String, Boolean)] = mutable.HashMap()
135+
136+
/** Were any units suspended in the typer phase? if so then pipeline tasty can not complete. */
137+
var suspendedAtTyperPhase: Boolean = false
134138

135139
def checkSuspendedUnits(newUnits: List[CompilationUnit])(using Context): Unit =
136140
if newUnits.isEmpty && suspendedUnits.nonEmpty && !ctx.reporter.errorsReported then
@@ -231,6 +235,24 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
231235
if !progress.isCancelled() then
232236
progress.tickSubphase()
233237

238+
/** if true, then we are done writing pipelined TASTy files (i.e. finished in a previous run.) */
239+
private var myAsyncTastyWritten = false
240+
241+
private var _asyncTasty: Option[AsyncTastyHolder] = None
242+
243+
/** populated when this run needs to write pipeline TASTy files. */
244+
def asyncTasty: Option[AsyncTastyHolder] = _asyncTasty
245+
246+
private def initializeAsyncTasty()(using Context): () => Context ?=> Unit =
247+
// should we provide a custom ExecutionContext?
248+
// currently it is just used to call the `apiPhaseCompleted` and `dependencyPhaseCompleted` callbacks in Zinc
249+
import scala.concurrent.ExecutionContext.Implicits.global
250+
val async = AsyncTastyHolder.init
251+
_asyncTasty = Some(async)
252+
() => ctx ?=>
253+
if ctx.reporter.hasErrors then
254+
async.cancel()
255+
234256
/** Will be set to true if any of the compiled compilation units contains
235257
* a pureFunctions language import.
236258
*/
@@ -342,13 +364,22 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
342364

343365
val runCtx = ctx.fresh
344366
runCtx.setProfiler(Profiler())
367+
368+
val cancelAsyncTasty: () => Context ?=> Unit =
369+
if !myAsyncTastyWritten && Phases.picklerPhase.exists && !ctx.settings.YearlyTastyOutput.isDefault then
370+
initializeAsyncTasty()
371+
else () => {}
372+
345373
unfusedPhases.foreach(_.initContext(runCtx))
346374
val fusedPhases = runCtx.base.allPhases
347375
if ctx.settings.explainCyclic.value then
348376
runCtx.setProperty(CyclicReference.Trace, new CyclicReference.Trace())
349377
runCtx.withProgressCallback: cb =>
350378
_progress = Progress(cb, this, fusedPhases.map(_.traversals).sum)
379+
351380
runPhases(allPhases = fusedPhases)(using runCtx)
381+
cancelAsyncTasty()
382+
352383
ctx.reporter.finalizeReporting()
353384
if (!ctx.reporter.hasErrors)
354385
Rewrites.writeBack()
@@ -365,9 +396,12 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
365396
/** Is this run started via a compilingSuspended? */
366397
def isCompilingSuspended: Boolean = myCompilingSuspended
367398

368-
/** Compile units `us` which were suspended in a previous run */
369-
def compileSuspendedUnits(us: List[CompilationUnit]): Unit =
399+
/** Compile units `us` which were suspended in a previous run,
400+
* also signal if all necessary async tasty files were written in a previous run.
401+
*/
402+
def compileSuspendedUnits(us: List[CompilationUnit], asyncTastyWritten: Boolean): Unit =
370403
myCompilingSuspended = true
404+
myAsyncTastyWritten = asyncTastyWritten
371405
for unit <- us do unit.suspended = false
372406
compileUnits(us)
373407

compiler/src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,9 @@ import StdNames.nme
3131
import compiletime.uninitialized
3232

3333
import scala.annotation.internal.sharable
34-
import scala.concurrent.Promise
3534

3635
import DenotTransformers.DenotTransformer
3736
import dotty.tools.dotc.profile.Profiler
38-
import dotty.tools.dotc.transform.Pickler.AsyncTastyHolder
3937
import dotty.tools.dotc.sbt.interfaces.{IncrementalCallback, ProgressCallback}
4038
import util.Property.Key
4139
import util.Store
@@ -56,9 +54,8 @@ object Contexts {
5654
private val (importInfoLoc, store9) = store8.newLocation[ImportInfo | Null]()
5755
private val (typeAssignerLoc, store10) = store9.newLocation[TypeAssigner](TypeAssigner)
5856
private val (progressCallbackLoc, store11) = store10.newLocation[ProgressCallback | Null]()
59-
private val (tastyPromiseLoc, store12) = store11.newLocation[Option[AsyncTastyHolder]](None)
6057

61-
private val initialStore = store12
58+
private val initialStore = store11
6259

6360
/** The current context */
6461
inline def ctx(using ctx: Context): Context = ctx
@@ -200,8 +197,6 @@ object Contexts {
200197
/** The current settings values */
201198
def settingsState: SettingsState = store(settingsStateLoc)
202199

203-
def asyncTastyPromise: Option[AsyncTastyHolder] = store(tastyPromiseLoc)
204-
205200
/** The current compilation unit */
206201
def compilationUnit: CompilationUnit = store(compilationUnitLoc)
207202

@@ -690,9 +685,6 @@ object Contexts {
690685
updateStore(compilationUnitLoc, compilationUnit)
691686
}
692687

693-
def setInitialAsyncTasty(): this.type =
694-
assert(store(tastyPromiseLoc) == None, "trying to set async tasty promise twice!")
695-
updateStore(tastyPromiseLoc, Some(AsyncTastyHolder(settings.YearlyTastyOutput.value, Promise())))
696688

697689
def setCompilerCallback(callback: CompilerCallback): this.type = updateStore(compilerCallbackLoc, callback)
698690
def setIncCallback(callback: IncrementalCallback): this.type = updateStore(incCallbackLoc, callback)

compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,7 @@ class ExtractAPI extends Phase {
9090
val sourceFile = cls.source
9191
if sourceFile.exists && cls.isDefinedInCurrentRun then
9292
recordNonLocalClass(cls, sourceFile, cb)
93-
for holder <- ctx.asyncTastyPromise do
94-
import scala.concurrent.ExecutionContext.Implicits.global
95-
// do not expect to be completed with failure
96-
holder.promise.future.foreach: state =>
97-
if !state.hasErrors then
98-
// We also await the promise at GenBCode to emit warnings/errors
99-
cb.apiPhaseCompleted()
100-
cb.dependencyPhaseCompleted()
93+
ctx.run.nn.asyncTasty.foreach(_.signalAPIComplete())
10194

10295
private def recordNonLocalClass(cls: Symbol, sourceFile: SourceFile, cb: interfaces.IncrementalCallback)(using Context): Unit =
10396
def registerProductNames(fullClassName: String, binaryClassName: String) =

compiler/src/dotty/tools/dotc/sbt/package.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,29 @@ import dotty.tools.dotc.core.NameOps.stripModuleClassSuffix
66
import dotty.tools.dotc.core.Names.Name
77
import dotty.tools.dotc.core.Names.termName
88

9+
import interfaces.IncrementalCallback
10+
import dotty.tools.io.FileWriters.BufferingReporter
11+
import dotty.tools.dotc.core.Decorators.em
12+
13+
import scala.util.chaining.given
14+
import scala.util.control.NonFatal
15+
916
inline val TermNameHash = 1987 // 300th prime
1017
inline val TypeNameHash = 1993 // 301st prime
1118
inline val InlineParamHash = 1997 // 302nd prime
1219

20+
def asyncZincPhasesCompleted(cb: IncrementalCallback, pending: Option[BufferingReporter]): Option[BufferingReporter] =
21+
val zincReporter = pending match
22+
case Some(buffered) => buffered
23+
case None => BufferingReporter()
24+
try
25+
cb.apiPhaseCompleted()
26+
cb.dependencyPhaseCompleted()
27+
catch
28+
case NonFatal(t) =>
29+
zincReporter.exception(em"signaling API and Dependencies phases completion", t)
30+
if zincReporter.hasErrors then Some(zincReporter) else None
31+
1332
extension (sym: Symbol)
1433

1534
/** Mangle a JVM symbol name in a format better suited for internal uses by sbt.

0 commit comments

Comments
 (0)