@@ -13,6 +13,25 @@ import config.Feature
13
13
14
14
import scala .util .control .NonFatal
15
15
import fromtasty .{TASTYCompiler , TastyFileUtil }
16
+ import dotty .tools .io .NoAbstractFile
17
+ import dotty .tools .io .{VirtualFile , VirtualDirectory }
18
+
19
+ import java .nio .file .Path as JPath
20
+ import scala .concurrent .*
21
+ import scala .annotation .internal .sharable
22
+ import scala .concurrent .duration .*
23
+ import scala .util .{Success , Failure }
24
+ import scala .annotation .targetName
25
+ import dotty .tools .dotc .classpath .FileUtils .hasScalaExtension
26
+ import dotty .tools .dotc .core .Symbols
27
+
28
+ object Driver {
29
+ @ sharable lazy val executor =
30
+ // fixed pool of 8 threads, because we have 8 cores
31
+ val pool = java.util.concurrent.Executors .newFixedThreadPool(8 ).nn
32
+ sys.addShutdownHook(pool.shutdown())
33
+ ExecutionContext .fromExecutor(pool)
34
+ }
16
35
17
36
/** Run the Dotty compiler.
18
37
*
@@ -28,6 +47,167 @@ class Driver {
28
47
29
48
protected def emptyReporter : Reporter = new StoreReporter (null )
30
49
50
+ protected def doCompile (files : List [AbstractFile ])(using ictx : Context ): Reporter =
51
+ val isOutline = ictx.settings.Youtline .value(using ictx)
52
+
53
+ if ! isOutline then inContext(ictx):
54
+ report.echo(s " basic compilation enabled on files ${files.headOption.map(f => s " $f... " ).getOrElse(" []" )}" )(using ictx)
55
+ doCompile(newCompiler, files) // standard compilation
56
+ else
57
+ report.echo(s " Outline compilation enabled on files ${files.headOption.map(f => s " $f... " ).getOrElse(" []" )}" )(using ictx)
58
+ val maxParallelism = ictx.settings.YmaxParallelism .valueIn(ictx.settingsState)
59
+ val absParallelism = math.abs(maxParallelism)
60
+ val isParallel = maxParallelism >= 0
61
+ val parallelism =
62
+ val ceiling = math.max(1 , Runtime .getRuntime().nn.availableProcessors() - 1 )
63
+ if absParallelism > 0 then math.min(absParallelism, ceiling)
64
+ else ceiling
65
+
66
+ // NOTE: sbt will delete this potentially as soon as you call `apiPhaseCompleted`
67
+ val pickleWriteOutput = ictx.settings.YearlyTastyOutput .valueIn(ictx.settingsState)
68
+
69
+ val pickleWriteSource =
70
+ pickleWriteOutput.underlyingSource match
71
+ case Some (source) =>
72
+ source.file.asInstanceOf [java.io.File | Null ] match
73
+ case f : java.io.File => Some (source)
74
+ case null =>
75
+ report.warning(s " Could not resolve file of ${source} (of class ${source.getClass.getName}) " )
76
+ None
77
+ case None =>
78
+ if pickleWriteOutput.isInstanceOf [dotty.tools.io.JarArchive ] then
79
+ report.warning(s " Could not resolve underlying source of jar ${pickleWriteOutput} (of class ${pickleWriteOutput.getClass.getName}) " )
80
+ None
81
+ else
82
+ report.warning(s " Could not resolve underlying source of ${pickleWriteOutput} (of class ${pickleWriteOutput.getClass.getName}) " )
83
+ Some (pickleWriteOutput)
84
+
85
+ val outlineOutput = new VirtualDirectory (" <outline-classpath>" ) {
86
+ override def underlyingSource : Option [AbstractFile ] = pickleWriteSource
87
+ }
88
+
89
+ if pickleWriteOutput == NoAbstractFile then
90
+ report.error(" Requested outline compilation with `-Youtline` " +
91
+ " but no output directory for TASTY files was specified with `-Ypickle-write`." )(using ictx)
92
+ return ictx.reporter
93
+
94
+ val firstPassCtx = ictx.fresh
95
+ .setSetting(ictx.settings.YoutlineClasspath , outlineOutput)
96
+ inContext(firstPassCtx):
97
+ doCompile(newCompiler, files)
98
+
99
+ def secondPassCtx (group : List [AbstractFile ], promise : scala.concurrent.Promise [Unit ]): Context =
100
+ val baseCtx = initCtx.fresh
101
+ .setSettings(ictx.settingsState) // copy over the classpath arguments also
102
+ .setSetting(ictx.settings.YsecondPass , true )
103
+ .setSetting(ictx.settings.YoutlineClasspath , outlineOutput)
104
+ .setCallbacks(ictx.store)
105
+ .setDepsFinishPromise(promise)
106
+ .setReporter(if isParallel then new StoreReporter (ictx.reporter) else ictx.reporter)
107
+
108
+ // if ictx.settings.YoutlineClasspath.valueIn(ictx.settingsState).isEmpty then
109
+ // baseCtx.setSetting(baseCtx.settings.YoutlineClasspath, pickleWriteAsClasspath)
110
+ val fileNames : Array [String ] =
111
+ if sourcesRequired then group.map(_.toString).toArray else Array .empty
112
+ setup(fileNames, baseCtx) match
113
+ case Some ((_, ctx)) =>
114
+ assert(ctx.incCallback != null , s " cannot run outline without incremental callback " )
115
+ assert(ctx.depsFinishPromiseOpt.isDefined, s " cannot run outline without dependencies promise " )
116
+ ctx
117
+ case None => baseCtx
118
+ end secondPassCtx
119
+
120
+ val scalaFiles = files.filter(_.hasScalaExtension)
121
+
122
+ // 516 units, 8 cores => maxGroupSize = 65, unitGroups = 8, compilers = 8
123
+ if ! firstPassCtx.reporter.hasErrors && scalaFiles.nonEmpty then
124
+ val maxGroupSize = Math .ceil(scalaFiles.length.toDouble / parallelism).toInt
125
+ val fileGroups = scalaFiles.grouped(maxGroupSize).toList
126
+ val compilers = fileGroups.length
127
+
128
+
129
+
130
+ def userRequestedSingleGroup = maxParallelism == 1
131
+
132
+ // TODO: probably not good to warn here because maybe compile is incremental
133
+ // if compilers == 1 && !userRequestedSingleGroup then
134
+ // val knownParallelism = maxParallelism > 0
135
+ // val requestedParallelism = s"Requested parallelism with `-Ymax-parallelism` was ${maxParallelism}"
136
+ // val computedAddedum =
137
+ // if knownParallelism then "."
138
+ // else s""",
139
+ // | therefore operating with computed parallelism of ${parallelism}.""".stripMargin
140
+ // val message =
141
+ // s"""Outline compilation second pass will run with a single compile group.
142
+ // | ${requestedParallelism}$computedAddedum
143
+ // | With ${scalaUnits.length} units to compile I can only batch them into a single group.
144
+ // | This will increase build times.
145
+ // | Perhaps consider turning off -Youtline for this project.""".stripMargin
146
+ // report.warning(message)(using firstPassCtx)
147
+
148
+ val promises = fileGroups.map(_ => scala.concurrent.Promise [Unit ]())
149
+
150
+ locally :
151
+ import scala .concurrent .ExecutionContext .Implicits .global
152
+ Future .sequence(promises.map(_.future)).andThen {
153
+ case Success (_) =>
154
+ ictx.withIncCallback(_.dependencyPhaseCompleted())
155
+ case Failure (ex) =>
156
+ ex.printStackTrace()
157
+ report.error(s " Exception during parallel compilation: ${ex.getMessage}" )(using firstPassCtx)
158
+ }
159
+
160
+ report.echo(s " Compiling $compilers groups of files ${if isParallel then " in parallel" else " sequentially" }" )(using firstPassCtx)
161
+
162
+ def compileEager (
163
+ promise : Promise [Unit ],
164
+ fileGroup : List [AbstractFile ]
165
+ ): Reporter = {
166
+ if ctx.settings.verbose.value then
167
+ report.echo(" #Compiling: " + fileGroup.take(3 ).mkString(" " , " , " , " ..." ))
168
+ val secondCtx = secondPassCtx(fileGroup, promise)
169
+ val reporter = inContext(secondCtx):
170
+ doCompile(newCompiler, fileGroup) // second pass
171
+ if ! secondCtx.reporter.hasErrors then
172
+ assert(promise.isCompleted, s " promise was not completed " )
173
+ if ctx.settings.verbose.value then
174
+ report.echo(" #Done: " + fileGroup.mkString(" " ))
175
+ reporter
176
+ }
177
+
178
+ def compileFuture (
179
+ promise : Promise [Unit ],
180
+ fileGroup : List [AbstractFile ]
181
+ )(using ExecutionContext ): Future [Reporter ] =
182
+ Future {
183
+ // println("#Compiling: " + fileGroup.mkString(" "))
184
+ val secondCtx = secondPassCtx(fileGroup, promise)
185
+ val reporter = inContext(secondCtx):
186
+ doCompile(newCompiler, fileGroup) // second pass
187
+ // println("#Done: " + fileGroup.mkString(" "))
188
+ reporter
189
+ }
190
+
191
+ if isParallel then
192
+ // val executor = java.util.concurrent.Executors.newFixedThreadPool(compilers).nn
193
+ given ec : ExecutionContext = Driver .executor // ExecutionContext.fromExecutor(executor)
194
+ val futureReporters = Future .sequence(promises.lazyZip(fileGroups).map(compileFuture)).andThen {
195
+ case Success (reporters) =>
196
+ reporters.foreach(_.flush()(using firstPassCtx))
197
+ case Failure (ex) =>
198
+ ex.printStackTrace
199
+ report.error(s " Exception during parallel compilation: ${ex.getMessage}" )(using firstPassCtx)
200
+ }
201
+ Await .ready(futureReporters, Duration .Inf )
202
+ // executor.shutdown()
203
+ else
204
+ promises.lazyZip(fileGroups).map(compileEager)
205
+ firstPassCtx.reporter
206
+ else
207
+ ictx.withIncCallback(_.dependencyPhaseCompleted()) // may be just java files compiled
208
+ firstPassCtx.reporter
209
+ end doCompile
210
+
31
211
protected def doCompile (compiler : Compiler , files : List [AbstractFile ])(using Context ): Reporter =
32
212
if files.nonEmpty then
33
213
var runOrNull = ctx.run
@@ -194,7 +374,7 @@ class Driver {
194
374
def process (args : Array [String ], rootCtx : Context ): Reporter = {
195
375
setup(args, rootCtx) match
196
376
case Some ((files, compileCtx)) =>
197
- doCompile(newCompiler( using compileCtx), files)(using compileCtx)
377
+ doCompile(files)(using compileCtx)
198
378
case None =>
199
379
rootCtx.reporter
200
380
}
0 commit comments