Skip to content
This repository has been archived by the owner on Aug 20, 2024. It is now read-only.

Commit

Permalink
Add Dependency API to firrtl.options package
Browse files Browse the repository at this point in the history
This adds a Dependency API (#948) to the firrtl.options package. This
adds two new methods to Phase: prerequisites and invalidates. The
former defines what Phases must have run before this Phase. The latter
is a function that can be used to determine if this Phase invalidates
another Phase. Additionally, this introduces a PhaseManager that
subclasses Phase that determines a sequence of Phases to perform a
requested lowering (from a given initial state ensure that some set of
Phases are executed).

This follows the original suggestion of @azidar in #446 for the
PhaseManager algorithm with one modification. The (DFS-based)
topological sort of dependencies is seeded using a topological sort of
the invalidations. This ensures that the number of repeated Phases is
kept to a minimum. (I am not sure if this is actually optimal,
however.) DiGraph is updated with a seeded topological sort which the
original topological sort method (linearize) now extends.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
  • Loading branch information
seldridge committed Feb 13, 2019
1 parent 8fafb03 commit 09a03ff
Show file tree
Hide file tree
Showing 4 changed files with 371 additions and 8 deletions.
19 changes: 14 additions & 5 deletions src/main/scala/firrtl/graph/DiGraph.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,14 @@ class DiGraph[T] private[graph] (private[graph] val edges: LinkedHashMap[T, Link
*/
def findSinks: Set[T] = reverse.findSources

/** Linearizes (topologically sorts) a DAG
*
/** Linearizes (topologically sorts) a DAG using a DFS. This can be seeded with an order to use for the DFS if the user
* wants to tease out a special ordering of the DAG.
* @param seed an optional sequence of vertices to use. This will default to the vertices ordering provided by getVertices.
* @throws CyclicException if the graph is cyclic
* @return a Map[T,T] from each visited node to its predecessor in the
* traversal
*/
def linearize: Seq[T] = {
def seededLinearize(seed: Option[Seq[T]] = None): Seq[T] = {
// permanently marked nodes are implicitly held in order
val order = new mutable.ArrayBuffer[T]
// invariant: no intersection between unmarked and tempMarked
Expand All @@ -80,7 +81,7 @@ class DiGraph[T] private[graph] (private[graph] val edges: LinkedHashMap[T, Link
case class LinearizeFrame[T](v: T, expanded: Boolean)
val callStack = mutable.Stack[LinearizeFrame[T]]()

unmarked ++= getVertices
unmarked ++= seed.getOrElse(getVertices)
while (unmarked.nonEmpty) {
callStack.push(LinearizeFrame(unmarked.head, false))
while (callStack.nonEmpty) {
Expand Down Expand Up @@ -109,6 +110,14 @@ class DiGraph[T] private[graph] (private[graph] val edges: LinkedHashMap[T, Link
order.reverse.toSeq
}

/** Linearizes (topologically sorts) a DAG
*
* @throws CyclicException if the graph is cyclic
* @return a Map[T,T] from each visited node to its predecessor in the
* traversal
*/
def linearize: Seq[T] = seededLinearize(None)

/** Performs breadth-first search on the directed graph
*
* @param root the start node
Expand Down Expand Up @@ -163,7 +172,7 @@ class DiGraph[T] private[graph] (private[graph] val edges: LinkedHashMap[T, Link
* @return a Seq[T] of nodes defining an arbitrary valid path
*/
def path(start: T, end: T): Seq[T] = path(start, end, Set.empty[T])

/** Finds a path (if one exists) from one node to another, with a blacklist
*
* @param start the start node
Expand Down
20 changes: 17 additions & 3 deletions src/main/scala/firrtl/options/Phase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import firrtl.annotations.DeletedAnnotation

import logger.LazyLogging

import scala.collection.mutable

/** A polymorphic mathematical transform
* @tparam A the transformed type
*/
Expand All @@ -25,12 +23,28 @@ trait TransformLike[A] extends LazyLogging {

}

trait DependencyAPI { this: Phase =>

/** All [[Phase]]s that must run before this [[Phase]] */
def prerequisites: Set[Phase] = Set.empty

/** A function that, given some other [[Phase]], will return [[true]] if this [[Phase]] invalidates the other [[Phase]].
* By default, this invalidates everything except itself.
* @note Can a [[Phase]] ever invalidate itself?
*/
def invalidates(phase: Phase): Boolean = phase match {
case _: this.type => false
case _ => true
}

}

/** A mathematical transformation of an [[AnnotationSeq]].
*
* A [[Phase]] forms one unit in the Chisel/FIRRTL Hardware Compiler Framework (HCF). The HCF is built from a sequence
* of [[Phase]]s applied to an [[AnnotationSeq]]. Note that a [[Phase]] may consist of multiple phases internally.
*/
abstract class Phase extends TransformLike[AnnotationSeq] {
abstract class Phase extends TransformLike[AnnotationSeq] with DependencyAPI {

/** The name of this [[Phase]]. This will be used to generate debug/error messages or when deleting annotations. This
* will default to the `simpleName` of the class.
Expand Down
117 changes: 117 additions & 0 deletions src/main/scala/firrtl/options/PhaseManager.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// See LICENSE for license details.

package firrtl.options

import firrtl.AnnotationSeq
import firrtl.graph.{DiGraph, CyclicException}

import scala.collection.mutable

case class PhaseManagerException(message: String, cause: Throwable = null) extends RuntimeException(message, cause)

/** A [[Phase]] that will ensure that some other [[Phase]]s and their prerequisites are executed.
*
* This tries to determine a phase ordering such that an [[AnnotationSeq]] ''output'' is produced that has had all of
* the requested [[Phase]] target transforms run without having them be invalidated.
* @param phaseTargets the [[Phase]]s you want to run
*/
case class PhaseManager(phaseTargets: Set[Phase], currentState: Set[Phase] = Set.empty) extends Phase {

/** Modified breadth-first search that supports multiple starting nodes and a custom extractor that can be used to
* generate/filter the edges to explore. Additionally, this will include edges to previously discovered nodes.
*/
private def bfs(start: Set[Phase], blacklist: Set[Phase], extractor: Phase => Set[Phase]): Map[Phase, Set[Phase]] = {
val queue: mutable.Queue[Phase] = mutable.Queue(start.toSeq:_*)
val edges: mutable.HashMap[Phase, Set[Phase]] = mutable.HashMap[Phase, Set[Phase]](start.map((_ -> Set[Phase]())).toSeq:_*)
while (queue.nonEmpty) {
val u = queue.dequeue
for (v <- extractor(u)) {
if (!blacklist.contains(v) && !edges.contains(v)) { queue.enqueue(v) }
if (!edges.contains(v)) { edges(v) = Set.empty }
edges(u) = edges(u) + v
}
}
edges.toMap
}

/** Pull in all registered phases once phase registration is integrated
* @todo implement this
*/
private lazy val registeredPhases: Set[Phase] = Set.empty

/** A directed graph consisting of prerequisite edges */
lazy val dependencyGraph: DiGraph[Phase] =
DiGraph(
bfs(
start = phaseTargets,
blacklist = currentState,
extractor = (p: Phase) => p.prerequisites))

/** A directed graph consisting of edges derived from invalidation */
lazy val invalidateGraph: DiGraph[Phase] = {
val v = dependencyGraph.getVertices
DiGraph(
bfs(
start = phaseTargets,
blacklist = currentState,
extractor = (p: Phase) => v.filter(p.invalidates).toSet))
.reverse
}

/** Wrap a possible [[CyclicException]] thrown by a thunk in a [[PhaseManagerException]] */
private def cyclePossible[A](a: String, thunk: => A): A = try { thunk } catch {
case e: CyclicException =>
throw new PhaseManagerException(
s"No Phase ordering possible due to cyclic dependency in $a at node '${e.node}'.", e)
}

/** The ordering of phases to run that respects prerequisites and reduces the number of required re-lowerings resulting
* from invalidations.
*/
lazy val phaseOrder: Seq[Phase] = {

/* Topologically sort the dependency graph using the invalidate graph topological sort as a seed. This has the effect
* of minimizing the number of repeated [[Phase]]s */
val sorted = {
val seed = cyclePossible("invalidates", invalidateGraph.linearize).reverse
cyclePossible("prerequisites",
dependencyGraph
.seededLinearize(Some(seed))
.reverse
.dropWhile(currentState.contains))
}

val (state, lowerers) = {
val (s, l) = sorted.foldLeft((currentState, Array[Phase]())){ case ((state, out), in) =>
val missing = (in.prerequisites -- state)
val preprocessing: Option[Phase] = {
if (missing.nonEmpty) { Some(PhaseManager(missing, state)) }
else { None }
}
((state ++ missing + in).filterNot(in.invalidates), out ++ preprocessing :+ in)
}
val missing = (phaseTargets -- s)
val postprocessing: Option[Phase] = {
if (missing.nonEmpty) { Some(PhaseManager(missing, s)) }
else { None }
}

(s ++ missing, l ++ postprocessing)
}

if (!phaseTargets.subsetOf(state)) {
throw new PhaseException(s"The final state ($state) did not include the requested targets (${phaseTargets})!")
}
lowerers
}

def flattenedPhaseOrder: Seq[Phase] = phaseOrder.flatMap {
case p: PhaseManager => p.flattenedPhaseOrder
case p: Phase => Some(p)
}

final def transform(annotations: AnnotationSeq): AnnotationSeq =
phaseOrder
.foldLeft(annotations){ case (a, p) => p.transform(a) }

}
Loading

0 comments on commit 09a03ff

Please sign in to comment.