Skip to content

Commit acbb1c6

Browse files
committed
wip - address feedback, todo - await on zinc callback in backend
1 parent a1a2c10 commit acbb1c6

File tree

4 files changed

+100
-64
lines changed

4 files changed

+100
-64
lines changed

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

+5-1
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

+4-14
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 =>
@@ -95,19 +95,9 @@ class GenBCode extends Phase { self =>
9595
val result = super.runOn(units)
9696
generatedClassHandler.complete()
9797
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
98+
val asyncState = Await.result(holder.promise.future, Duration.Inf) // TODO: add another optional promise for Zinc callback, and await both.
99+
for reporter <- asyncState.pending do
100+
reporter.relayReports(frontendAccess.backendReporting)
111101
result
112102
finally
113103
// frontendAccess and postProcessor are created lazilly, clean them up only if they were initialized

compiler/src/dotty/tools/dotc/transform/Pickler.scala

+31-19
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import scala.concurrent.Promise
2626
import dotty.tools.dotc.transform.Pickler.writeSigFilesAsync
2727

2828
import scala.util.chaining.given
29-
import dotty.tools.io.FileWriters.BufferingDelayedReporting
29+
import dotty.tools.io.FileWriters.{EagerReporter, BufferingReporter}
3030

3131
object Pickler {
3232
val name: String = "pickler"
@@ -39,7 +39,7 @@ object Pickler {
3939
inline val ParallelPickling = true
4040

4141
class AsyncTastyHolder(val earlyOut: AbstractFile, val promise: Promise[AsyncTastyState])
42-
class AsyncTastyState(val hasErrors: Boolean, val pending: Option[BufferingDelayedReporting])
42+
class AsyncTastyState(val hasErrors: Boolean, val pending: Option[BufferingReporter])
4343

4444
// Why we only write to early output in the first run?
4545
// ===================================================
@@ -71,24 +71,35 @@ object Pickler {
7171
tasks: List[(String, Array[Byte])],
7272
writer: EarlyFileWriter,
7373
promise: Promise[AsyncTastyState])(using ctx: ReadOnlyContext): Unit = {
74+
75+
def logException(ex: Exception): Unit =
76+
ctx.reporter.error({
77+
val trace = ex.getStackTrace().nn.mkString("\n ")
78+
em"Error writing TASTy to early output:${ex}\n $trace"
79+
})
80+
7481
try
75-
for (internalName, pickled) <- tasks do
76-
val _ = writer.writeTasty(internalName, pickled)
77-
finally
7882
try
79-
writer.close()
83+
for (internalName, pickled) <- tasks do
84+
val _ = writer.writeTasty(internalName, pickled)
85+
catch
86+
case ex: Exception => logException(ex)
8087
finally
81-
promise.success(
82-
AsyncTastyState(
83-
hasErrors = ctx.reporter.hasErrors,
84-
pending = (
85-
ctx.reporter match
86-
case buffered: BufferingDelayedReporting => Some(buffered)
87-
case _ => None
88-
)
88+
writer.close()
89+
end try
90+
catch
91+
case ex: Exception => logException(ex)
92+
finally
93+
promise.success(
94+
AsyncTastyState(
95+
hasErrors = ctx.reporter.hasErrors,
96+
pending = (
97+
ctx.reporter match
98+
case buffered: BufferingReporter => Some(buffered)
99+
case _: EagerReporter => None // already reported
89100
)
90101
)
91-
end try
102+
)
92103
end try
93104
}
94105

@@ -115,7 +126,8 @@ class Pickler extends Phase {
115126

116127
override def description: String = Pickler.description
117128

118-
// No need to repickle trees coming from TASTY
129+
// No need to repickle trees coming from TASTY, however in the case that we need to write TASTy to early-output,
130+
// then we need to run this phase to send the tasty from compilation units to the early-output.
119131
override def isRunnable(using Context): Boolean =
120132
super.isRunnable && (!ctx.settings.fromTasty.value || doAsyncTasty)
121133

@@ -289,19 +301,19 @@ class Pickler extends Phase {
289301
}
290302

291303
override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = {
292-
val isConcurrent = useExecutor
304+
val useExecutor = this.useExecutor
293305

294306
val writeTask: Option[() => Unit] = ctx.asyncTastyPromise.map: holder =>
295307
() =>
296-
given ReadOnlyContext = if isConcurrent then ReadOnlyContext.buffered else ReadOnlyContext.eager
308+
given ReadOnlyContext = if useExecutor then ReadOnlyContext.buffered else ReadOnlyContext.eager
297309
val writer = Pickler.EarlyFileWriter(holder.earlyOut)
298310
writeSigFilesAsync(serialized.result(), writer, holder.promise)
299311

300312
def runPhase(writeCB: (doWrite: () => Unit) => Unit) =
301313
super.runOn(units).tap(_ => writeTask.foreach(writeCB))
302314

303315
val result =
304-
if isConcurrent then
316+
if useExecutor then
305317
executor.start()
306318
try
307319
runPhase: doWrite =>

compiler/src/dotty/tools/io/FileWriters.scala

+60-30
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,21 @@ import dotty.tools.dotc.report
3636

3737
import dotty.tools.backend.jvm.PostProcessorFrontendAccess.BackendReporting
3838
import scala.annotation.constructorOnly
39-
40-
/** Copied from `dotty.tools.backend.jvm.ClassfileWriters` but no `PostProcessorFrontendAccess` needed */
39+
import java.util.concurrent.atomic.AtomicReference
40+
import java.util.concurrent.atomic.AtomicBoolean
41+
42+
/** !!!Copied from `dotty.tools.backend.jvm.ClassfileWriters` but no `PostProcessorFrontendAccess` needed.
43+
* this should probably be changed to wrap that class instead.
44+
*
45+
* Until then, any changes to this file should be copied to `dotty.tools.backend.jvm.ClassfileWriters` as well.
46+
*/
4147
object FileWriters {
4248
type InternalName = String
4349
type NullableFile = AbstractFile | Null
4450

4551
inline def ctx(using ReadOnlyContext): ReadOnlyContext = summon[ReadOnlyContext]
4652

47-
sealed trait DelayedReporting {
53+
sealed trait DelayedReporter {
4854
def hasErrors: Boolean
4955
def error(message: Context ?=> Message, position: SourcePosition): Unit
5056
def warning(message: Context ?=> Message, position: SourcePosition): Unit
@@ -54,7 +60,7 @@ object FileWriters {
5460
def warning(message: Context ?=> Message): Unit = warning(message, NoSourcePosition)
5561
}
5662

57-
final class EagerDelayedReporting(using captured: Context) extends DelayedReporting:
63+
final class EagerReporter(using captured: Context) extends DelayedReporter:
5864
private var _hasErrors = false
5965

6066
def hasErrors: Boolean = _hasErrors
@@ -68,62 +74,86 @@ object FileWriters {
6874

6975
def log(message: String): Unit = report.echo(message)
7076

71-
final class BufferingDelayedReporting extends DelayedReporting {
77+
final class BufferingReporter extends DelayedReporter {
7278
// We optimise access to the buffered reports for the common case - that there are no warning/errors to report
7379
// We could use a listBuffer etc - but that would be extra allocation in the common case
74-
// Note - all access is externally synchronized, as this allow the reports to be generated in on thread and
75-
// consumed in another
76-
private var bufferedReports = List.empty[Report]
77-
private var _hasErrors = false
80+
// buffered logs are updated atomically.
81+
82+
private val _bufferedReports = AtomicReference(List.empty[Report])
83+
private val _hasErrors = AtomicBoolean(false)
84+
7885
enum Report(val relay: Context ?=> BackendReporting => Unit):
7986
case Error(message: Context => Message, position: SourcePosition) extends Report(ctx ?=> _.error(message(ctx), position))
8087
case Warning(message: Context => Message, position: SourcePosition) extends Report(ctx ?=> _.warning(message(ctx), position))
8188
case Log(message: String) extends Report(_.log(message))
8289

83-
def hasErrors: Boolean = synchronized:
84-
_hasErrors
90+
/** Atomically record that an error occurred */
91+
private def recordError(): Unit =
92+
while
93+
val old = _hasErrors.get
94+
!old && !_hasErrors.compareAndSet(old, true)
95+
do ()
96+
97+
/** Atomically add a report to the log */
98+
private def recordReport(report: Report): Unit =
99+
while
100+
val old = _bufferedReports.get
101+
!_bufferedReports.compareAndSet(old, report :: old)
102+
do ()
103+
104+
/** atomically extract and clear the buffered reports */
105+
private def resetReports(): List[Report] =
106+
while
107+
val old = _bufferedReports.get
108+
if _bufferedReports.compareAndSet(old, Nil) then
109+
return old
110+
else
111+
true
112+
do ()
113+
throw new AssertionError("Unreachable")
114+
115+
def hasErrors: Boolean = _hasErrors.get()
85116

86-
def error(message: Context ?=> Message, position: SourcePosition): Unit = synchronized:
87-
bufferedReports ::= Report.Error({case given Context => message}, position)
88-
_hasErrors = true
117+
def error(message: Context ?=> Message, position: SourcePosition): Unit =
118+
recordReport(Report.Error({case given Context => message}, position))
119+
recordError()
89120

90-
def warning(message: Context ?=> Message, position: SourcePosition): Unit = synchronized:
91-
bufferedReports ::= Report.Warning({case given Context => message}, position)
121+
def warning(message: Context ?=> Message, position: SourcePosition): Unit =
122+
recordReport(Report.Warning({case given Context => message}, position))
92123

93-
def log(message: String): Unit = synchronized:
94-
bufferedReports ::= Report.Log(message)
124+
def log(message: String): Unit =
125+
recordReport(Report.Log(message))
95126

96127
/** Should only be called from main compiler thread. */
97-
def relayReports(toReporting: BackendReporting)(using Context): Unit = synchronized:
98-
if bufferedReports.nonEmpty then
99-
bufferedReports.reverse.foreach(_.relay(toReporting))
100-
bufferedReports = Nil
128+
def relayReports(toReporting: BackendReporting)(using Context): Unit =
129+
val reports = resetReports()
130+
if reports.nonEmpty then
131+
reports.reverse.foreach(_.relay(toReporting))
101132
}
102133

103-
trait ReadSettings:
134+
trait ReadOnlySettings:
104135
def jarCompressionLevel: Int
105136
def debug: Boolean
106137

107138
trait ReadOnlyContext:
108-
109-
val settings: ReadSettings
110-
val reporter: DelayedReporting
139+
val settings: ReadOnlySettings
140+
val reporter: DelayedReporter
111141

112142
trait BufferedReadOnlyContext extends ReadOnlyContext:
113-
val reporter: BufferingDelayedReporting
143+
val reporter: BufferingReporter
114144

115145
object ReadOnlyContext:
116-
def readSettings(using ctx: Context): ReadSettings = new:
146+
def readSettings(using ctx: Context): ReadOnlySettings = new:
117147
val jarCompressionLevel = ctx.settings.YjarCompressionLevel.value
118148
val debug = ctx.settings.Ydebug.value
119149

120150
def buffered(using Context): BufferedReadOnlyContext = new:
121151
val settings = readSettings
122-
val reporter = BufferingDelayedReporting()
152+
val reporter = BufferingReporter()
123153

124154
def eager(using Context): ReadOnlyContext = new:
125155
val settings = readSettings
126-
val reporter = EagerDelayedReporting()
156+
val reporter = EagerReporter()
127157

128158
/**
129159
* The interface to writing classfiles. GeneratedClassHandler calls these methods to generate the

0 commit comments

Comments
 (0)