aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/firrtl/annotations
diff options
context:
space:
mode:
authorJack Koenig2018-02-27 18:07:11 -0800
committerGitHub2018-02-27 18:07:11 -0800
commitc7eb1570dfb1c7701ea32d1209982a053f3cec1d (patch)
tree3f509b202d82841c5dad5588d1f953a25d389b44 /src/main/scala/firrtl/annotations
parentb90fc784a1819c1d7905910130a7da022214bc22 (diff)
Refactor Annotations (#721)
- Old Annotation renamed to deprecated LegacyAnnotation - Annotation is now a trait that can be extended - New JsonProtocol for Annotation [de]serialization - Replace AnnotationMap with AnnotationSeq - Deprecate Transform.getMyAnnotations - Update Transforms - Turn on deprecation warnings - Remove deprecated Driver.compile - Make AnnotationTests abstract with Legacy and Json subclasses - Add functionality to convert LegacyAnnotations of built-in annos This will give a noisy warning and is more of a best effort than a robust solution. Fixes #475 Closes #609
Diffstat (limited to 'src/main/scala/firrtl/annotations')
-rw-r--r--src/main/scala/firrtl/annotations/Annotation.scala169
-rw-r--r--src/main/scala/firrtl/annotations/AnnotationUtils.scala11
-rw-r--r--src/main/scala/firrtl/annotations/AnnotationYamlProtocol.scala13
-rw-r--r--src/main/scala/firrtl/annotations/JsonProtocol.scala84
-rw-r--r--src/main/scala/firrtl/annotations/Named.scala1
5 files changed, 244 insertions, 34 deletions
diff --git a/src/main/scala/firrtl/annotations/Annotation.scala b/src/main/scala/firrtl/annotations/Annotation.scala
index f96724c6..b16509a1 100644
--- a/src/main/scala/firrtl/annotations/Annotation.scala
+++ b/src/main/scala/firrtl/annotations/Annotation.scala
@@ -8,16 +8,80 @@ import firrtl.annotations.AnnotationYamlProtocol._
case class AnnotationException(message: String) extends Exception(message)
-final case class Annotation(target: Named, transform: Class[_ <: Transform], value: String) {
+/** Base type of auxiliary information */
+trait Annotation {
+ /** Update the target based on how signals are renamed */
+ def update(renames: RenameMap): Seq[Annotation]
+
+ /** Pretty Print
+ *
+ * @note In [[logger.LogLevel.Debug]] this is called on every Annotation after every Transform
+ */
+ def serialize: String = this.toString
+}
+
+/** If an Annotation does not target any [[Named]] thing in the circuit, then all updates just
+ * return the Annotation itself
+ */
+trait NoTargetAnnotation extends Annotation {
+ def update(renames: RenameMap) = Seq(this)
+}
+
+/** An Annotation that targets a single [[Named]] thing */
+trait SingleTargetAnnotation[T <: Named] extends Annotation {
+ val target: T
+
+ /** Create another instance of this Annotation */
+ def duplicate(n: T): Annotation
+
+ // This mess of @unchecked and try-catch is working around the fact that T is unknown due to type
+ // erasure. We cannot that newTarget is of type T, but a CastClassException will be thrown upon
+ // invoking duplicate if newTarget cannot be cast to T (only possible in the concrete subclass)
+ def update(renames: RenameMap): Seq[Annotation] =
+ renames.get(target).map(_.map(newT => (newT: @unchecked) match {
+ case newTarget: T @unchecked =>
+ try {
+ duplicate(newTarget)
+ } catch {
+ case _: java.lang.ClassCastException =>
+ val msg = s"${this.getClass.getName} target ${target.getClass.getName} " +
+ s"cannot be renamed to ${newTarget.getClass}"
+ throw AnnotationException(msg)
+ }
+ })).getOrElse(List(this))
+}
+
+trait SingleStringAnnotation extends NoTargetAnnotation {
+ def value: String
+}
+
+object Annotation {
+ @deprecated("This returns a LegacyAnnotation, use an explicit Annotation type", "1.1")
+ def apply(target: Named, transform: Class[_ <: Transform], value: String) =
+ new LegacyAnnotation(target, transform, value)
+ @deprecated("This uses LegacyAnnotation, use an explicit Annotation type", "1.1")
+ def unapply(a: LegacyAnnotation): Option[(Named, Class[_ <: Transform], String)] =
+ Some((a.target, a.transform, a.value))
+}
+
+// Constructor is private so that we can still construct these internally without deprecation
+// warnings
+final case class LegacyAnnotation private[firrtl] (
+ target: Named,
+ transform: Class[_ <: Transform],
+ value: String) extends SingleTargetAnnotation[Named] {
val targetString: String = target.serialize
val transformClass: String = transform.getName
+ def targets(named: Named): Boolean = named == target
+ def targets(transform: Class[_ <: Transform]): Boolean = transform == this.transform
+
/**
* This serialize is basically a pretty printer, actual serialization is handled by
* AnnotationYamlProtocol
* @return a nicer string than the raw case class default
*/
- def serialize: String = {
+ override def serialize: String = {
s"Annotation(${target.serialize},${transform.getCanonicalName},$value)"
}
@@ -27,34 +91,89 @@ final case class Annotation(target: Named, transform: Class[_ <: Transform], val
}
def propagate(from: Named, tos: Seq[Named], dup: Named=>Annotation): Seq[Annotation] = tos.map(dup(_))
def check(from: Named, tos: Seq[Named], which: Annotation): Unit = {}
- def duplicate(n: Named) = Annotation(n, transform, value)
+ def duplicate(n: Named) = new LegacyAnnotation(n, transform, value)
}
-object DeletedAnnotation {
- def apply(xFormName: String, anno: Annotation): Annotation =
- Annotation(anno.target, classOf[Transform], s"""DELETED by $xFormName\n${AnnotationUtils.toYaml(anno)}""")
+// Private so that LegacyAnnotation can only be constructed via deprecated Annotation.apply
+private[firrtl] object LegacyAnnotation {
+ // ***** Everything below here is to help people migrate off of old annotations *****
+ def errorIllegalAnno(name: String) =
+ throw new Exception(s"Old-style annotations that look like $name are no longer supported")
+
+ private val OldDeletedRegex = """(?s)DELETED by ([^\n]*)\n(.*)""".r
+ private val PinsRegex = "pins:(.*)".r
+ private val SourceRegex = "source (.+)".r
+ private val SinkRegex = "sink (.+)".r
- private val deletedRegex = """(?s)DELETED by ([^\n]*)\n(.*)""".r
- def unapply(a: Annotation): Option[(String, Annotation)] = a match {
- case Annotation(named, t, deletedRegex(xFormName, annoString)) if t == classOf[Transform] =>
- Some((xFormName, AnnotationUtils.fromYaml(annoString)))
- case _ => None
+ import firrtl.transforms._
+ import firrtl.passes._
+ import firrtl.passes.memlib._
+ import firrtl.passes.wiring._
+ import firrtl.passes.clocklist._
+ // Attempt to convert common Annotations and error on the rest of old-style build-in annotations
+ def convertLegacyAnno(anno: LegacyAnnotation): Annotation = anno match {
+ // All old-style Emitter annotations are illegal
+ case LegacyAnnotation(_,_,"emitCircuit") => errorIllegalAnno("EmitCircuitAnnotation")
+ case LegacyAnnotation(_,_,"emitAllModules") => errorIllegalAnno("EmitAllModulesAnnotation")
+ case LegacyAnnotation(_,_,value) if value.startsWith("emittedFirrtlCircuit") =>
+ errorIllegalAnno("EmittedFirrtlCircuitAnnotation")
+ case LegacyAnnotation(_,_,value) if value.startsWith("emittedFirrtlModule") =>
+ errorIllegalAnno("EmittedFirrtlModuleAnnotation")
+ case LegacyAnnotation(_,_,value) if value.startsWith("emittedVerilogCircuit") =>
+ errorIllegalAnno("EmittedVerilogCircuitAnnotation")
+ case LegacyAnnotation(_,_,value) if value.startsWith("emittedVerilogModule") =>
+ errorIllegalAnno("EmittedVerilogModuleAnnotation")
+ // People shouldn't be trying to pass deleted annotations to Firrtl
+ case LegacyAnnotation(_,_,OldDeletedRegex(_,_)) => errorIllegalAnno("DeletedAnnotation")
+ // Some annotations we'll try to support
+ case LegacyAnnotation(named, t, _) if t == classOf[InlineInstances] => InlineAnnotation(named)
+ case LegacyAnnotation(n: ModuleName, t, outputConfig) if t == classOf[ClockListTransform] =>
+ ClockListAnnotation(n, outputConfig)
+ case LegacyAnnotation(CircuitName(_), transform, "") if transform == classOf[InferReadWrite] =>
+ InferReadWriteAnnotation
+ case LegacyAnnotation(_,_,PinsRegex(pins)) => PinAnnotation(pins.split(" "))
+ case LegacyAnnotation(_, t, value) if t == classOf[ReplSeqMem] =>
+ val args = value.split(" ")
+ require(args.size == 2, "Something went wrong, stop using legacy ReplSeqMemAnnotation")
+ ReplSeqMemAnnotation(args(0), args(1))
+ case LegacyAnnotation(c: ComponentName, transform, "nodedupmem!")
+ if transform == classOf[ResolveMemoryReference] => NoDedupMemAnnotation(c)
+ case LegacyAnnotation(m: ModuleName, transform, "nodedup!")
+ if transform == classOf[DedupModules] => NoDedupAnnotation(m)
+ case LegacyAnnotation(c: ComponentName, _, SourceRegex(pin)) => SourceAnnotation(c, pin)
+ case LegacyAnnotation(n, _, SinkRegex(pin)) => SinkAnnotation(n, pin)
+ case LegacyAnnotation(m: ModuleName, t, text) if t == classOf[BlackBoxSourceHelper] =>
+ text.split("\n", 3).toList match {
+ case "resource" :: id :: _ => BlackBoxResourceAnno(m, id)
+ case "inline" :: name :: text :: _ => BlackBoxInlineAnno(m, name, text)
+ case "targetDir" :: targetDir :: _ => BlackBoxTargetDirAnno(targetDir)
+ case _ => errorIllegalAnno("BlackBoxSourceAnnotation")
+ }
+ case LegacyAnnotation(_, transform, "noDCE!") if transform == classOf[DeadCodeElimination] =>
+ NoDCEAnnotation
+ case LegacyAnnotation(c: ComponentName, _, "DONTtouch!") => DontTouchAnnotation(c)
+ case LegacyAnnotation(c: ModuleName, _, "optimizableExtModule!") =>
+ OptimizableExtModuleAnnotation(c)
+ case other => other
+ }
+ def convertLegacyAnnos(annos: Seq[Annotation]): Seq[Annotation] = {
+ var warned: Boolean = false
+ annos.map {
+ case legacy: LegacyAnnotation =>
+ val annox = convertLegacyAnno(legacy)
+ if (!warned && (annox ne legacy)) {
+ val msg = s"A LegacyAnnotation was automatically converted.\n" + (" "*9) +
+ "This functionality will soon be removed. Please migrate to new annotations."
+ Driver.dramaticWarning(msg)
+ warned = true
+ }
+ annox
+ case other => other
+ }
}
}
-/** Parent class to create global annotations
- *
- * These annotations are Circuit-level and available to every transform
- */
-abstract class GlobalCircuitAnnotation {
- private lazy val marker = this.getClass.getName
- def apply(value: String): Annotation =
- Annotation(CircuitTopName, classOf[Transform], s"$marker:$value")
- def unapply(a: Annotation): Option[String] = a match {
- // Assumes transform is already filtered appropriately
- case Annotation(CircuitTopName, _, str) if str.startsWith(marker) =>
- Some(str.stripPrefix(s"$marker:"))
- case _ => None
- }
+case class DeletedAnnotation(xFormName: String, anno: Annotation) extends NoTargetAnnotation {
+ override def serialize: String = s"""DELETED by $xFormName\n${anno.serialize}"""
}
diff --git a/src/main/scala/firrtl/annotations/AnnotationUtils.scala b/src/main/scala/firrtl/annotations/AnnotationUtils.scala
index 240c46d6..0fc192a6 100644
--- a/src/main/scala/firrtl/annotations/AnnotationUtils.scala
+++ b/src/main/scala/firrtl/annotations/AnnotationUtils.scala
@@ -3,15 +3,22 @@
package firrtl
package annotations
+import org.json4s._
+import org.json4s.native.JsonMethods._
+import org.json4s.native.Serialization
+import org.json4s.native.Serialization.{read, write, writePretty}
+
import net.jcazevedo.moultingyaml._
import firrtl.annotations.AnnotationYamlProtocol._
import firrtl.ir._
import firrtl.Utils.error
+class InvalidAnnotationFileException(msg: String) extends Exception(msg)
+
object AnnotationUtils {
- def toYaml(a: Annotation): String = a.toYaml.prettyPrint
- def fromYaml(s: String): Annotation = s.parseYaml.convertTo[Annotation]
+ def toYaml(a: LegacyAnnotation): String = a.toYaml.prettyPrint
+ def fromYaml(s: String): LegacyAnnotation = s.parseYaml.convertTo[LegacyAnnotation]
/** Returns true if a valid Module name */
val SerializedModuleName = """([a-zA-Z_][a-zA-Z_0-9~!@#$%^*\-+=?/]*)""".r
diff --git a/src/main/scala/firrtl/annotations/AnnotationYamlProtocol.scala b/src/main/scala/firrtl/annotations/AnnotationYamlProtocol.scala
index 9018d494..e0337d6e 100644
--- a/src/main/scala/firrtl/annotations/AnnotationYamlProtocol.scala
+++ b/src/main/scala/firrtl/annotations/AnnotationYamlProtocol.scala
@@ -7,23 +7,24 @@ import net.jcazevedo.moultingyaml._
object AnnotationYamlProtocol extends DefaultYamlProtocol {
// bottom depends on top
- implicit object AnnotationYamlFormat extends YamlFormat[Annotation] {
- def write(a: Annotation) = YamlObject(
+ implicit object AnnotationYamlFormat extends YamlFormat[LegacyAnnotation] {
+ def write(a: LegacyAnnotation) = YamlObject(
YamlString("targetString") -> YamlString(a.targetString),
YamlString("transformClass") -> YamlString(a.transformClass),
YamlString("value") -> YamlString(a.value)
)
- def read(yamlValue: YamlValue): Annotation = {
+ def read(yamlValue: YamlValue): LegacyAnnotation = {
try {
yamlValue.asYamlObject.getFields(
YamlString("targetString"),
YamlString("transformClass"),
YamlString("value")) match {
case Seq(YamlString(targetString), YamlString(transformClass), YamlString(value)) =>
- Annotation(
- toTarget(targetString), Class.forName(transformClass).asInstanceOf[Class[_ <: Transform]], value)
- case _ => deserializationError("Annotation expected")
+ LegacyAnnotation(toTarget(targetString),
+ Class.forName(transformClass).asInstanceOf[Class[_ <: Transform]],
+ value)
+ case _ => deserializationError("LegacyAnnotation expected")
}
}
catch {
diff --git a/src/main/scala/firrtl/annotations/JsonProtocol.scala b/src/main/scala/firrtl/annotations/JsonProtocol.scala
new file mode 100644
index 00000000..5005ccb0
--- /dev/null
+++ b/src/main/scala/firrtl/annotations/JsonProtocol.scala
@@ -0,0 +1,84 @@
+
+package firrtl
+package annotations
+
+import scala.util.Try
+
+import org.json4s._
+import org.json4s.native.JsonMethods._
+import org.json4s.native.Serialization
+import org.json4s.native.Serialization.{read, write, writePretty}
+
+import firrtl.ir._
+import firrtl.Utils.error
+
+object JsonProtocol {
+
+ // Helper for error messages
+ private def prettifyJsonInput(in: JsonInput): String = {
+ def defaultToString(base: String, obj: Any): String = s"$base@${obj.hashCode.toHexString}"
+ in match {
+ case FileInput(file) => file.toString
+ case StringInput(o) => defaultToString("String", o)
+ case ReaderInput(o) => defaultToString("Reader", o)
+ case StreamInput(o) => defaultToString("Stream", o)
+ }
+ }
+
+ class TransformClassSerializer extends CustomSerializer[Class[_ <: Transform]](format => (
+ { case JString(s) => Class.forName(s).asInstanceOf[Class[_ <: Transform]] },
+ { case x: Class[_] => JString(x.getName) }
+ ))
+ // TODO Reduce boilerplate?
+ class NamedSerializer extends CustomSerializer[Named](format => (
+ { case JString(s) => AnnotationUtils.toNamed(s) },
+ { case named: Named => JString(named.serialize) }
+ ))
+ class CircuitNameSerializer extends CustomSerializer[CircuitName](format => (
+ { case JString(s) => AnnotationUtils.toNamed(s).asInstanceOf[CircuitName] },
+ { case named: CircuitName => JString(named.serialize) }
+ ))
+ class ModuleNameSerializer extends CustomSerializer[ModuleName](format => (
+ { case JString(s) => AnnotationUtils.toNamed(s).asInstanceOf[ModuleName] },
+ { case named: ModuleName => JString(named.serialize) }
+ ))
+ class ComponentNameSerializer extends CustomSerializer[ComponentName](format => (
+ { case JString(s) => AnnotationUtils.toNamed(s).asInstanceOf[ComponentName] },
+ { case named: ComponentName => JString(named.serialize) }
+ ))
+
+ /** Construct Json formatter for annotations */
+ def jsonFormat(tags: Seq[Class[_ <: Annotation]]) = {
+ Serialization.formats(FullTypeHints(tags.toList)).withTypeHintFieldName("class") +
+ new TransformClassSerializer + new NamedSerializer + new CircuitNameSerializer +
+ new ModuleNameSerializer + new ComponentNameSerializer
+ }
+
+ /** Serialize annotations to a String for emission */
+ def serialize(annos: Seq[Annotation]): String = serializeTry(annos).get
+
+ def serializeTry(annos: Seq[Annotation]): Try[String] = {
+ val tags = annos.map(_.getClass).distinct
+ implicit val formats = jsonFormat(tags)
+ Try(writePretty(annos))
+ }
+
+ def deserialize(in: JsonInput): Seq[Annotation] = deserializeTry(in).get
+
+ def deserializeTry(in: JsonInput): Try[Seq[Annotation]] = Try({
+ def throwError() = throw new InvalidAnnotationFileException(prettifyJsonInput(in))
+ val parsed = parse(in)
+ val annos = parsed match {
+ case JArray(objs) => objs
+ case _ => throwError()
+ }
+ // Gather classes so we can deserialize arbitrary Annotations
+ val classes = annos.map({
+ case JObject(("class", JString(c)) :: tail) => c
+ case _ => throwError()
+ }).distinct
+ val loaded = classes.map(Class.forName(_).asInstanceOf[Class[_ <: Annotation]])
+ implicit val formats = jsonFormat(loaded)
+ read[List[Annotation]](in)
+ })
+}
diff --git a/src/main/scala/firrtl/annotations/Named.scala b/src/main/scala/firrtl/annotations/Named.scala
index 0e365249..d3a70643 100644
--- a/src/main/scala/firrtl/annotations/Named.scala
+++ b/src/main/scala/firrtl/annotations/Named.scala
@@ -10,7 +10,6 @@ import AnnotationUtils.{validModuleName, validComponentName, toExp}
* Named classes associate an annotation with a component in a Firrtl circuit
*/
sealed trait Named {
- def name: String
def serialize: String
}