Skip to content

Commit 04e1528

Browse files
authored
Emit annotations in the .fir file (#3180)
This commit reimplements the previous work adding emission of annotations via the FIRRTL specification v2.0.0 %[[...]] style. Now all annotations that were previously emitted to the .anno.json file are emitted in the .fir file. This uses a slightly hacky new internal mechanism in firrtl that is already marked deprecated to discourage users from touching it.
1 parent 0f3fd05 commit 04e1528

File tree

22 files changed

+269
-168
lines changed

22 files changed

+269
-168
lines changed

build.sbt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ lazy val warningSuppression = Seq(
6666
"msg=migration to the MLIR:s",
6767
"msg=method hasDefiniteSize in trait IterableOnceOps is deprecated:s", // replacement `knownSize` is not in 2.12
6868
"msg=object JavaConverters in package collection is deprecated:s",
69-
"msg=undefined in comment for method cf in class PrintableHelper:s"
69+
"msg=undefined in comment for method cf in class PrintableHelper:s",
70+
// This is deprecated for external users but not internal use
71+
"cat=deprecation&origin=firrtl\\.options\\.internal\\.WriteableCircuitAnnotation:s"
7072
).mkString(",")
7173
)
7274

core/src/main/scala/chisel3/internal/firrtl/Converter.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,11 +374,11 @@ private[chisel3] object Converter {
374374
}
375375

376376
def convert(circuit: Circuit): fir.Circuit =
377-
fir.Circuit(fir.NoInfo, circuit.components.map(convert), circuit.name, circuit.firrtlAnnotations.toSeq)
377+
fir.Circuit(fir.NoInfo, circuit.components.map(convert), circuit.name)
378378

379379
// TODO Unclear if this should just be the default
380380
def convertLazily(circuit: Circuit): fir.Circuit = {
381381
val lazyModules = LazyList() ++ circuit.components
382-
fir.Circuit(fir.NoInfo, lazyModules.map(convert), circuit.name, circuit.firrtlAnnotations.toSeq)
382+
fir.Circuit(fir.NoInfo, lazyModules.map(convert), circuit.name)
383383
}
384384
}

firrtl/src/main/scala/firrtl/ir/IR.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
package firrtl
44
package ir
55

6+
import firrtl.annotations.Annotation
7+
68
import dataclass.{data, since}
79
import org.apache.commons.text.translate.{AggregateTranslator, JavaUnicodeEscaper, LookupTranslator}
810

@@ -543,7 +545,6 @@ case class IntModule(
543545
extends DefModule
544546
with UseSerializer
545547

546-
case class Circuit(info: Info, modules: Seq[DefModule], main: String, annotations: AnnotationSeq)
547-
extends FirrtlNode
548-
with HasInfo
549-
with UseSerializer
548+
case class Circuit(info: Info, modules: Seq[DefModule], main: String) extends FirrtlNode with HasInfo with UseSerializer
549+
550+
case class CircuitWithAnnos(circuit: Circuit, annotations: Seq[Annotation]) extends FirrtlNode with UseSerializer

firrtl/src/main/scala/firrtl/ir/Serializer.scala

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
package firrtl.ir
44

5-
import firrtl.annotations.JsonProtocol
5+
import firrtl.annotations.{Annotation, JsonProtocol}
66

77
case class Version(major: Int, minor: Int, patch: Int) {
88
def serialize: String = s"$major.$minor.$patch"
@@ -15,7 +15,7 @@ object Serializer {
1515
val Indent = " "
1616

1717
// The version supported by the serializer.
18-
val version = Version(1, 2, 0)
18+
val version = Version(2, 0, 0)
1919

2020
/** Converts a `FirrtlNode` into its string representation with
2121
* default indentation.
@@ -28,19 +28,20 @@ object Serializer {
2828
def serialize(node: FirrtlNode, indent: Int): String = {
2929
val builder = new StringBuilder()
3030
node match {
31-
case n: Info => s(n)(builder, indent)
32-
case n: StringLit => s(n)(builder, indent)
33-
case n: Expression => s(n)(builder, indent)
34-
case n: Statement => builder ++= lazily(n, indent).mkString
35-
case n: Width => s(n)(builder, indent)
36-
case n: Orientation => s(n)(builder, indent)
37-
case n: Field => s(n)(builder, indent)
38-
case n: Type => s(n)(builder, indent)
39-
case n: Direction => s(n)(builder, indent)
40-
case n: Port => s(n)(builder, indent)
41-
case n: Param => s(n)(builder, indent)
42-
case n: DefModule => builder ++= lazily(n, indent).mkString
43-
case n: Circuit => builder ++= lazily(n, indent).mkString
31+
case n: Info => s(n)(builder, indent)
32+
case n: StringLit => s(n)(builder, indent)
33+
case n: Expression => s(n)(builder, indent)
34+
case n: Statement => builder ++= lazily(n, indent).mkString
35+
case n: Width => s(n)(builder, indent)
36+
case n: Orientation => s(n)(builder, indent)
37+
case n: Field => s(n)(builder, indent)
38+
case n: Type => s(n)(builder, indent)
39+
case n: Direction => s(n)(builder, indent)
40+
case n: Port => s(n)(builder, indent)
41+
case n: Param => s(n)(builder, indent)
42+
case n: DefModule => builder ++= lazily(n, indent).mkString
43+
case n: Circuit => builder ++= lazily(n, indent).mkString
44+
case n: CircuitWithAnnos => builder ++= lazily(n, indent).mkString
4445
case other => builder ++= other.serialize // Handle user-defined nodes
4546
}
4647
builder.toString()
@@ -62,9 +63,10 @@ object Serializer {
6263
*/
6364
def lazily(node: FirrtlNode, indent: Int): Iterable[String] = new Iterable[String] {
6465
def iterator = node match {
65-
case n: Statement => sIt(n)(indent)
66-
case n: DefModule => sIt(n)(indent)
67-
case n: Circuit => sIt(n)(indent)
66+
case n: Statement => sIt(n)(indent)
67+
case n: DefModule => sIt(n)(indent)
68+
case n: Circuit => sIt(n)(indent)
69+
case n: CircuitWithAnnos => sIt(n)(indent)
6870
case other => Iterator(serialize(other, indent))
6971
}
7072
}.view // TODO replace .view with constructing a view directly above, but must drop 2.12 first.
@@ -373,25 +375,29 @@ object Serializer {
373375
Iterator(Indent * indent, other.serialize) // Handle user-defined nodes
374376
}
375377

376-
private def sIt(node: Circuit)(implicit indent: Int): Iterator[String] = node match {
377-
case Circuit(info, modules, main, annotations) =>
378-
val prelude = {
379-
implicit val b = new StringBuilder // Scope this so we don't accidentally pass it anywhere
380-
b ++= s"FIRRTL version ${version.serialize}\n"
381-
b ++= "circuit "; b ++= main; b ++= " :";
382-
if (annotations.nonEmpty) {
383-
b ++= "%["; b ++= JsonProtocol.serialize(annotations); b ++= "]";
384-
}
385-
s(info)
386-
b.toString
378+
private def sIt(node: Circuit)(implicit indent: Int): Iterator[String] =
379+
sIt(CircuitWithAnnos(node, Nil))
380+
381+
// TODO make Annotation serialization lazy
382+
private def sIt(node: CircuitWithAnnos)(implicit indent: Int): Iterator[String] = {
383+
val CircuitWithAnnos(circuit, annotations) = node
384+
val prelude = {
385+
implicit val b = new StringBuilder
386+
b ++= s"FIRRTL version ${version.serialize}\n"
387+
b ++= "circuit "; b ++= circuit.main; b ++= " :";
388+
if (annotations.nonEmpty) {
389+
b ++= "%["; b ++= JsonProtocol.serialize(annotations); b ++= "]";
387390
}
388-
Iterator(prelude) ++
389-
modules.iterator.zipWithIndex.flatMap {
390-
case (m, i) =>
391-
val newline = Iterator(if (i == 0) s"$NewLine" else s"${NewLine}${NewLine}")
392-
newline ++ sIt(m)(indent + 1)
393-
} ++
394-
Iterator(s"$NewLine")
391+
s(circuit.info)
392+
Iterator(b.toString)
393+
}
394+
prelude ++
395+
circuit.modules.iterator.zipWithIndex.flatMap {
396+
case (m, i) =>
397+
val newline = Iterator(if (i == 0) s"$NewLine" else s"${NewLine}${NewLine}")
398+
newline ++ sIt(m)(indent + 1)
399+
} ++
400+
Iterator(s"$NewLine")
395401
}
396402

397403
/** create a new line with the appropriate indent */
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package firrtl.options.internal
4+
5+
import firrtl.options.CustomFileEmission
6+
import firrtl.annotations.Annotation
7+
8+
import java.io.File
9+
10+
// Hack for enabling special emission of the ChiselCircuitAnnotation in WriteOutputAnnotations
11+
@deprecated("This trait is for internal use only. Do not use it.", "Chisel 5.0")
12+
trait WriteableCircuitAnnotation extends Annotation with CustomFileEmission {
13+
14+
protected def writeToFileImpl(file: File, annos: Seq[Annotation]): Unit
15+
16+
private[firrtl] final def writeToFile(file: File, annos: Seq[Annotation]): Unit = writeToFileImpl(file, annos)
17+
}

firrtl/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala

Lines changed: 81 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import firrtl.options.{
1414
Unserializable,
1515
Viewer
1616
}
17+
import firrtl.options.internal.WriteableCircuitAnnotation
1718

1819
import java.io.{BufferedOutputStream, File, FileOutputStream, PrintWriter}
1920

@@ -41,52 +42,93 @@ class WriteOutputAnnotations extends Phase {
4142
/** Write the input [[AnnotationSeq]] to a fie. */
4243
def transform(annotations: AnnotationSeq): AnnotationSeq = {
4344
val sopts = Viewer[StageOptions].view(annotations)
44-
val filesWritten = mutable.HashMap.empty[String, Annotation]
45-
val serializable: AnnotationSeq = annotations.toSeq.flatMap {
46-
case _: Unserializable => None
47-
case a: CustomFileEmission =>
48-
val filename = a.filename(annotations)
49-
val canonical = filename.getCanonicalPath()
45+
val filesToWrite = mutable.HashMap.empty[String, Annotation]
46+
// Grab the circuit annotation so we can write serializable annotations to it
47+
// We also must calculate the filename because the annotation will be deleted before calling
48+
// writeToFile
49+
var circuitAnnoOpt: Option[(File, WriteableCircuitAnnotation)] = None
50+
val serializable = annotations.flatMap { anno =>
51+
// Check for file clobbering
52+
anno match {
53+
case _: Unserializable =>
54+
case a: CustomFileEmission =>
55+
val filename = a.filename(annotations)
56+
val canonical = filename.getCanonicalPath()
5057

51-
filesWritten.get(canonical) match {
52-
case None =>
53-
val w = new BufferedOutputStream(new FileOutputStream(filename))
54-
a match {
55-
// Further optimized emission
56-
case buf: BufferedCustomFileEmission =>
57-
val it = buf.getBytesBuffered
58-
it.foreach(bytearr => w.write(bytearr))
59-
// Regular emission
60-
case _ =>
61-
a.getBytes match {
62-
case arr: mutable.ArraySeq[Byte] => w.write(arr.array.asInstanceOf[Array[Byte]])
63-
case other => other.foreach(w.write(_))
64-
}
65-
}
66-
w.close()
67-
filesWritten(canonical) = a
68-
case Some(first) =>
58+
filesToWrite.get(canonical) match {
59+
case None =>
60+
case Some(first) =>
61+
val msg =
62+
s"""|Multiple CustomFileEmission annotations would be serialized to the same file, '$canonical'
63+
| - first writer:
64+
| class: ${first.getClass.getName}
65+
| trimmed serialization: ${first.serialize.take(80)}
66+
| - second writer:
67+
| class: ${a.getClass.getName}
68+
| trimmed serialization: ${a.serialize.take(80)}
69+
|""".stripMargin
70+
throw new PhaseException(msg)
71+
}
72+
filesToWrite(canonical) = a
73+
case _ =>
74+
}
75+
// Write files with CustomFileEmission and filter out Unserializable ones
76+
anno match {
77+
case _: Unserializable => None
78+
case wca: WriteableCircuitAnnotation =>
79+
val filename = wca.filename(annotations)
80+
if (circuitAnnoOpt.nonEmpty) {
81+
val Some((firstFN, firstWCA)) = circuitAnnoOpt
6982
val msg =
70-
s"""|Multiple CustomFileEmission annotations would be serialized to the same file, '$canonical'
71-
| - first writer:
72-
| class: ${first.getClass.getName}
73-
| trimmed serialization: ${first.serialize.take(80)}
74-
| - second writer:
75-
| class: ${a.getClass.getName}
76-
| trimmed serialization: ${a.serialize.take(80)}
83+
s"""|Multiple circuit annotations found--only 1 is supported
84+
| - first circuit:
85+
| filename: $firstFN
86+
| trimmed serialization: ${firstWCA.serialize.take(80)}
87+
| - second circuit:
88+
| filename: $filename
89+
| trimmed serialization: ${wca.serialize.take(80)}
90+
|
7791
|""".stripMargin
7892
throw new PhaseException(msg)
79-
}
80-
a.replacements(filename)
81-
case a => Some(a)
93+
}
94+
circuitAnnoOpt = Some(filename -> wca)
95+
None
96+
case a: CustomFileEmission =>
97+
val filename = a.filename(annotations)
98+
val canonical = filename.getCanonicalPath()
99+
100+
val w = new BufferedOutputStream(new FileOutputStream(filename))
101+
a match {
102+
// Further optimized emission
103+
case buf: BufferedCustomFileEmission =>
104+
val it = buf.getBytesBuffered
105+
it.foreach(bytearr => w.write(bytearr))
106+
// Regular emission
107+
case _ =>
108+
a.getBytes match {
109+
case arr: mutable.ArraySeq[Byte] => w.write(arr.array.asInstanceOf[Array[Byte]])
110+
case other => other.foreach(w.write(_))
111+
}
112+
}
113+
w.close()
114+
a.replacements(filename)
115+
case a => Some(a)
116+
}
82117
}
83118

84-
sopts.annotationFileOut match {
119+
// If the circuit annotation exists, write annotations to it
120+
circuitAnnoOpt match {
121+
case Some((file, circuitAnno)) =>
122+
circuitAnno.writeToFile(file, serializable)
85123
case None =>
86-
case Some(file) =>
87-
val pw = new PrintWriter(sopts.getBuildFileName(file, Some(".anno.json")))
88-
pw.write(JsonProtocol.serialize(serializable))
89-
pw.close()
124+
// Otherwise, write to the old .anno.json
125+
sopts.annotationFileOut match {
126+
case None =>
127+
case Some(file) =>
128+
val pw = new PrintWriter(sopts.getBuildFileName(file, Some(".anno.json")))
129+
pw.write(JsonProtocol.serialize(serializable))
130+
pw.close()
131+
}
90132
}
91133

92134
annotations

firrtl/src/test/scala/firrtlTests/ExtModuleTests.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import firrtl.testutils._
88
class ExtModuleTests extends FirrtlFlatSpec {
99
"extmodule" should "serialize and re-parse equivalently" in {
1010
val input =
11-
"""|FIRRTL version 1.2.0
11+
"""|FIRRTL version 2.0.0
1212
|circuit Top :
1313
| extmodule Top :
1414
| input y : UInt<0>
@@ -38,8 +38,7 @@ class ExtModuleTests extends FirrtlFlatSpec {
3838
)
3939
)
4040
),
41-
"Top",
42-
Seq.empty
41+
"Top"
4342
)
4443

4544
circuit.serialize should be(input)

firrtl/src/test/scala/firrtlTests/SerializerSpec.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,7 @@ object SerializerSpec {
7979
childModuleIR,
8080
testModuleIR
8181
),
82-
"test",
83-
Seq.empty
82+
"test"
8483
)
8584

8685
}
@@ -113,8 +112,7 @@ object SMemTestCircuit {
113112
)
114113
)
115114
),
116-
"Example",
117-
Seq.empty
115+
"Example"
118116
)
119117

120118
def findRuw(c: Circuit): ReadUnderWrite.Value = {

firrtl/src/test/scala/firrtlTests/stage/FirrtlOptionsViewSpec.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ class FirrtlOptionsViewSpec extends AnyFlatSpec with Matchers {
3030
)
3131
)
3232
),
33-
main,
34-
Seq.empty
33+
main
3534
)
3635

3736
val grault: ir.Circuit = circuitIR("grault")

firrtl/src/test/scala/firrtlTests/stage/phases/ChecksSpec.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class ChecksSpec extends AnyFlatSpec with Matchers {
1414

1515
class Fixture { val phase: Phase = new Checks }
1616

17-
val inputCircuit = FirrtlCircuitAnnotation(firrtl.ir.Circuit(firrtl.ir.NoInfo, Seq.empty, "Foo", Seq.empty))
17+
val inputCircuit = FirrtlCircuitAnnotation(firrtl.ir.Circuit(firrtl.ir.NoInfo, Seq.empty, "Foo"))
1818
val outputFile = OutputFileAnnotation("bar")
1919
val outputAnnotationFile = OutputAnnotationFileAnnotation("baz")
2020
val infoMode = InfoModeAnnotation("ignore")

0 commit comments

Comments
 (0)