diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index d186ad5c33d4..1a7ed96e14e6 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -579,7 +579,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter) // phaseName = "specialize" object specializeTypes extends { val global: Global.this.type = Global.this - val runsAfter = List("") + val runsAfter = Nil val runsRightAfter = Some("tailcalls") } with SpecializeTypes @@ -593,14 +593,14 @@ class Global(var currentSettings: Settings, reporter0: Reporter) // phaseName = "erasure" override object erasure extends { val global: Global.this.type = Global.this - val runsAfter = List("explicitouter") + val runsAfter = Nil val runsRightAfter = Some("explicitouter") } with Erasure // phaseName = "posterasure" override object postErasure extends { val global: Global.this.type = Global.this - val runsAfter = List("erasure") + val runsAfter = Nil val runsRightAfter = Some("erasure") } with PostErasure @@ -665,7 +665,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter) val global: Global.this.type = Global.this } with SubComponent { val phaseName = "terminal" - val runsAfter = List("jvm") + val runsAfter = Nil val runsRightAfter = None override val terminal = true @@ -694,10 +694,10 @@ class Global(var currentSettings: Settings, reporter0: Reporter) /** Add the internal compiler phases to the phases set. * This implementation creates a description map at the same time. */ - protected def computeInternalPhases(): Unit = { + protected def computeInternalPhases(): Unit = // Note: this fits -Xshow-phases into 80 column width, which is // desirable to preserve. - val phs = List( + List( syntaxAnalyzer -> "parse source into ASTs, perform simple desugaring", analyzer.namerFactory -> "resolve names, attach symbols to named trees", analyzer.packageObjects -> "load package objects", @@ -722,9 +722,8 @@ class Global(var currentSettings: Settings, reporter0: Reporter) cleanup -> "platform-specific cleanups, generate reflective calls", terminal -> "the last phase during a compilation run" ) + .foreach((addToPhasesSet _).tupled) - phs foreach (addToPhasesSet _).tupled - } // This is slightly inelegant but it avoids adding a new member to SubComponent, // and attractive -Vphases output is unlikely if the descs span 20 files anyway. private val otherPhaseDescriptions = Map( @@ -732,9 +731,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter) "jvm" -> "generate JVM bytecode" ) withDefaultValue "" - protected def computePlatformPhases() = platform.platformPhases foreach { sub => - addToPhasesSet(sub, otherPhaseDescriptions(sub.phaseName)) - } + protected def computePlatformPhases() = platform.platformPhases.foreach(p => addToPhasesSet(p, otherPhaseDescriptions(p.phaseName))) // sequences the phase assembly protected def computePhaseDescriptors: List[SubComponent] = { @@ -767,7 +764,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter) /** The names of the phases. */ lazy val phaseNames = { new Run // force some initialization - phaseDescriptors map (_.phaseName) + phaseDescriptors.map(_.phaseName) } /** A description of the phases that will run in this configuration, or all if -Vdebug. */ @@ -1279,10 +1276,13 @@ class Global(var currentSettings: Settings, reporter0: Reporter) } else phs } // Create phases and link them together. We supply the previous, and the ctor sets prev.next. - val last = components.foldLeft(NoPhase: Phase)((prev, c) => c newPhase prev) - val phaseList = Iterator.iterate(last)(_.prev).takeWhile(_ != NoPhase).toList.reverse - val maxId = phaseList.map(_.id).max - nextFrom = Array.tabulate(maxId)(i => infoTransformers.nextFrom(i)) + val phaseList = { + val last = components.foldLeft(NoPhase: Phase)((prev, c) => c.newPhase(prev)) + Iterator.iterate(last)(_.prev).takeWhile(_ != NoPhase).toList.reverse + } + nextFrom = Array.tabulate(phaseList.maxBy(_.id).id)(infoTransformers.nextFrom(_)) + //println(s"nextFrom: ${scala.runtime.ScalaRunTime.stringOf(nextFrom.map(_.pid))}") + //println(s"phaseList: ${scala.runtime.ScalaRunTime.stringOf(phaseList.map(_.name))}") val first = phaseList.head val ss = settings diff --git a/src/compiler/scala/tools/nsc/PhaseAssembly.scala b/src/compiler/scala/tools/nsc/PhaseAssembly.scala index 3dbf95cab19a..0ccf7c561f69 100644 --- a/src/compiler/scala/tools/nsc/PhaseAssembly.scala +++ b/src/compiler/scala/tools/nsc/PhaseAssembly.scala @@ -12,271 +12,228 @@ package scala.tools.nsc -import scala.collection.mutable +import scala.collection.mutable, mutable.ArrayDeque, mutable.ListBuffer import scala.reflect.io.{File, Path} -import scala.tools.nsc.Reporting.WarningCategory +import scala.util.chaining._ -import DependencyGraph.Messaging - -/** Converts an unordered morass of components into an order that - * satisfies their mutual constraints. +/** Sorts the global phasesSet according to SubComponent constraints. */ trait PhaseAssembly { - self: Global => + this: Global => /** Called by Global#computePhaseDescriptors to compute phase order. */ def computePhaseAssembly(): List[SubComponent] = { - - implicit val messaging: Messaging = Messaging(informProgress, - runReporting.warning(NoPosition, _, WarningCategory.Other, site=""), - globalError, - (g, f) => for (d <- settings.outputDirs.getSingleOutput if !d.isVirtual) DependencyGraph.graphToDotFile(g, Path(d.file) / File(f)) - ) - val graph = DependencyGraph(phasesSet) - - // Output the phase dependency graph at this stage - def dump(stage: Int) = settings.genPhaseGraph.valueSetByUser.foreach(n => graph.dump(s"$n-$stage.dot")) - - dump(1) - - // Remove nodes without phaseobj - graph.removeDanglingNodes() - - dump(2) - - // Validate and Enforce hardlinks / runsRightAfter and promote nodes down the tree - graph.validateAndEnforceHardlinks() - - dump(3) - - // test for cycles, assign levels and collapse hard links into nodes - graph.collapseHardLinksAndLevels(graph.getNodeByPhase("parser"), 1) - - dump(4) - + for (n <- settings.genPhaseGraph.valueSetByUser; d <- settings.outputDirs.getSingleOutput if !d.isVirtual) + DependencyGraph.graphToDotFile(graph, Path(d.file) / File(s"$n.dot")) graph.compilerPhaseList() } } -/** - * Aux data structure for solving the constraint system - * The dependency graph container with helper methods for node and edge creation +/** A graph with the given number of vertices. */ -private class DependencyGraph { +class DependencyGraph(order: Int, val components: Map[String, SubComponent]) { + import DependencyGraph.{FollowsNow, Start, Weight} - /** Simple edge with to and from refs */ - case class Edge(var frm: Node, var to: Node, var hard: Boolean) + //private final val debugging = false - /** - * Simple node with name and object ref for the phase object, - * also sets of incoming and outgoing dependencies. - */ - case class Node(phasename: String) { - var phaseobj = Option.empty[List[SubComponent]] - var before = new mutable.HashSet[Edge]() - val after = new mutable.HashSet[Edge]() - var visited = false - var level = 0 - - def allPhaseNames: String = phaseobj match { - case None => phasename - case Some(lst) => lst.map(_.phaseName).mkString(",") - } - } + /** Number of edges. */ + //private var size = 0 - val nodes = new mutable.HashMap[String, Node]() - val edges = new mutable.HashSet[Edge]() + /** For ith vertex, its outgoing edges. */ + private val adjacency: Array[List[Edge]] = Array.fill(order)(Nil) - /** Given the name of a phase object, get the node for that name. - * If the node object does not exist, then create it. - */ - def getNodeByPhase(name: String): Node = nodes.getOrElseUpdate(name, Node(name)) - def getNodeByPhase(phs: SubComponent): Node = { - val node: Node = getNodeByPhase(phs.phaseName) - if (node.phaseobj.isEmpty) - node.phaseobj = Some(List(phs)) - node - } + /** For ith vertex, the count of its incoming edges. */ + //private val inDegree: Array[Int] = Array.ofDim(order) - def softConnectNodes(frm: Node, to: Node) = connectNodes(Edge(frm, to, hard = false)) - def hardConnectNodes(frm: Node, to: Node) = connectNodes(Edge(frm, to, hard = true)) + /** Directed edge. */ + private case class Edge(from: Int, to: Int, weight: Weight) - // Connect the frm and to nodes in the edge and add it to the set of edges. - private def connectNodes(e: Edge): Unit = { - edges += e - e.frm.after += e - e.to.before += e - } + // phase names and their vertex index + private val nodeCount = new java.util.concurrent.atomic.AtomicInteger + private val nodes = mutable.HashMap.empty[String, Int] + private val names = Array.ofDim[String](order) - /* Given the entire graph, collect the phase objects at each level, where the phase - * names are sorted alphabetical at each level, into the compiler phase list + /** Add the edge between named phases, where `to` follows `from`. */ - def compilerPhaseList(): List[SubComponent] = - nodes.values.toList.filter(_.level > 0).sortBy(x => (x.level, x.phasename)).flatMap(_.phaseobj).flatten - - // Test for graph cycles, assign levels to the nodes & collapse hard links into nodes. - def collapseHardLinksAndLevels(node: Node, lvl: Int)(implicit messaging: Messaging): Unit = { - if (node.visited) { - dump("phase-cycle") - throw new FatalError(s"Cycle in phase dependencies detected at ${node.phasename}, created phase-cycle.dot") + private def addEdge(from: String, to: String, weight: Weight): Unit = { + def getNode(name: String): Int = { + def installName(name: String, n: Int): Unit = + if (n >= names.length) throw new FatalError(names.mkString(s"Extra name $name; names [",",","]")) + else names(n) = name + nodes.getOrElseUpdate(name, nodeCount.getAndIncrement().tap(installName(name, _))) } - - if (node.level < lvl) - node.level = lvl // level up - else if (node.level != 0) { - node.visited = false - return // no need to revisit + val v = getNode(from) + val w = getNode(to) + adjacency(v).find(e => e.from == v && e.to == w) match { + case None => + //inDegree(w) += 1 + //size += 1 + adjacency(v) ::= Edge(from = v, to = w, weight) + case Some(e) if weight == FollowsNow => // use runsRightAfter edge FollowsNow + adjacency(v) = Edge(from = v, to = w, weight) :: adjacency(v).filterNot(e => e.from == v && e.to == w) + case _ => } - - var hardlinks = node.before.filter(_.hard) - while (hardlinks.nonEmpty) { - for (hl <- hardlinks) { - node.phaseobj = Some(node.phaseobj.get ++ hl.frm.phaseobj.get) - node.before = hl.frm.before - nodes -= hl.frm.phasename - edges -= hl - for (edge <- node.before) - edge.to = node - } - hardlinks = node.before.filter(_.hard) - } - - node.visited = true - - for (edge <- node.before) { - collapseHardLinksAndLevels(edge.frm, lvl + 1) - } - - node.visited = false } - /* Find all edges in the given graph that are hard links. - * For each hard link we need to check that it's the only dependency. - * If not, then we will promote the other dependencies down. - */ - def validateAndEnforceHardlinks()(implicit messaging: Messaging): Unit = { - for (hl <- edges if hl.hard) { - if (hl.frm.after.sizeIs > 1) { - dump("phase-order") - throw new FatalError(s"Phase ${hl.frm.phasename} can't follow ${hl.to.phasename}, created phase-order.dot") + // input must be acyclic and only one FollowsNow edge is allowed between a pair of vertices + private def validate(): Unit = { + def checkFollowsNow(v: Int): Unit = + adjacency(v).foldLeft(-1) { (w, e) => + if (e.weight != FollowsNow) w + else if (w == -1) e.to + else throw new FatalError(s"Phases ${names(w)} and ${names(e.to)} both immediately follow ${names(v)}") } - } - - var rerun = true - while (rerun) { - rerun = false - for (hl <- edges if hl.hard) { - hl.to.before.filter(_.hard).toList match { - case Seq() => - throw new FatalError("There is no runs right after dependency, where there should be one! This is not supposed to happen!") - case asm @ (head :: _ :: _) => - dump("phase-order") - val following = asm.map(_.frm.phasename).sorted mkString "," - throw new FatalError(s"Multiple phases want to run right after ${head.to.phasename}; followers: $following; created phase-order.dot") - case asm => - val promote = hl.to.before.filter(e => !e.hard) - hl.to.before.clear() - asm.foreach(edge => hl.to.before += edge) - for (edge <- promote) { - rerun = true - val msg = s"promote the dependency of ${edge.frm.phasename}: ${edge.to.phasename} => ${hl.frm.phasename}" - messaging.informProgress(msg) - edge.to = hl.frm - hl.frm.before += edge + val seen = Array.ofDim[Boolean](order) + val onPath = Array.ofDim[Boolean](order) + val stack = mutable.Stack.empty[(Int, List[Edge])] // a vertex and list of edges remaining to follow + def walk(): Unit = { + nodes(Start).tap { start => + stack.push(start -> adjacency(start)) + } + while (!stack.isEmpty) { + val (v, edges) = stack.pop() + if (!seen(v)) { + checkFollowsNow(v) + seen(v) = true + } + onPath(v) = true + edges match { + case Edge(_, to, _) :: edges => + if (onPath(to)) { + var path = v :: to :: Nil + while (path.head != to) + path ::= stack.pop()._1 + throw new FatalError(s"Phases form a cycle: ${path.map(names(_)).mkString(" -> ")}") } + stack.push(v -> edges) + if (!seen(to)) + stack.push(to -> adjacency(to)) + case _ => onPath(v) = false } } } + walk() } - /** Remove all nodes in the given graph, that have no phase object. - * Make sure to clean up all edges when removing the node object. - * `Inform` with warnings, if an external phase has a dependency on something that is dropped. - */ - def removeDanglingNodes()(implicit messaging: Messaging): Unit = { - for (node <- nodes.values if node.phaseobj.isEmpty) { - val msg = s"dropping dependency on node with no phase object: ${node.phasename}" - messaging.informProgress(msg) - nodes -= node.phasename + def compilerPhaseList(): List[SubComponent] = { + // distance from source to each vertex + val distance = Array.fill[Int](order)(Int.MinValue) - for (edge <- node.before) { - edges -= edge - edge.frm.after -= edge - if (edge.frm.phaseobj exists (lsc => !lsc.head.internal)) - messaging.warning(msg) - } - } - } + // incoming edge terminating in each vertex for the current path + val edgeTo = Array.ofDim[Edge](order) - def dump(title: String)(implicit messaging: Messaging): Unit = messaging.dump(this, s"$title.dot") -} -private object DependencyGraph { + // whether vertex is on the queue + val enqueued = Array.ofDim[Boolean](order) - /** Given the phases set, will build a dependency graph from the phases set - * Using the aux. method of the DependencyGraph to create nodes and edges. - */ - def apply(phasesSet: Iterable[SubComponent])(implicit messaging: Messaging): DependencyGraph = { - val graph = new DependencyGraph + // vertices to process + val queue = mutable.Queue.empty[Int] - for (phs <- phasesSet) { - val fromnode = graph.getNodeByPhase(phs) + def enqueue(v: Int): Unit = if (!enqueued(v)) queue.enqueue(v).tap(_ => enqueued(v) = true) - phs.runsRightAfter match { - case None => - for (phsname <- phs.runsAfter) { - if (phsname != "terminal") { - val tonode = graph.getNodeByPhase(phsname) - graph.softConnectNodes(fromnode, tonode) - } else { - messaging.error(s"[phase assembly, after dependency on terminal phase not allowed: ${fromnode.phasename} => $phsname]") - } + def dequeue(): Int = queue.dequeue().tap(v => enqueued(v) = false) + + //def namedEdge(e: Edge): String = if (e == null) "[no edge]" else s"${names(e.from)} ${if (e.weight == FollowsNow) "=" else "-"}> ${names(e.to)}" + + def relax(): Unit = { + nodes(Start).tap { start => + distance(start) = 0 + enqueue(start) + } + while (!queue.isEmpty) { + val v = dequeue() + //if (debugging) println(s"deq ${names(v)}") + for (e <- adjacency(v)) { + val w = e.to + val e2 = edgeTo(w) + if (e.weight == FollowsNow && e2 != null && e2.weight == FollowsNow && e.from != e2.from) + throw new FatalError(s"${names(w)} cannot follow right after both ${names(e.from)} and ${names(e2.from)}") + if (distance(w) < distance(v) + e.weight) { + distance(w) = distance(v) + e.weight + edgeTo(w) = e + enqueue(w) + //if (debugging) println(s"update ${namedEdge(e)} dist = ${distance(w)}, enq ${names(w)}") } - for (phsname <- phs.runsBefore) { - if (phsname != "parser") { - val tonode = graph.getNodeByPhase(phsname) - graph.softConnectNodes(tonode, fromnode) - } else { - messaging.error(s"[phase assembly, before dependency on parser phase not allowed: $phsname => ${fromnode.phasename}]") + } + } + //if (debugging) edgeTo.foreach(e => println(namedEdge(e))) + } + def traverse(): List[SubComponent] = { + def componentOf(i: Int) = components(names(i)) + def sortComponents(c: SubComponent, d: SubComponent): Boolean = + c.internal && !d.internal || c.phaseName.compareTo(d.phaseName) < 0 + def sortVertex(i: Int, j: Int): Boolean = sortComponents(componentOf(i), componentOf(j)) + + distance.zipWithIndex.groupBy(_._1).toList.sortBy(_._1) + .flatMap { case (d, dis) => + val vs = dis.map { case (_, i) => i } + val (anchors, followers) = vs.partition(v => edgeTo(v) == null || distance(edgeTo(v).from) != d) + //if (debugging) println(s"d=$d, anchors=${anchors.toList.map(n => names(n))}, followers=${followers.toList.map(n => names(n))}") + if (followers.isEmpty) + anchors.toList.map(componentOf).sortWith(sortComponents) + else { + // find phases which are not the source of an edgeTo, then construct paths at this level distance + val froms = followers.map(v => edgeTo(v).from).toSet + val ends = followers.iterator.filterNot(froms).toList + val followed: Array[ArrayDeque[Int]] = anchors.map(ArrayDeque(_)) + def drill(v: Int, path: List[Int]): Unit = + edgeTo(v) match { + case e if e != null && distance(e.from) == d => drill(e.from, v :: path) + case _ => followed.find(_.apply(0) == v).foreach(deque => path.foreach(deque.append)) } - } - case Some(phsname) => - if (phsname != "terminal") { - val tonode = graph.getNodeByPhase(phsname) - graph.hardConnectNodes(fromnode, tonode) - } else { - messaging.error(s"[phase assembly, right after dependency on terminal phase not allowed: ${fromnode.phasename} => $phsname]") - } + ends.foreach(drill(_, Nil)) + followed.sortWith((p, q) => sortVertex(p(0), q(0))).toList.flatten.map(componentOf) + } } } - - graph + validate() + relax() + traverse() } +} +object DependencyGraph { - /* This is a helper method, that given a dependency graph will generate a graphviz dot - * file showing its structure. - * Plug-in supplied phases are marked as green nodes and hard links are marked as blue edges. - */ - def graphToDotFile(graph: DependencyGraph, file: File): Unit = { - val edges = graph.edges.toSeq - val extnodes = edges.map(_.frm).filter(!_.phaseobj.get.head.internal) - val fatnodes = edges.flatMap(e => List(e.frm, e.to)).filter(_.phaseobj.exists(_.sizeIs > 1)) + type Weight = Int + final val FollowsNow = 0 + final val Follows = 1 - def color(hex: String) = s""" [color="#$hex"]""" - def node(n: graph.Node) = s""""${n.allPhaseNames}(${n.level})"""" + final val Parser = "parser" + final val Start = Parser + final val Terminal = "terminal" - file.printlnAll("digraph G {") - file.printlnAll(edges.map(e => s"${node(e.frm)}->${node(e.to)}" + color(if (e.hard) "0000ff" else "000000")): _*) - file.printlnAll(extnodes.distinct.map(n => node(n) + color("00ff00")): _*) - file.printlnAll(fatnodes.distinct.map(n => node(n) + color("0000ff")): _*) - file.printlnAll("}") - } + /** Create a DependencyGraph from the given phases. + * The graph must be acyclic. + */ + def apply(phases: Iterable[SubComponent]): DependencyGraph = + new DependencyGraph(phases.size, phases.map(p => p.phaseName -> p).toMap).tap { graph => + for (p <- phases) { + val name = p.phaseName + require(!name.isEmpty, "Phase name must be non-empty.") + require(!p.runsRightAfter.exists(_.isEmpty), s"Phase $name has empty name for runsRightAfter.") + require(!p.runsAfter.exists(_.isEmpty), s"Phase $name has empty name for runsAfter.") + require(!p.runsBefore.exists(_.isEmpty), s"Phase $name has empty name for runsBefore.") + for (after <- p.runsRightAfter) graph.addEdge(after, name, FollowsNow) + for (after <- p.runsAfter.filterNot(p.runsRightAfter.contains)) graph.addEdge(after, name, Follows) + if (!p.initial && !p.terminal) + if (p.runsRightAfter.isEmpty && p.runsAfter.isEmpty) graph.addEdge(Start, name, Follows) + for (before <- p.runsBefore) graph.addEdge(name, before, Follows) + if (!p.terminal) + if (!p.runsBefore.contains(Terminal)) graph.addEdge(name, Terminal, Follows) + } + } - case class Messaging(informProgress: String => Unit, warning: String => Unit, error: String => Unit, dump: (DependencyGraph, String) => Unit) - object Messaging { - val silent = Messaging(_ => (), _ => (), _ => (), (_, _) => ()) - val stdout = Messaging(s => println(s), s => println(s), s => println(s), (_, _) => ()) - val throws = Messaging(s => fail(s), s => fail(s), s => fail(s), (_, _) => ()) - private def fail(s: String) = throw new Exception(s) + /** Emit a graphviz dot file for the graph. + * Plug-in supplied phases are marked as green nodes and hard links are marked as blue edges. + */ + def graphToDotFile(graph: DependencyGraph, file: File): Unit = { + def color(hex: String) = s""" [color="#$hex"]""" + val sb = ListBuffer.empty[String] + sb.addOne("digraph G {") + for (edges <- graph.adjacency; e <- edges) + sb.addOne(s"${graph.names(e.from)} -> ${graph.names(e.to)}${if (e.weight == FollowsNow) color("0000ff") else ""}") + for (n <- graph.names) + sb.addOne(s"${n}${if (graph.components(n).internal) "" else color("00ff00")}") + sb.addOne("}") + file.printlnAll(sb.toList: _*) } } diff --git a/src/reflect/scala/reflect/internal/InfoTransformers.scala b/src/reflect/scala/reflect/internal/InfoTransformers.scala index ed568eaaa42c..2120f1538c76 100644 --- a/src/reflect/scala/reflect/internal/InfoTransformers.scala +++ b/src/reflect/scala/reflect/internal/InfoTransformers.scala @@ -38,7 +38,7 @@ trait InfoTransformers { } else if (next.pid <= that.pid && next.pid != NoPhase.id) { next insert that } else { - log("Inserting info transformer %s following %s".format(phaseOf(that.pid), phaseOf(this.pid))) + log(s"Inserting info transformer ${phaseOf(that.pid)} following ${phaseOf(this.pid)}") that.next = next that.prev = this next.prev = that diff --git a/src/scaladoc/scala/tools/nsc/doc/DocParser.scala b/src/scaladoc/scala/tools/nsc/doc/DocParser.scala index e23b33e1f8c6..932a7bec5c47 100644 --- a/src/scaladoc/scala/tools/nsc/doc/DocParser.scala +++ b/src/scaladoc/scala/tools/nsc/doc/DocParser.scala @@ -29,7 +29,12 @@ class DocParser(settings: nsc.Settings, reporter: Reporter) extends Global(setti // the usual global initialization locally { new Run() } - override protected def computeInternalPhases(): Unit = phasesSet += syntaxAnalyzer + override protected def computePhaseDescriptors: List[SubComponent] = { + assert(phasesSet.isEmpty, "Scaladoc limits available phases") + phasesSet += syntaxAnalyzer + phasesSet += terminal + computePhaseAssembly() + } /** Returns a list of `DocParser.Parseds`, which hold the DocDefs found * in the given code along with the surrounding trees. diff --git a/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala b/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala index 3f1bbf1701fd..8a5a928fe2b2 100644 --- a/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala +++ b/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala @@ -30,6 +30,7 @@ trait ScaladocGlobalTrait extends Global { override lazy val syntaxAnalyzer = new ScaladocSyntaxAnalyzer[outer.type](outer) { val runsAfter = List[String]() val runsRightAfter = None + override val initial = true } override lazy val loaders = new { @@ -46,11 +47,14 @@ trait ScaladocGlobalTrait extends Global { // takes a `Reporter`, not `FilteringReporter` for sbt compatibility class ScaladocGlobal(settings: doc.Settings, reporter: Reporter) extends Global(settings, reporter) with ScaladocGlobalTrait { - override protected def computeInternalPhases(): Unit = { + override protected def computePhaseDescriptors: List[SubComponent] = { + assert(phasesSet.isEmpty, "Scaladoc limits available phases") phasesSet += syntaxAnalyzer phasesSet += analyzer.namerFactory phasesSet += analyzer.packageObjects phasesSet += analyzer.typerFactory + phasesSet += terminal + computePhaseAssembly() } override def createJavadoc = if (settings.docNoJavaComments.value) false else true diff --git a/test/benchmarks/src/main/scala/scala/tools/nsc/PhaseAssemblyBenchmark.scala b/test/benchmarks/src/main/scala/scala/tools/nsc/PhaseAssemblyBenchmark.scala index 6d9a5998efb5..ff77e6bf392d 100644 --- a/test/benchmarks/src/main/scala/scala/tools/nsc/PhaseAssemblyBenchmark.scala +++ b/test/benchmarks/src/main/scala/scala/tools/nsc/PhaseAssemblyBenchmark.scala @@ -18,26 +18,28 @@ class PhaseAssemblyBenchmark { class Data[G <: Global with Singleton](val global: G, val components: List[SubComponent { val global: G}]) var data: Data[_] = _ - @Param(Array("1", "4", "8", "16")) - var size: Int = 16 + case class component[G <: Global with Singleton]( + global: G, + phaseName: String, + override val runsRightAfter: Option[String], + override val runsAfter: List[String], + override val runsBefore: List[String], + ) extends SubComponent { + override val initial: Boolean = phaseName == "parser" + override val terminal: Boolean = phaseName == "terminal" + override def newPhase(prev: Phase): Phase = ??? + } + + @Param(Array("1", "4", "8", "16", "64")) + var size: Int = 64 @Setup def setup(): Unit = { val global = new Global(new Settings) - case class component[G <: Global with Singleton](val global: G, val phaseName: String, override val runsRightAfter: Option[String], override val runsAfter: List[String], override val runsBefore: List[String]) extends SubComponent { - override def newPhase(prev: Phase): Phase = ??? - - } - object component { - def apply(phaseName: String, runsRightAfter: Option[String], runsAfter: List[String], runsBefore: List[String]): component[global.type] = { - new component[global.type](global, phaseName, runsRightAfter, runsAfter, runsBefore) - } - } val N = size val components = List.tabulate(N){ i => - component(i.toString, None, if (i == 0) List("parser") else List.tabulate(2)(j => i - j - 1).filter(_ >= 0).map(_.toString), List("terminal")) - } ::: List(component("parser", None, Nil, Nil), component("terminal", None, Nil, List(N.toString))) - + component(global, i.toString, None, if (i == 0) List("parser") else List.tabulate(2)(j => i - j - 1).filter(_ >= 0).map(_.toString), List("terminal")) + } ::: List(component(global, "parser", None, Nil, Nil), component(global, "terminal", None, Nil, Nil)) data = new Data[global.type](global, components ) } @@ -45,12 +47,8 @@ class PhaseAssemblyBenchmark { @Benchmark def assemble(): Object = { val s = data.asInstanceOf[Data[Global with Singleton]] val g = s.global - implicit val messaging: DependencyGraph.Messaging = DependencyGraph.Messaging.silent val graph = DependencyGraph(s.components.reverse) - graph.removeDanglingNodes() - graph.validateAndEnforceHardlinks() - graph.collapseHardLinksAndLevels(graph.getNodeByPhase("parser"), 1) - graph + graph.compilerPhaseList() } } diff --git a/test/files/neg/t7494-after-terminal.check b/test/files/neg/t7494-after-terminal.check index de95de761dd3..efdf150eb06e 100644 --- a/test/files/neg/t7494-after-terminal.check +++ b/test/files/neg/t7494-after-terminal.check @@ -1,2 +1 @@ -error: [phase assembly, after dependency on terminal phase not allowed: afterterminal => terminal] -1 error +fatal error: Phases form a cycle: terminal -> afterterminal -> terminal diff --git a/test/files/neg/t7494-before-parser.check b/test/files/neg/t7494-before-parser.check index c5ba82faff81..4816ecca9886 100644 --- a/test/files/neg/t7494-before-parser.check +++ b/test/files/neg/t7494-before-parser.check @@ -1,2 +1 @@ -error: [phase assembly, before dependency on parser phase not allowed: parser => beforeparser] -1 error +fatal error: Phases form a cycle: parser -> beforeparser -> parser diff --git a/test/files/neg/t7494-multi-right-after.check b/test/files/neg/t7494-multi-right-after.check index 654f35fa156d..b5dd2b8f71d1 100644 --- a/test/files/neg/t7494-multi-right-after.check +++ b/test/files/neg/t7494-multi-right-after.check @@ -1 +1 @@ -fatal error: Multiple phases want to run right after explicitouter; followers: erasure,multi-rafter; created phase-order.dot +fatal error: Phases multi-rafter and erasure both immediately follow explicitouter diff --git a/test/files/neg/t7494-right-after-before.check b/test/files/neg/t7494-right-after-before.check index 06690b9112c1..695073496674 100644 --- a/test/files/neg/t7494-right-after-before.check +++ b/test/files/neg/t7494-right-after-before.check @@ -1 +1,27 @@ -fatal error: Phase erasure can't follow explicitouter, created phase-order.dot + phase name id description + ---------- -- ----------- + parser 1 parse source into ASTs, perform simple desugaring + namer 2 resolve names, attach symbols to named trees + packageobjects 3 load package objects + typer 4 the meat and potatoes: type the trees + superaccessors 5 add super accessors in traits and nested classes + extmethods 6 add extension methods for inline classes + pickler 7 serialize symbol tables + refchecks 8 reference/override checking, translate nested objects + patmat 9 translate match expressions +rafter-before-1 10 hey it works + uncurry 11 uncurry, translate function values to anonymous classes + fields 12 synthesize accessors and fields, add bitmaps for lazy vals + tailcalls 13 replace tail calls by jumps + specialize 14 @specialized-driven class and method specialization + explicitouter 15 this refs to outer pointers + erasure 16 erase types, add interfaces for traits + posterasure 17 clean up erased inline classes + lambdalift 18 move nested functions to top level + constructors 19 move field definitions into constructors + flatten 20 eliminate inner classes + mixin 21 mixin composition + cleanup 22 platform-specific cleanups, generate reflective calls + delambdafy 23 remove lambdas + jvm 24 generate JVM bytecode + terminal 25 the last phase during a compilation run diff --git a/test/files/neg/t7494-right-after-before/ThePlugin.scala b/test/files/neg/t7494-right-after-before/ThePlugin.scala index 967c1fb30705..cc4670b2fe66 100644 --- a/test/files/neg/t7494-right-after-before/ThePlugin.scala +++ b/test/files/neg/t7494-right-after-before/ThePlugin.scala @@ -10,11 +10,12 @@ class ThePlugin(val global: Global) extends Plugin { import global._ val name = "rafter-before-1" - val description = "" + val description = "hey it works" val components = List[PluginComponent](thePhase1) private object thePhase1 extends PluginComponent { val global = ThePlugin.this.global + override def description = ThePlugin.this.description val runsAfter = List[String]("refchecks") override val runsBefore = List[String]("erasure") diff --git a/test/files/neg/t7494-right-after-before/sample_2.scala b/test/files/neg/t7494-right-after-before/sample_2.scala index ebe48c4e8911..bc9a8d934c1f 100644 --- a/test/files/neg/t7494-right-after-before/sample_2.scala +++ b/test/files/neg/t7494-right-after-before/sample_2.scala @@ -1,4 +1,4 @@ -// scalac: -Xplugin:. -Xplugin-require:rafter-before-1 +//> using options -Xplugin:. -Xplugin-require:rafter-before-1 -Vphases package sample // just a sample that is compiled with the sample plugin enabled diff --git a/test/files/neg/t7494-right-after-terminal.check b/test/files/neg/t7494-right-after-terminal.check index 191f087b2804..95a92ffe6010 100644 --- a/test/files/neg/t7494-right-after-terminal.check +++ b/test/files/neg/t7494-right-after-terminal.check @@ -1,2 +1 @@ -error: [phase assembly, right after dependency on terminal phase not allowed: rightafterterminal => terminal] -1 error +fatal error: Phases form a cycle: terminal -> rightafterterminal -> terminal diff --git a/test/files/neg/t7622-cyclic-dependency.check b/test/files/neg/t7622-cyclic-dependency.check index a339c6bc37cf..c824e07ecbd8 100644 --- a/test/files/neg/t7622-cyclic-dependency.check +++ b/test/files/neg/t7622-cyclic-dependency.check @@ -1 +1 @@ -fatal error: Cycle in phase dependencies detected at cyclicdependency2, created phase-cycle.dot +fatal error: Phases form a cycle: cyclicdependency2 -> cyclicdependency1 -> cyclicdependency2 diff --git a/test/files/neg/t7622-multi-followers.check b/test/files/neg/t7622-multi-followers.check index 4ef80ffdba1d..82eb0ee03ff4 100644 --- a/test/files/neg/t7622-multi-followers.check +++ b/test/files/neg/t7622-multi-followers.check @@ -1 +1 @@ -fatal error: Multiple phases want to run right after parser; followers: multi1,multi2; created phase-order.dot +fatal error: Phases multi1 and multi2 both immediately follow parser diff --git a/test/junit/scala/tools/nsc/PhaseAssemblyTest.scala b/test/junit/scala/tools/nsc/PhaseAssemblyTest.scala index 959124fbf23b..eba6f7413250 100644 --- a/test/junit/scala/tools/nsc/PhaseAssemblyTest.scala +++ b/test/junit/scala/tools/nsc/PhaseAssemblyTest.scala @@ -15,38 +15,180 @@ package scala.tools.nsc import org.junit.Assert.assertEquals import org.junit.Test +import scala.reflect.internal.FatalError +import scala.tools.testkit.AssertUtil.assertThrows + class PhaseAssemblyTest { - @Test - def multipleRunsRightAfter(): Unit = { + case class component[G <: Global with Singleton]( + global: G, + phaseName: String, + override val runsRightAfter: Option[String], + override val runsAfter: List[String], + override val runsBefore: List[String], + ) extends SubComponent { + override val initial: Boolean = phaseName == "parser" + override val terminal: Boolean = phaseName == "terminal" + override def newPhase(prev: Phase): Phase = ??? + } + def parserAndTerminal[G <: Global with Singleton](global: G) = List( + component(global, "parser", None, Nil, Nil), + component(global,"terminal", None, Nil, Nil), + ) + case class Komponent(phaseName: String, runsRightAfter: String = null, runsAfter: String = "", runsBefore: String = "") + def komponents[G <: Global with Singleton](global: G)(ks: Komponent*): List[component[global.type]] = + ks.iterator.map(k => component(global, k.phaseName, Option(k.runsRightAfter), List(k.runsAfter).filter(_.nonEmpty), List(k.runsBefore).filter(_.nonEmpty))).toList + + @Test def multipleRunsRightAfter: Unit = { val settings = new Settings - //settings.verbose.tryToSet(Nil) + settings.verbose.tryToSet(Nil) val global = new Global(settings) - case class component[G <: Global with Singleton](global: G, phaseName: String, override val runsRightAfter: Option[String], override val runsAfter: List[String], override val runsBefore: List[String]) extends SubComponent { - override def newPhase(prev: Phase): Phase = ??? - } - //val N = 16 * 4096 // 65536 ~ 11-21 secs, 256 ~ 1-2 secs + val N = 16 * 4096 // 65536 ~ 11-21 secs, 256 ~ 1-2 secs //val N = 256 - val N = 16 + //val N = 16 val random = new scala.util.Random(123502L) val names = Array.tabulate(N)(n => s"phase_${n+1}_${random.nextInt(1024)}") - val parserAndTerminal = List( - component(global, "parser", None, Nil, Nil), - component(global,"terminal", None, Nil, List(N.toString)) - ) val beforeTerminal = List("terminal") - val components = names.foldLeft(parserAndTerminal) { (comps, nm) => + val components = names.foldLeft(parserAndTerminal(global)) { (comps, nm) => component(global, nm, runsRightAfter = comps.headOption.map(_.phaseName), runsAfter = Nil, runsBefore = beforeTerminal) :: comps } val inputs = random.shuffle(components) - //implicit val messaging: DependencyGraph.Messaging = DependencyGraph.Messaging.throws - implicit val messaging: DependencyGraph.Messaging = DependencyGraph.Messaging.silent - //implicit val messaging: DependencyGraph.Messaging = DependencyGraph.Messaging.stdout val graph = DependencyGraph(inputs) - graph.removeDanglingNodes() - graph.validateAndEnforceHardlinks() - graph.collapseHardLinksAndLevels(graph.getNodeByPhase("parser"), 1) - val result: List[String] = graph.compilerPhaseList().map(_.phaseName).filter(_.startsWith("phase_")) - //println(graph.compilerPhaseList().mkString("PHASE LIST\n", "\n", "\n")) + val phases: List[SubComponent] = graph.compilerPhaseList() + val result: List[String] = phases.map(_.phaseName).filter(_.startsWith("phase_")) + assertEquals("parser", phases.head.phaseName) + assertEquals("terminal", phases.last.phaseName) assertEquals(names.toList, result) } + @Test def trivial: Unit = { + val settings = new Settings + val global = new Global(settings) + val beforeTerminal = List("terminal") + val names = Array("phooey", "kerfuffle") + val components = names.foldLeft(parserAndTerminal(global)) { (comps, nm) => + component(global, nm, runsRightAfter = None, runsAfter = comps.headOption.map(_.phaseName).toList, runsBefore = beforeTerminal) :: comps + } + val inputs = components + val graph = DependencyGraph(inputs) + val result: List[SubComponent] = graph.compilerPhaseList() + assertEquals("parser", result.head.phaseName) + assertEquals("terminal", result.last.phaseName) + assertEquals(names.toList, result.init.tail.map(_.phaseName)) + } + @Test def `trivial conflict`: Unit = { + val settings = new Settings + val global = new Global(settings) + val beforeTerminal = List("terminal") + val names = Array("phooey", "kerfuffle", "konflikt") + def rra(nm: String) = nm match { case "kerfuffle"|"konflikt" => Some("phooey") case _ => None } + def ra(comps: List[component[global.type]], nm: String) = nm match { case "kerfuffle"|"konflikt" => Nil case _ => comps.headOption.map(_.phaseName).toList } + val components = names.foldLeft(parserAndTerminal(global)) { (comps, nm) => + component(global, nm, rra(nm), ra(comps, nm), runsBefore = beforeTerminal) :: comps + } + val graph = DependencyGraph(components) + assertThrows[FatalError](graph.compilerPhaseList(), _ == "Phases kerfuffle and konflikt both immediately follow phooey") + } + @Test def `trivial cycle`: Unit = { + val settings = new Settings + val global = new Global(settings) + val beforeTerminal = List("terminal") + val names = Array("phooey", "kerfuffle", "konflikt") + def rra(nm: String) = None + def ra(comps: List[component[global.type]], nm: String) = nm match { + case "phooey" => List("parser", "konflikt") + case "konflikt" => List("kerfuffle") + case "kerfuffle" => List("phooey") + case _ => comps.headOption.map(_.phaseName).toList + } + val components = names.foldLeft(parserAndTerminal(global)) { (comps, nm) => + component(global, nm, rra(nm), ra(comps, nm), runsBefore = beforeTerminal) :: comps + } + val graph = DependencyGraph(components) + assertThrows[FatalError](graph.compilerPhaseList(), _ == "Phases form a cycle: phooey -> kerfuffle -> konflikt -> phooey") + } + @Test def `run before tightly bound phases`: Unit = { + val settings = new Settings + val global = new Global(settings) + val components = + component(global, "phooey", None, List("parser"), List("terminal")) :: + component(global, "kerfuffle", None, List("phooey"), List("erasure")) :: + component(global, "konflikt", None, List("phooey"), List("terminal")) :: + component(global, "erasure", Some("konflikt"), Nil, List("terminal")) :: + component(global, "posterasure", Some("erasure"), Nil, List("terminal")) :: + parserAndTerminal(global) + val graph = DependencyGraph(components) + val result: List[SubComponent] = graph.compilerPhaseList() + assertEquals(List("parser", "phooey", "kerfuffle", "konflikt", "erasure", "posterasure", "terminal"), result.map(_.phaseName)) + } + //phaseList: List(parser, namer, packageobjects, typer, superaccessors, extmethods, + //pickler, xsbt-api, xsbt-dependency, refchecks, patmat, uncurry, fields, tailcalls, + //specialize, explicitouter, erasure, posterasure, lambdalift, constructors, flatten, + //mixin, cleanup, delambdafy, jvm, xsbt-analyzer, terminal) + // phasesSet is a hash set, so order of inputs should not matter. + @Test def `constraints under sbt`: Unit = { + val settings = new Settings + val global = new Global(settings) + val components = komponents(global)( + Komponent("parser"), + Komponent("namer", runsAfter = "parser"), + Komponent("packageobjects", runsRightAfter = "namer"), + Komponent("typer", runsRightAfter = "packageobjects"), + Komponent("superaccessors", runsAfter = "typer"), + Komponent("extmethods", runsAfter = "superaccessors"), + Komponent("pickler", runsAfter = "extmethods"), + Komponent("refchecks", runsAfter = "pickler"), + Komponent("patmat", runsAfter = "refchecks"), + Komponent("uncurry", runsAfter = "patmat"), + Komponent("fields", runsAfter = "uncurry"), + Komponent("tailcalls", runsAfter = "fields"), + Komponent("specialize", runsAfter = "tailcalls"), + Komponent("explicitouter", runsAfter = "specialize"), + Komponent("erasure", runsAfter = "explicitouter"), + Komponent("posterasure", runsRightAfter = "erasure"), + Komponent("async", runsAfter = "posterasure"), + Komponent("lambdalift", runsAfter = "async"), + Komponent("constructors", runsAfter = "lambdalift"), + Komponent("flatten", runsAfter = "constructors"), + Komponent("mixin", runsAfter = "flatten"), + Komponent("cleanup", runsAfter = "mixin"), + Komponent("delambdafy", runsAfter = "cleanup"), + Komponent("jvm", runsAfter = "delambdafy"), + Komponent("terminal", runsAfter = "jvm"), + Komponent("xsbt-api", runsRightAfter = "pickler", runsAfter = "typer", runsBefore = "erasure"), + Komponent("xsbt-dependency", runsRightAfter = "xsbt-api", runsBefore = "refchecks"), + Komponent("xsbt-analyzer", runsAfter = "jvm", runsBefore = "terminal"), + ) + val graph = DependencyGraph(components) + val result: List[SubComponent] = graph.compilerPhaseList() + assertEquals(List( + "parser", + "namer", + "packageobjects", + "typer", + "superaccessors", + "extmethods", + "pickler", + "xsbt-api", + "xsbt-dependency", + "refchecks", + "patmat", + "uncurry", + "fields", + "tailcalls", + "specialize", + "explicitouter", + "erasure", + "posterasure", + "async", + "lambdalift", + "constructors", + "flatten", + "mixin", + "cleanup", + "delambdafy", + "jvm", + "xsbt-analyzer", + "terminal", + ), + result.map(_.phaseName)) + } } diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsyncTest.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsyncTest.scala index 0cf7f8b07dc1..9710892d5894 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsyncTest.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsyncTest.scala @@ -682,7 +682,8 @@ abstract class AnnotationDrivenAsyncPlugin extends Plugin { } } - override val runsAfter: List[String] = "refchecks" :: "patmat" :: Nil + override val runsAfter: List[String] = "refchecks" :: Nil + override val runsRightAfter: Option[String] = Some("patmat") override val phaseName: String = "postpatmat" })