@@ -33,10 +33,10 @@ trait PhaseAssembly {
33
33
phasesSet.add(terminal)
34
34
reporter.warning(NoPosition , " Added default terminal phase" )
35
35
}
36
- val graph = DependencyGraph (phasesSet)
36
+ val graph = DependencyGraph (phasesSet, warnAll = ! settings.isScaladoc || settings.isDebug )
37
37
for (n <- settings.genPhaseGraph.valueSetByUser; d <- settings.outputDirs.getSingleOutput if ! d.isVirtual)
38
38
DependencyGraph .graphToDotFile(graph, Path (d.file) / File (s " $n.dot " ))
39
- graph.compilerPhaseList().tap(_ => if ( ! settings.isScaladoc || settings.isDebug) graph.warnings.foreach(msg => reporter.warning(NoPosition , msg)))
39
+ graph.compilerPhaseList().tap(_ => graph.warnings.foreach(msg => reporter.warning(NoPosition , msg)))
40
40
}
41
41
}
42
42
@@ -45,9 +45,11 @@ trait PhaseAssembly {
45
45
* Each vertex is labeled with its phase name.
46
46
*/
47
47
class DependencyGraph (order : Int , start : String , val components : Map [String , SubComponent ]) {
48
- import DependencyGraph .{FollowsNow , Weight }
48
+ import DependencyGraph .{Follows , FollowsNow , Weight }
49
49
50
- // private final val debugging = false
50
+ require(components.contains(start), s " Start $start is not a component in ${components.keys.mkString(" , " )}" )
51
+
52
+ private final val debugging = false
51
53
52
54
private var messages : List [String ] = Nil
53
55
def warning (message : String ): Unit = messages ::= message
@@ -80,11 +82,11 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
80
82
adjacency(v) ::= Edge (from = v, to = w, weight)
81
83
case Some (_) if weight == FollowsNow => // retain runsRightAfter if there is a competing constraint
82
84
adjacency(v) = Edge (from = v, to = w, weight) :: adjacency(v).filterNot(_.to == w)
83
- case _ =>
85
+ case _ => // ignore duplicate
84
86
}
85
87
}
86
88
87
- // input must be acyclic and only one FollowsNow edge is allowed between a pair of vertices
89
+ // input must be acyclic and a vertex can have only one outgoing FollowsNow edge
88
90
private def validate (): Unit = {
89
91
def checkFollowsNow (v : Int ): Unit =
90
92
adjacency(v).foldLeft(- 1 ) { (w, e) =>
@@ -122,6 +124,12 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
122
124
}
123
125
}
124
126
walk()
127
+ // check reachability
128
+ for (i <- 0 .until(order) if ! seen(i)) {
129
+ warning(s " Phase ${names(i)} is not reachable from $start" )
130
+ addEdge(start, names(i), Follows )
131
+ walk() // check cycles
132
+ }
125
133
}
126
134
127
135
def compilerPhaseList (): List [SubComponent ] = if (order == 1 ) List (components(start)) else {
@@ -141,11 +149,11 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
141
149
142
150
def dequeue (): Int = queue.dequeue().tap(v => enqueued(v) = false )
143
151
144
- // def namedEdge(e: Edge): String = if (e == null) "[no edge]" else s"${names(e.from)} ${if (e.weight == FollowsNow) "=" else "-"}> ${names(e.to)}"
152
+ def namedEdge (e : Edge ): String = if (e == null ) " [no edge]" else s " ${names(e.from)}< ${distance(e.from)} > ${if (e.weight == FollowsNow ) " =" else " -" }> ${names(e.to)}< ${distance(e.to)} > "
145
153
146
154
/** Remove a vertex from the queue and check outgoing edges:
147
- * if an edge improves (increases) the distance at the terminal ,
148
- * record that as the new incoming edge and enqueue that vertex
155
+ * if an edge improves (increases) the distance at the terminus of the edge ,
156
+ * record that as the new incoming edge to the terminus and enqueue that vertex
149
157
* to propagate updates.
150
158
*/
151
159
def relax (): Unit = {
@@ -155,7 +163,7 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
155
163
}
156
164
while (! queue.isEmpty) {
157
165
val v = dequeue()
158
- // if (debugging) println(s"deq ${names(v)}")
166
+ if (debugging) println(s " deq ${names(v)}" )
159
167
for (e <- adjacency(v)) {
160
168
val w = e.to
161
169
/* cannot happen as `runsRightAfter: Option[String]` is the only way to introduce a `FollowsNow`
@@ -167,12 +175,13 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
167
175
distance(w) = distance(v) + e.weight
168
176
edgeTo(w) = e
169
177
enqueue(w)
170
- // if (debugging) println(s"update ${namedEdge(e)} dist = ${distance(w)}, enq ${names(w)}")
178
+ if (debugging) println(s " update ${namedEdge(e)} dist = ${distance(w)}, enq ${names(w)}" )
171
179
}
172
180
}
173
181
}
174
- // if (debugging) edgeTo.foreach(e => println(namedEdge(e)))
182
+ if (debugging) edgeTo.foreach(e => println(namedEdge(e)))
175
183
}
184
+
176
185
/** Put the vertices in a linear order.
177
186
*
178
187
* `Follows` edges increase the level, `FollowsNow` don't.
@@ -189,8 +198,9 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
189
198
/* c.internal && !d.internal ||*/ c.phaseName.compareTo(d.phaseName) < 0
190
199
def sortVertex (i : Int , j : Int ): Boolean = sortComponents(componentOf(i), componentOf(j))
191
200
192
- distance.zipWithIndex.groupBy(_._1).toList.sortBy(_._1)
193
- .flatMap { case (_, dis) =>
201
+ val di = distance.zipWithIndex // pairs (distance, node)
202
+ di.groupBy(_._1).toArray.sortBy(_._1).iterator // group by distance, sorted by distance
203
+ .flatMap { case (d@ _, dis) =>
194
204
val vs = dis.map { case (_, i) => i }
195
205
val (anchors, followers) = vs.partition(v => edgeTo(v) == null || edgeTo(v).weight != FollowsNow )
196
206
// if (debugging) println(s"d=$d, anchors=${anchors.toList.map(n => names(n))}, followers=${followers.toList.map(n => names(n))}")
@@ -208,7 +218,7 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
208
218
ends.foreach(drill(_, Nil ))
209
219
chains.sortWith((p, q) => sortVertex(p(0 ), q(0 ))).toList.flatten.map(componentOf)
210
220
}
211
- }
221
+ }.toList
212
222
}
213
223
validate()
214
224
relax()
@@ -224,39 +234,92 @@ object DependencyGraph {
224
234
final val Parser = " parser"
225
235
final val Terminal = " terminal"
226
236
237
+ private val compilerPhases = List (
238
+ " parser" , " namer" , " packageobjects" , " typer" , " superaccessors" , " extmethods" , " pickler" ,
239
+ " refchecks" , " patmat" , " uncurry" , " fields" , " tailcalls" , " specialize" , " explicitouter" ,
240
+ " erasure" , " posterasure" , " lambdalift" , " constructors" , " flatten" , " mixin" , " cleanup" ,
241
+ " delambdafy" , " jvm" , " terminal" ,
242
+ )
243
+
227
244
/** Create a DependencyGraph from the given phases. The graph must be acyclic.
228
245
*
229
246
* A component must be declared as "initial".
230
247
* If no phase is "initial" but a phase is named "parser", it is taken as initial.
231
248
* If no phase is "terminal" but a phase is named "terminal", it is taken as terminal.
232
- * Empty constraints are ignored.
249
+ * A component phase must declare what phase it follows (runsAfter or runsRightAfter).
250
+ * If there is no such phase in this run, the component is omitted with a warning.
251
+ * The component may specify which phase it must precede (runsBefore), but if there is
252
+ * no such phase in this run, that constraint is silently ignored.
253
+ * Apparent typos of phase names incur warnings.
233
254
*/
234
- def apply (phases : Iterable [SubComponent ]): DependencyGraph = {
255
+ def apply (components : Iterable [SubComponent ], warnAll : Boolean = true ): DependencyGraph = {
256
+ val phases = components.toArray
257
+ val phaseNames = components.iterator.map(_.phaseName).toSet
258
+ def isPhaseName (s : String ): Boolean = phaseNames.contains(s)
235
259
val start = phases.find(_.initial)
236
260
.orElse(phases.find(_.phaseName == Parser ))
237
261
.getOrElse(throw new AssertionError (" Missing initial component" ))
238
262
val end = phases.find(_.terminal)
239
263
.orElse(phases.find(_.phaseName == Terminal ))
240
264
.getOrElse(throw new AssertionError (" Missing terminal component" ))
241
- new DependencyGraph (phases.size, start.phaseName, phases.map(p => p.phaseName -> p).toMap).tap { graph =>
242
- for (p <- phases) {
243
- require(p.phaseName.nonEmpty, " Phase name must be non-empty." )
244
- def checkConstraint (name : String , constraint : String ): Boolean =
245
- graph.components.contains(name).tap(ok => if (! ok) graph.warning(s " No phase ` $name` for ${p.phaseName}. $constraint" ))
246
- for (after <- p.runsRightAfter if after.nonEmpty && checkConstraint(after, " runsRightAfter" ))
247
- graph.addEdge(after, p.phaseName, FollowsNow )
248
- for (after <- p.runsAfter if after.nonEmpty && ! p.runsRightAfter.contains(after) && checkConstraint(after, " runsAfter" ))
249
- graph.addEdge(after, p.phaseName, Follows )
250
- for (before <- p.runsBefore if before.nonEmpty && checkConstraint(before, " runsBefore" ))
251
- graph.addEdge(p.phaseName, before, Follows )
252
- def noPhase (s : String ): Boolean = s.isEmpty || ! graph.components.contains(s)
253
- if (p != start && p != end)
254
- if (p.runsRightAfter.forall(noPhase) && p.runsAfter.forall(noPhase))
255
- graph.addEdge(start.phaseName, p.phaseName, Follows )
256
- if (p != end || p == end && p == start)
257
- if (p.runsBefore.forall(noPhase))
258
- graph.addEdge(p.phaseName, end.phaseName, Follows )
265
+ val constraints = ListBuffer .empty[(String , String , Weight )]
266
+ def addConstraint (from : String , to : String , weight : Weight ): Unit = constraints.addOne((from, to, weight))
267
+ val warnings = ListBuffer .empty[String ]
268
+ var i = 0
269
+ while (i < phases.length) {
270
+ val p = phases(i)
271
+ require(p.phaseName.nonEmpty, " Phase name must be non-empty." )
272
+ var constrained = false
273
+ var badConstraint = false
274
+ def checkConstraint (name : String , constraint : String , warn : Boolean ): Boolean = isPhaseName(name).tap { ok =>
275
+ if (! ok && warn) {
276
+ val help = if (compilerPhases.contains(name)) " " else
277
+ compilerPhases.filter(util.EditDistance .levenshtein(name, _) < 3 ) match {
278
+ case Nil => " "
279
+ case close => s " - did you mean ${util.StringUtil .oxford(close, " or" )}? "
280
+ }
281
+ warnings.addOne(s " No phase ` $name` for ${p.phaseName}. $constraint$help" )
282
+ badConstraint = true
283
+ }
259
284
}
285
+ // add edges for each constraint
286
+ for (after <- p.runsRightAfter if after.nonEmpty && checkConstraint(after, " runsRightAfter" , warn= true )) {
287
+ addConstraint(after, p.phaseName, FollowsNow )
288
+ constrained = true
289
+ }
290
+ for (after <- p.runsAfter if after.nonEmpty && ! p.runsRightAfter.contains(after) && checkConstraint(after, " runsAfter" , warn= true )) {
291
+ addConstraint(after, p.phaseName, Follows )
292
+ constrained = true
293
+ }
294
+ for (before <- p.runsBefore if before.nonEmpty && checkConstraint(before, " runsBefore" , warn= warnAll)) {
295
+ addConstraint(p.phaseName, before, Follows )
296
+ constrained = true
297
+ }
298
+ // if it doesn't follow a phase in the run, replace this component with a no-op that follows start
299
+ if ((! constrained || badConstraint) && p != start && p != end) {
300
+ phases(i) = new SubComponent {
301
+ val global = p.global
302
+ val phaseName = p.phaseName
303
+ val runsAfter = start.phaseName :: Nil
304
+ val runsRightAfter = None
305
+ def newPhase (prev : Phase ): Phase = new Phase (prev) {
306
+ val name = phaseName
307
+ def run () = ()
308
+ }
309
+ }
310
+ addConstraint(start.phaseName, p.phaseName, Follows )
311
+ if (warnAll) warnings.addOne(s " Component ${p.phaseName} dropped due to bad or missing constraint " )
312
+ }
313
+ // terminal follows every phase; parser is not before every phase because traverse uses edgeTo to find "anchors"
314
+ if (p != end || p == end && p == start)
315
+ addConstraint(p.phaseName, end.phaseName, Follows )
316
+ i += 1
317
+ }
318
+ new DependencyGraph (phases.size, start.phaseName, phases.iterator.map(p => p.phaseName -> p).toMap).tap { graph =>
319
+ for ((from, to, weight) <- constraints)
320
+ graph.addEdge(from, to, weight)
321
+ for (warning <- warnings)
322
+ graph.warning(warning)
260
323
}
261
324
}
262
325
0 commit comments