Skip to content

Commit

Permalink
DriverCompatibility => DriverCompatibilityExtensions (#223)
Browse files Browse the repository at this point in the history
DriverCompatibility still exists in firrtl so we want to ensure there
are no classpath collisions. Instead, we'll represent polyfill methods
via extension methods. This does require an additional import (it's not
source compatible), but it's the best we can do.
  • Loading branch information
jackkoenig authored Dec 16, 2021
1 parent a0fa6fd commit a1a0260
Showing 1 changed file with 9 additions and 247 deletions.
256 changes: 9 additions & 247 deletions src/main/scala/firrtl/stage/phases/DriverCompatibility.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: Apache-2.0

package firrtl.stage.phases
package firrtl.stage

import firrtl.stage._

Expand All @@ -16,255 +16,17 @@ import scopt.OptionParser

import java.io.File

/** Provides compatibility methods to replicate deprecated [[Driver]] semantics.
*
* At a high level, the [[Driver]] tries extremely hard to figure out what the user meant and to enable them to not be
* explicit with command line options. As an example, the `--top-name` option is not used for any FIRRTL top module
* determination, but to find a FIRRTL file by that name and/or an annotation file by that name. This mode of file
* discovery is only used if no explicit FIRRTL file/source/circuit and/or annotation file is given. Going further, the
* `--top-name` argument is implicitly specified by the `main` of an input circuit if not explicit and can be used to
* derive an annotation file. Summarily, the [[firrtl.options.Phase Phase]]s provided by this enable this type of
* resolution.
*
* '''Only use these methods if you are intending to replicate old [[Driver]] semantics for a good reason.'''
* Otherwise, opt for more explicit specification by the user.
*/
object DriverCompatibility {
package object phases {

/** Shorthand object for throwing an exception due to an option that was removed */
private def optionRemoved(a: String): String =
s"""|Option '$a' was removed as part of the FIRRTL Stage refactor. Use an explicit input/output options instead.
|This error will be removed in 1.3.""".stripMargin
implicit class DriverCompatibilityExtensions(x: DriverCompatibility.type) {

/** Convert an [[firrtl.AnnotationSeq AnnotationSeq]] to a ''deprecated'' [[firrtl.FirrtlExecutionResult
* FirrtlExecutionResult]].
* @param annotations a sequence of [[firrtl.annotations.Annotation Annotation]]
*/
@deprecated("FirrtlExecutionResult is deprecated as part of the Stage/Phase refactor. Migrate to FirrtlStage.", "1.2")
def firrtlResultView(annotations: AnnotationSeq): FirrtlExecutionResult =
Viewer[FirrtlExecutionResult].view(annotations)

/** Holds the name of the top (main) module in an input circuit
* @param value top module name
*/
@deprecated(
""""top-name" is deprecated as part of the Stage/Phase refactor. Use explicit input/output files.""",
"1.2"
)
case class TopNameAnnotation(topName: String) extends NoTargetAnnotation

object TopNameAnnotation {

def addOptions(p: OptionParser[AnnotationSeq]): Unit = p
.opt[Unit]("top-name")
.abbr("tn")
.hidden()
.unbounded()
.action((_, _) => throw new OptionsException(optionRemoved("--top-name/-tn")))
}

/** Indicates that the implicit emitter, derived from a [[CompilerAnnotation]] should be an [[EmitAllModulesAnnotation]]
* as opposed to an [[EmitCircuitAnnotation]].
*/
case object EmitOneFilePerModuleAnnotation extends NoTargetAnnotation {

def addOptions(p: OptionParser[AnnotationSeq]): Unit = p
.opt[Unit]("split-modules")
.abbr("fsm")
.hidden()
.unbounded()
.action((_, _) => throw new OptionsException(optionRemoved("--split-modules/-fsm")))

}

/** Determine the top name using the following precedence (highest to lowest):
* - Explicitly from a [[TopNameAnnotation]]
* - Implicitly from the top module ([[firrtl.ir.Circuit.main main]]) of a [[FirrtlCircuitAnnotation]]
* - Implicitly from the top module ([[firrtl.ir.Circuit.main main]]) of a [[FirrtlSourceAnnotation]]
* - Implicitly from the top module ([[firrtl.ir.Circuit.main main]]) of a [[FirrtlFileAnnotation]]
*
* @param annotations annotations to extract topName from
* @return the top module ''if it can be determined''
*/
private def topName(annotations: AnnotationSeq): Option[String] =
annotations.collectFirst { case TopNameAnnotation(n) => n }
.orElse(annotations.collectFirst { case FirrtlCircuitAnnotation(c) => c.main }.orElse(annotations.collectFirst {
case FirrtlSourceAnnotation(s) => Parser.parse(s).main
}.orElse(annotations.collectFirst {
case FirrtlFileAnnotation(f) =>
FirrtlStageUtils.getFileExtension(f) match {
case ProtoBufFile => FromProto.fromFile(f).main
case FirrtlFile => Parser.parse(FileUtils.getText(f)).main
}
})))

/** Determine the target directory with the following precedence (highest to lowest):
* - Explicitly from the user-specified [[firrtl.options.TargetDirAnnotation TargetDirAnnotation]]
* - Implicitly from the default of [[firrtl.options.StageOptions.targetDir StageOptions.targetDir]]
*
* @param annotations input annotations to extract targetDir from
* @return the target directory
*/
private def targetDir(annotations: AnnotationSeq): String = Viewer[StageOptions].view(annotations).targetDir

/** Add an implicit annotation file derived from the determined top name of the circuit if no
* [[firrtl.options.InputAnnotationFileAnnotation InputAnnotationFileAnnotation]] is present.
*
* The implicit annotation file is determined through the following complicated semantics:
* - If an [[firrtl.options.InputAnnotationFileAnnotation InputAnnotationFileAnnotation]] already exists, then
* nothing is modified
* - If the derived topName (the `main` in a [[firrtl.ir.Circuit Circuit]]) is ''discernable'' (see below) and a
* file called `topName.anno` (exactly, not `topName.anno.json`) exists, then this will add an
* [[firrtl.options.InputAnnotationFileAnnotation InputAnnotationFileAnnotation]] using that `topName.anno`
* - If any of this doesn't work, then the the [[AnnotationSeq]] is unmodified
*
* The precedence for determining the `topName` is the following (first one wins):
* - The `topName` in a [[TopNameAnnotation]]
* - The `main` [[FirrtlCircuitAnnotation]]
* - The `main` in a parsed [[FirrtlSourceAnnotation]]
* - The `main` in the first [[FirrtlFileAnnotation]] using either ProtoBuf or parsing as determined by file
* extension
*
* @param annos input annotations
* @return output annotations
*/
class AddImplicitAnnotationFile extends Phase {

override def prerequisites = Seq(Dependency[AddImplicitFirrtlFile])

override def optionalPrerequisiteOf = Seq(Dependency[FirrtlPhase], Dependency[FirrtlStage])

override def invalidates(a: Phase) = false

/** Try to add an [[firrtl.options.InputAnnotationFileAnnotation InputAnnotationFileAnnotation]] implicitly specified by
* an [[AnnotationSeq]].
/** Convert an [[firrtl.AnnotationSeq AnnotationSeq]] to a ''deprecated'' [[firrtl.FirrtlExecutionResult
* FirrtlExecutionResult]].
* @param annotations a sequence of [[firrtl.annotations.Annotation Annotation]]
*/
def transform(annotations: AnnotationSeq): AnnotationSeq = annotations.collectFirst {
case a: InputAnnotationFileAnnotation => a
} match {
case Some(_) => annotations
case None =>
topName(annotations) match {
case Some(n) =>
val filename = targetDir(annotations) + "/" + n + ".anno"
if (new File(filename).exists) {
StageUtils.dramaticWarning(
s"Implicit reading of the annotation file is deprecated! Use an explict --annotation-file argument."
)
annotations :+ InputAnnotationFileAnnotation(filename)
} else {
annotations
}
case None => annotations
}
}
@deprecated("FirrtlExecutionResult is deprecated as part of the Stage/Phase refactor. Migrate to FirrtlStage.", "1.2")
def firrtlResultView(annotations: AnnotationSeq): FirrtlExecutionResult =
Viewer[FirrtlExecutionResult].view(annotations)

}

/** Add a [[FirrtlFileAnnotation]] if no annotation that explictly defines a circuit exists.
*
* This takes the option with the following precedence:
* - If an annotation subclassing [[CircuitOption]] exists, do nothing
* - If a [[TopNameAnnotation]] exists, use that to derive a [[FirrtlFileAnnotation]] and append it
* - Do nothing
*
* In the case of (3) above, this [[AnnotationSeq]] is likely insufficient for FIRRTL to work with (no circuit was
* passed). However, instead of catching this here, we rely on [[Checks]] to validate the annotations.
*
* @param annotations input annotations
* @return
*/
class AddImplicitFirrtlFile extends Phase {

override def prerequisites = Seq.empty

override def optionalPrerequisiteOf = Seq(Dependency[FirrtlPhase], Dependency[FirrtlStage])

override def invalidates(a: Phase) = false

/** Try to add a [[FirrtlFileAnnotation]] implicitly specified by an [[AnnotationSeq]]. */
def transform(annotations: AnnotationSeq): AnnotationSeq = {
val circuit = annotations.collectFirst { case a @ (_: CircuitOption | _: FirrtlCircuitAnnotation) => a }
val main = annotations.collectFirst { case a: TopNameAnnotation => a.topName }

if (circuit.nonEmpty) {
annotations
} else if (main.nonEmpty) {
StageUtils.dramaticWarning(
s"Implicit reading of the input file is deprecated! Use an explict --input-file argument."
)
FirrtlFileAnnotation(Viewer[StageOptions].view(annotations).getBuildFileName(s"${main.get}.fir")) +: annotations
} else {
annotations
}
}
}

/** Adds an [[firrtl.EmitAnnotation EmitAnnotation]] for each [[CompilerAnnotation]].
*
* If an [[EmitOneFilePerModuleAnnotation]] exists, then this will add an [[EmitAllModulesAnnotation]]. Otherwise,
* this adds an [[EmitCircuitAnnotation]]. This replicates old behavior where specifying a compiler automatically
* meant that an emitter would also run.
*/
@deprecated(
"""AddImplicitEmitter should only be used to build Driver compatibility wrappers. Switch to Stage.""",
"1.2"
)
class AddImplicitEmitter extends Phase {

override def prerequisites = Seq.empty

override def optionalPrerequisiteOf = Seq(Dependency[FirrtlPhase], Dependency[FirrtlStage])

override def invalidates(a: Phase) = false

/** Add one [[EmitAnnotation]] foreach [[CompilerAnnotation]]. */
def transform(annotations: AnnotationSeq): AnnotationSeq = {
val splitModules = annotations.collectFirst { case a: EmitOneFilePerModuleAnnotation.type => a }.isDefined

annotations.flatMap {
case a @ CompilerAnnotation(c) =>
val b = RunFirrtlTransformAnnotation(a.compiler.emitter)
if (splitModules) { Seq(a, b, EmitAllModulesAnnotation(c.emitter.getClass)) }
else { Seq(a, b, EmitCircuitAnnotation(c.emitter.getClass)) }
case a @ RunFirrtlTransformAnnotation(e: Emitter) =>
if (splitModules) { Seq(a, EmitAllModulesAnnotation(e.getClass)) }
else { Seq(a, EmitCircuitAnnotation(e.getClass)) }
case a => Seq(a)
}
}

}

/** Adds an [[OutputFileAnnotation]] derived from a [[TopNameAnnotation]] if no [[OutputFileAnnotation]] already
* exists. If no [[TopNameAnnotation]] exists, then no [[OutputFileAnnotation]] is added.
*/
@deprecated(
"""AddImplicitOutputFile should only be used to build Driver compatibility wrappers. Switch to Stage.""",
"1.2"
)
class AddImplicitOutputFile extends Phase {

override def prerequisites = Seq(Dependency[AddImplicitFirrtlFile])

override def optionalPrerequisiteOf = Seq(Dependency[FirrtlPhase], Dependency[FirrtlStage])

override def invalidates(a: Phase) = false

/** Add an [[OutputFileAnnotation]] derived from a [[TopNameAnnotation]] if needed. */
def transform(annotations: AnnotationSeq): AnnotationSeq = {
val hasOutputFile = annotations.collectFirst {
case a @ (_: EmitOneFilePerModuleAnnotation.type | _: OutputFileAnnotation) => a
}.isDefined
val top = topName(annotations)

if (!hasOutputFile && top.isDefined) {
OutputFileAnnotation(top.get) +: annotations
} else {
annotations
}
}

}

}

0 comments on commit a1a0260

Please # to comment.