12
12
13
13
package scala .tools .nsc
14
14
15
- import java .util .concurrent .atomic .AtomicInteger
16
15
import scala .collection .mutable , mutable .ArrayDeque , mutable .ListBuffer
17
16
import scala .reflect .io .{File , Path }
18
17
import scala .util .chaining ._
@@ -33,7 +32,7 @@ trait PhaseAssembly {
33
32
phasesSet.add(terminal)
34
33
reporter.warning(NoPosition , " Added default terminal phase" )
35
34
}
36
- val graph = DependencyGraph (phasesSet)
35
+ val graph = DependencyGraph (phasesSet, warnAll = ! settings.isScaladoc || settings.isDebug )
37
36
for (n <- settings.genPhaseGraph.valueSetByUser; d <- settings.outputDirs.getSingleOutput if ! d.isVirtual)
38
37
DependencyGraph .graphToDotFile(graph, Path (d.file) / File (s " $n.dot " ))
39
38
graph.compilerPhaseList().tap(_ => graph.warnings.foreach(msg => reporter.warning(NoPosition , msg)))
@@ -44,10 +43,14 @@ trait PhaseAssembly {
44
43
*
45
44
* Each vertex is labeled with its phase name.
46
45
*/
47
- class DependencyGraph (order : Int , start : String , val components : Map [String , SubComponent ]) {
48
- import DependencyGraph .{FollowsNow , Weight }
46
+ class DependencyGraph (start : String , val components : Map [String , SubComponent ]) {
47
+ import DependencyGraph .{FollowsNow , Weight , weightedArrow }
49
48
50
- // private final val debugging = false
49
+ require(components.contains(start), s " Start $start is not a component in ${components.keys.mkString(" , " )}" )
50
+
51
+ private final val debugging = false
52
+
53
+ private val order = components.size
51
54
52
55
private var messages : List [String ] = Nil
53
56
def warning (message : String ): Unit = messages ::= message
@@ -60,32 +63,29 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
60
63
private case class Edge (from : Int , to : Int , weight : Weight )
61
64
62
65
// phase names and their vertex index
63
- private val nodeCount = new AtomicInteger
64
- private val nodes = mutable.HashMap .empty[String , Int ] // name to index
65
- private val names = Array .ofDim[String ](order) // index to name
66
+ private val names = components.keys.toArray
67
+ private val nodes = names.zipWithIndex.toMap
66
68
67
69
/** Add the edge between named phases, where `to` follows `from`.
68
70
*/
69
71
private def addEdge (from : String , to : String , weight : Weight ): Unit = {
70
- def getNode (name : String ): Int = {
71
- def installName (name : String , n : Int ): Unit =
72
- if (n >= names.length) throw new FatalError (names.mkString(s " Extra name $name; names [ " ," ," ," ]" ))
73
- else names(n) = name
74
- nodes.getOrElseUpdate(name, nodeCount.getAndIncrement().tap(installName(name, _)))
75
- }
76
- val v = getNode(from)
77
- val w = getNode(to)
72
+ val v = nodes(from)
73
+ val w = nodes(to)
74
+ if (debugging) println(s " add edge $from ${weightedArrow(weight)} $to" )
78
75
adjacency(v).find(_.to == w) match {
79
76
case None =>
80
77
adjacency(v) ::= Edge (from = v, to = w, weight)
81
78
case Some (_) if weight == FollowsNow => // retain runsRightAfter if there is a competing constraint
82
79
adjacency(v) = Edge (from = v, to = w, weight) :: adjacency(v).filterNot(_.to == w)
83
- case _ =>
80
+ case _ => // ignore duplicate
84
81
}
85
82
}
86
83
87
- // input must be acyclic and only one FollowsNow edge is allowed between a pair of vertices
88
- private def validate (): Unit = {
84
+ /** Find unreachable vertices.
85
+ *
86
+ * Input must be acyclic and a vertex can have only one outgoing FollowsNow edge.
87
+ */
88
+ private def validated (): List [String ] = {
89
89
def checkFollowsNow (v : Int ): Unit =
90
90
adjacency(v).foldLeft(- 1 ) { (w, e) =>
91
91
if (e.weight != FollowsNow ) w
@@ -122,9 +122,14 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
122
122
}
123
123
}
124
124
walk()
125
+ val unseen = ListBuffer .empty[String ]
126
+ for (i <- 0 .until(order) if ! seen(i))
127
+ unseen.addOne(names(i))
128
+ unseen.toList
125
129
}
126
130
127
131
def compilerPhaseList (): List [SubComponent ] = if (order == 1 ) List (components(start)) else {
132
+
128
133
// distance from source to each vertex
129
134
val distance = Array .fill[Int ](order)(Int .MinValue )
130
135
@@ -141,11 +146,13 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
141
146
142
147
def dequeue (): Int = queue.dequeue().tap(v => enqueued(v) = false )
143
148
144
- // def namedEdge(e: Edge): String = if (e == null) "[no edge]" else s"${names(e.from)} ${if (e.weight == FollowsNow) "=" else "-"}> ${names(e.to)}"
149
+ def namedEdge (e : Edge ): String =
150
+ if (e == null ) " [no edge]"
151
+ else s " ${names(e.from)}< ${distance(e.from)}> ${weightedArrow(e.weight)} ${names(e.to)}< ${distance(e.to)}> "
145
152
146
153
/** 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
154
+ * if an edge improves (increases) the distance at the terminus of the edge ,
155
+ * record that as the new incoming edge to the terminus and enqueue that vertex
149
156
* to propagate updates.
150
157
*/
151
158
def relax (): Unit = {
@@ -155,7 +162,7 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
155
162
}
156
163
while (! queue.isEmpty) {
157
164
val v = dequeue()
158
- // if (debugging) println(s"deq ${names(v)}")
165
+ if (debugging) println(s " deq ${names(v)}" )
159
166
for (e <- adjacency(v)) {
160
167
val w = e.to
161
168
/* cannot happen as `runsRightAfter: Option[String]` is the only way to introduce a `FollowsNow`
@@ -167,15 +174,16 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
167
174
distance(w) = distance(v) + e.weight
168
175
edgeTo(w) = e
169
176
enqueue(w)
170
- // if (debugging) println(s"update ${namedEdge(e)} dist = ${distance(w)}, enq ${names(w)}")
177
+ if (debugging) println(s " update ${namedEdge(e)} dist = ${distance(w)}, enq ${names(w)}" )
171
178
}
172
179
}
173
180
}
174
- // if (debugging) edgeTo.foreach(e => println(namedEdge(e)))
181
+ if (debugging) edgeTo.foreach(e => println(namedEdge(e)))
175
182
}
183
+
176
184
/** Put the vertices in a linear order.
177
185
*
178
- * `Follows` edges increase the level, `FollowsNow` don't.
186
+ * `Follows` edges increase the " level" , `FollowsNow` don't.
179
187
* Partition by "level" or distance from start.
180
188
* Partition the level into "anchors" that follow a node in the previous level, and "followers" (nodes
181
189
* with a `FollowsNow` edge).
@@ -189,13 +197,14 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
189
197
/* c.internal && !d.internal ||*/ c.phaseName.compareTo(d.phaseName) < 0
190
198
def sortVertex (i : Int , j : Int ): Boolean = sortComponents(componentOf(i), componentOf(j))
191
199
192
- distance.zipWithIndex.groupBy(_._1).toList.sortBy(_._1)
193
- .flatMap { case (_, dis) =>
194
- val vs = dis.map { case (_, i) => i }
200
+ val res = ListBuffer .empty[SubComponent ]
201
+ val levels = distance.zipWithIndex.groupBy(_._1) // distance -> (distance, node)
202
+ for (level <- levels.keysIterator.filter(_ >= 0 ).toArray.sorted) {
203
+ val vs = levels(level).map(_._2)
195
204
val (anchors, followers) = vs.partition(v => edgeTo(v) == null || edgeTo(v).weight != FollowsNow )
196
- // if (debugging) println(s"d=$d , anchors=${anchors.toList. map(n => names(n))} , followers=${followers.toList. map(n => names(n)) }")
205
+ if (debugging) println(s " d= $level , anchors={ ${anchors.map(names(_)).mkString( " , " )} } , followers={ ${followers.map(names(_)).mkString( " , " )} } " )
197
206
if (followers.isEmpty)
198
- anchors.toList .map(componentOf).sortWith(sortComponents)
207
+ anchors.toArray .map(componentOf).sortWith(sortComponents).foreach(res.addOne(_) )
199
208
else {
200
209
val froms = followers.map(v => edgeTo(v).from).toSet
201
210
val ends = followers.iterator.filterNot(froms).toList
@@ -206,11 +215,13 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
206
215
case _ => chains.find(_.apply(0 ) == v).foreach(deque => path.foreach(deque.append))
207
216
}
208
217
ends.foreach(drill(_, Nil ))
209
- chains.sortWith((p, q) => sortVertex(p(0 ), q(0 ))).toList.flatten.map(componentOf)
218
+ for (chain <- chains.sortWith((p, q) => sortVertex(p(0 ), q(0 ))); v <- chain)
219
+ res.addOne(componentOf(v))
210
220
}
211
221
}
222
+ res.toList
212
223
}
213
- validate( )
224
+ for (p <- validated()) warning( s " Phase $p is not reachable from $start " )
214
225
relax()
215
226
traverse()
216
227
}
@@ -220,42 +231,91 @@ object DependencyGraph {
220
231
type Weight = Int
221
232
final val FollowsNow = 0
222
233
final val Follows = 1
234
+ def weightedArrow (w : Weight ) = w match {
235
+ case FollowsNow => " =>"
236
+ case Follows => " ->"
237
+ case _ => " ?>"
238
+ }
223
239
224
240
final val Parser = " parser"
225
241
final val Terminal = " terminal"
226
242
243
+ private val compilerPhases = List (
244
+ " parser" , " namer" , " packageobjects" , " typer" , " superaccessors" , " extmethods" , " pickler" ,
245
+ " refchecks" , " patmat" , " uncurry" , " fields" , " tailcalls" , " specialize" , " explicitouter" ,
246
+ " erasure" , " posterasure" , " lambdalift" , " constructors" , " flatten" , " mixin" , " cleanup" ,
247
+ " delambdafy" , " jvm" , " terminal" ,
248
+ )
249
+
227
250
/** Create a DependencyGraph from the given phases. The graph must be acyclic.
228
251
*
229
252
* A component must be declared as "initial".
230
253
* If no phase is "initial" but a phase is named "parser", it is taken as initial.
231
254
* If no phase is "terminal" but a phase is named "terminal", it is taken as terminal.
232
- * Empty constraints are ignored.
255
+ *
256
+ * If a component declares that it follows (runsAfter or runsRightAfter)
257
+ * a phase that is not named in this run, then the component is omitted.
258
+ *
259
+ * If a component declares that it precedes (runsBefore)
260
+ * a phase that is not named in this run, then the constraint is dropped.
261
+ *
262
+ * Therefore, the graph contains only edges between the input components.
263
+ *
264
+ * On construction (compilerPhaseList), unreachable components will incur warnings.
265
+ *
266
+ * Apparent typos of phase names incur warnings.
233
267
*/
234
- def apply (phases : Iterable [SubComponent ]): DependencyGraph = {
268
+ def apply (components : Iterable [SubComponent ], warnAll : Boolean = true ): DependencyGraph = {
269
+ val warnings = ListBuffer .empty[String ]
270
+ val phases = components.iterator.filter { p =>
271
+ p.phaseName.nonEmpty.tap(b => if (! b) warnings.addOne(s " Dropped component with empty name (class ${p.getClass}) " ))
272
+ }.toArray
273
+ val phaseNames = phases.iterator.map(_.phaseName).toSet
274
+ def isPhaseName (s : String ): Boolean = phaseNames.contains(s)
235
275
val start = phases.find(_.initial)
236
276
.orElse(phases.find(_.phaseName == Parser ))
237
277
.getOrElse(throw new AssertionError (" Missing initial component" ))
238
278
val end = phases.find(_.terminal)
239
279
.orElse(phases.find(_.phaseName == Terminal ))
240
280
.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
- if (p != start && p != end)
253
- if (p.runsRightAfter.forall(_.isEmpty) && p.runsAfter.forall(_.isEmpty))
254
- graph.addEdge(start.phaseName, p.phaseName, Follows )
255
- if (p != end || p == end && p == start)
256
- if (p.runsBefore.forall(_.isEmpty))
257
- graph.addEdge(p.phaseName, end.phaseName, Follows )
281
+ var dropped = Set .empty[String ]
282
+ val constraints = ListBuffer .empty[(String , String , Weight )]
283
+ def addConstraint (from : String , to : String , weight : Weight ): Unit = constraints.addOne((from, to, weight))
284
+ def checkConstraint (p : SubComponent )(name : String , constraint : String , warn : Boolean ): Boolean = isPhaseName(name) || {
285
+ if (warn) {
286
+ val knownNames = phaseNames ++ compilerPhases
287
+ val help = if (knownNames.contains(name)) " " else
288
+ knownNames.filter(util.EditDistance .levenshtein(name, _) < 3 ).toList match {
289
+ case Nil => " "
290
+ case close => s " - did you mean ${util.StringUtil .oxford(close, " or" )}? "
291
+ }
292
+ warnings.addOne(s " No phase ` $name` for ${p.phaseName}. $constraint$help" )
258
293
}
294
+ false
295
+ }
296
+ for ((p, i) <- phases.zipWithIndex) {
297
+ for (after <- p.runsRightAfter if after.nonEmpty)
298
+ if (checkConstraint(p)(after, " runsRightAfter" , warn= warnAll)) addConstraint(after, p.phaseName, FollowsNow )
299
+ else dropped += p.phaseName
300
+ for (after <- p.runsAfter if after.nonEmpty && ! p.runsRightAfter.contains(after))
301
+ if (checkConstraint(p)(after, " runsAfter" , warn= warnAll)) addConstraint(after, p.phaseName, Follows )
302
+ else dropped += p.phaseName
303
+ for (before <- p.runsBefore if before.nonEmpty)
304
+ if (checkConstraint(p)(before, " runsBefore" , warn= warnAll)) addConstraint(p.phaseName, before, Follows )
305
+ // terminal follows every phase; parser is not before every phase because traverse uses edgeTo to find "anchors"
306
+ if (p != end || p == end && p == start)
307
+ addConstraint(p.phaseName, end.phaseName, Follows )
308
+ }
309
+ if (warnAll)
310
+ dropped.foreach(p => warnings.addOne(s " Component ${p} dropped due to bad constraint " ))
311
+ val purgedConstraints = constraints.filterInPlace {
312
+ case (from, to, w) => ! dropped.contains(from) && ! dropped.contains(to)
313
+ }.toList
314
+ new DependencyGraph (start.phaseName, phases.iterator.map(p => p.phaseName -> p).toMap).tap { graph =>
315
+ for ((from, to, weight) <- purgedConstraints)
316
+ graph.addEdge(from, to, weight)
317
+ for (warning <- warnings)
318
+ graph.warning(warning)
259
319
}
260
320
}
261
321
0 commit comments