diff options
| author | Jack Koenig | 2018-02-27 18:07:11 -0800 |
|---|---|---|
| committer | GitHub | 2018-02-27 18:07:11 -0800 |
| commit | c7eb1570dfb1c7701ea32d1209982a053f3cec1d (patch) | |
| tree | 3f509b202d82841c5dad5588d1f953a25d389b44 /src/main/scala/firrtl/annotations | |
| parent | b90fc784a1819c1d7905910130a7da022214bc22 (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')
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 } |
