Skip to content

Commit 3279a4c

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). 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 3279a4c

File tree

11 files changed

+277
-139
lines changed

11 files changed

+277
-139
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.asyncTastyHolder
100+
reporter <- async.sync
101+
do
102+
reporter.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: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,10 @@ class CompilationUnit protected (val source: SourceFile, val info: CompilationUn
9999
if !suspended then
100100
suspended = true
101101
ctx.run.nn.suspendedUnits += this
102+
val isInliningPhase = ctx.phase == Phases.inliningPhase
102103
if ctx.settings.XprintSuspension.value then
103-
ctx.run.nn.suspendedHints += (this -> hint)
104-
if ctx.phase == Phases.inliningPhase then
104+
ctx.run.nn.suspendedHints += (this -> (hint, isInliningPhase))
105+
if isInliningPhase then
105106
suspendedAtInliningPhase = true
106107
throw CompilationUnit.SuspendException()
107108

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ class Driver {
3535
val run = compiler.newRun
3636
runOrNull = run
3737
run.compile(files)
38-
finish(compiler, run)
38+
inContext(ctx.fresh.resetAsyncTasty()):
39+
finish(compiler, run)
3940
catch
4041
case ex: FatalError =>
4142
report.error(ex.getMessage.nn) // signals that we should fail compilation.
@@ -54,11 +55,11 @@ class Driver {
5455
if (ctx.settings.XprintSuspension.value)
5556
val suspendedHints = run.suspendedHints.toList
5657
report.echo(i"compiling suspended $suspendedUnits%, %")
57-
for (unit, hint) <- suspendedHints do
58-
report.echo(s" $unit: $hint")
58+
for (unit, (hint, atInlining)) <- suspendedHints do
59+
report.echo(s" $unit at ${if atInlining then "inlining" else "typer"}: $hint")
5960
val run1 = compiler.newRun
6061
run1.compileSuspendedUnits(suspendedUnits)
61-
finish(compiler, run1)(using MacroClassLoader.init(ctx.fresh))
62+
finish(compiler, run1)(using MacroClassLoader.init(ctx.fresh.resetAsyncTasty()))
6263

6364
protected def initCtx: Context = (new ContextBase).initialCtx
6465

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
130130
myUnits = us
131131

132132
var suspendedUnits: mutable.ListBuffer[CompilationUnit] = mutable.ListBuffer()
133-
var suspendedHints: mutable.Map[CompilationUnit, String] = mutable.HashMap()
133+
var suspendedHints: mutable.Map[CompilationUnit, (String, Boolean)] = mutable.HashMap()
134134

135135
def checkSuspendedUnits(newUnits: List[CompilationUnit])(using Context): Unit =
136136
if newUnits.isEmpty && suspendedUnits.nonEmpty && !ctx.reporter.errorsReported then

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

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ object Contexts {
5656
private val (importInfoLoc, store9) = store8.newLocation[ImportInfo | Null]()
5757
private val (typeAssignerLoc, store10) = store9.newLocation[TypeAssigner](TypeAssigner)
5858
private val (progressCallbackLoc, store11) = store10.newLocation[ProgressCallback | Null]()
59-
private val (tastyPromiseLoc, store12) = store11.newLocation[Option[AsyncTastyHolder]](None)
59+
private val (asyncTastyLoc, store12) = store11.newLocation[Option[AsyncTastyHolder]](None)
6060

6161
private val initialStore = store12
6262

@@ -200,7 +200,7 @@ object Contexts {
200200
/** The current settings values */
201201
def settingsState: SettingsState = store(settingsStateLoc)
202202

203-
def asyncTastyPromise: Option[AsyncTastyHolder] = store(tastyPromiseLoc)
203+
def asyncTastyHolder: Option[AsyncTastyHolder] = store(asyncTastyLoc)
204204

205205
/** The current compilation unit */
206206
def compilationUnit: CompilationUnit = store(compilationUnitLoc)
@@ -690,9 +690,28 @@ object Contexts {
690690
updateStore(compilationUnitLoc, compilationUnit)
691691
}
692692

693+
private def setAsyncTasty(): this.type =
694+
// should we provide a custom ExecutionContext?
695+
// currently it is just used to call the `apiPhaseCompleted` and `dependencyPhaseCompleted` callbacks in Zinc
696+
import scala.concurrent.ExecutionContext.Implicits.global
697+
updateStore(asyncTastyLoc, Some(AsyncTastyHolder.init))
698+
693699
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())))
700+
assert(store(asyncTastyLoc) == None, "trying to set async tasty holder twice!")
701+
setAsyncTasty()
702+
703+
/**replace the async tasty holder if one exists, ready for the next run.
704+
*/
705+
def resetAsyncTasty(): this.type =
706+
asyncTastyHolder match
707+
case Some(asyncTasty) =>
708+
if !reporter.hasErrors && asyncTasty.syncIsDone then
709+
updateStore(asyncTastyLoc, None) // we closed so clear the holder.
710+
else
711+
setAsyncTasty() // reset the holder as we will start a new run and write to it
712+
case _ =>
713+
this
714+
696715

697716
def setCompilerCallback(callback: CompilerCallback): this.type = updateStore(compilerCallbackLoc, callback)
698717
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.asyncTastyHolder.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)