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