summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.sbt7
-rw-r--r--core/src/main/scala/chisel3/Aggregate.scala32
-rw-r--r--plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala278
-rw-r--r--src/test/scala/chiselTests/AutoClonetypeSpec.scala41
4 files changed, 222 insertions, 136 deletions
diff --git a/build.sbt b/build.sbt
index 49643118..b958b19a 100644
--- a/build.sbt
+++ b/build.sbt
@@ -143,6 +143,13 @@ lazy val plugin = (project in file("plugin")).
}
).
settings(
+ // Given that the plugin is 1) a compile-time only dependency and 2) package chisel3.internal,
+ // I'm not really sure why we both checking binary compatbility
+ mimaBinaryIssueFilters ++= Seq(
+ // MyTypingTransformer is private (https://github.com/lightbend/mima/issues/53)
+ ProblemFilters.exclude[DirectMissingMethodProblem]("chisel3.internal.plugin.BundleComponent#MyTypingTransformer.isBundle"),
+ ProblemFilters.exclude[DirectMissingMethodProblem]("chisel3.internal.plugin.BundleComponent#MyTypingTransformer.getConstructorAndParams")
+ ),
mimaPreviousArtifacts := {
// There are not yet artifacts for 2.12.17, 2.13.9, nor 2.13.10; suppress until 3.5.5 is released
val skipVersions = Seq("2.12.17", "2.13.9", "2.13.10")
diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala
index 042e78b1..aacf0b1c 100644
--- a/core/src/main/scala/chisel3/Aggregate.scala
+++ b/core/src/main/scala/chisel3/Aggregate.scala
@@ -1158,6 +1158,16 @@ abstract class Record(private[chisel3] implicit val compileOptions: CompileOptio
* Results in "`\$className(elt0.name -> elt0.value, ...)`"
*/
def toPrintable: Printable = toPrintableHelper(elements.toList)
+
+ /** Implementation of cloneType that is [optionally for Record] overridden by the compiler plugin
+ *
+ * @note This should _never_ be overridden or called in user-code
+ */
+ protected def _cloneTypeImpl: Record = {
+ throwException(
+ s"Internal Error! This should have been implemented by the chisel3-plugin. Please file an issue against chisel3"
+ )
+ }
}
/**
@@ -1182,6 +1192,15 @@ package experimental {
class BundleLiteralException(message: String) extends ChiselException(message)
class VecLiteralException(message: String) extends ChiselException(message)
+ /** Indicates that the compiler plugin should generate [[cloneType]] for this type
+ *
+ * All user-defined [[Record]]s should mix this trait in as it will be required for upgrading to Chisel 3.6.
+ */
+ trait AutoCloneType { self: Record =>
+
+ override def cloneType: this.type = _cloneTypeImpl.asInstanceOf[this.type]
+
+ }
}
/** Base class for data types defined as a bundle of other data types.
@@ -1217,7 +1236,7 @@ package experimental {
* }
* }}}
*/
-abstract class Bundle(implicit compileOptions: CompileOptions) extends Record {
+abstract class Bundle(implicit compileOptions: CompileOptions) extends Record with experimental.AutoCloneType {
assert(
_usingPlugin,
"The Chisel compiler plugin is now required for compiling Chisel code. " +
@@ -1385,15 +1404,8 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record {
clone
}
- /** Implementation of cloneType using runtime reflection. This should _never_ be overridden or called in user-code
- *
- * @note This is overridden by the compiler plugin (this implementation is never called)
- */
- protected def _cloneTypeImpl: Bundle = {
- throwException(
- s"Internal Error! This should have been implemented by the chisel3-plugin. Please file an issue against chisel3"
- )
- }
+ // This is overriden for binary compatibility reasons in 3.5
+ override protected def _cloneTypeImpl: Bundle = super._cloneTypeImpl.asInstanceOf[Bundle]
/** Default "pretty-print" implementation
* Analogous to printing a Map
diff --git a/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala b/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala
index eca3b158..f9452f5a 100644
--- a/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala
+++ b/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala
@@ -42,6 +42,8 @@ private[plugin] class BundleComponent(val global: Global, arguments: ChiselPlugi
def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe
val bundleTpe: Type = inferType(tq"chisel3.Bundle")
+ val recordTpe: Type = inferType(tq"chisel3.Record")
+ val autoCloneTpe: Type = inferType(tq"chisel3.experimental.AutoCloneType")
val dataTpe: Type = inferType(tq"chisel3.Data")
val ignoreSeqTpe: Type = inferType(tq"chisel3.IgnoreSeqInBundle")
val seqOfDataTpe: Type = inferType(tq"scala.collection.Seq[chisel3.Data]")
@@ -49,7 +51,11 @@ private[plugin] class BundleComponent(val global: Global, arguments: ChiselPlugi
val itStringAnyTpe: Type = inferType(tq"scala.collection.Iterable[(String,Any)]")
// Not cached because it should only be run once per class (thus once per Type)
- def isBundle(sym: Symbol): Boolean = { sym.tpe <:< bundleTpe }
+ def isABundle(sym: Symbol): Boolean = { sym.tpe <:< bundleTpe }
+
+ def isARecord(sym: Symbol): Boolean = { sym.tpe <:< recordTpe }
+
+ def isAnAutoCloneType(sym: Symbol): Boolean = { sym.tpe <:< autoCloneTpe }
def isIgnoreSeqInBundle(sym: Symbol): Boolean = { sym.tpe <:< ignoreSeqTpe }
@@ -86,7 +92,7 @@ private[plugin] class BundleComponent(val global: Global, arguments: ChiselPlugi
def isVarArgs(sym: Symbol): Boolean = definitions.isRepeatedParamType(sym.tpe)
- def getConstructorAndParams(body: List[Tree]): (Option[DefDef], Seq[Symbol]) = {
+ def getConstructorAndParams(body: List[Tree], isBundle: Boolean): (Option[DefDef], Seq[Symbol]) = {
val paramAccessors = mutable.ListBuffer[Symbol]()
var primaryConstructor: Option[DefDef] = None
body.foreach {
@@ -96,154 +102,174 @@ private[plugin] class BundleComponent(val global: Global, arguments: ChiselPlugi
primaryConstructor = Some(con)
case d: DefDef if isNullaryMethodNamed("_cloneTypeImpl", d) =>
val msg = "Users cannot override _cloneTypeImpl. Let the compiler plugin generate it."
- global.globalError(d.pos, msg)
- case d: DefDef if isNullaryMethodNamed("_elementsImpl", d) =>
+ global.reporter.error(d.pos, msg)
+ case d: DefDef if isNullaryMethodNamed("_elementsImpl", d) && isBundle =>
val msg = "Users cannot override _elementsImpl. Let the compiler plugin generate it."
- global.globalError(d.pos, msg)
- case d: DefDef if isNullaryMethodNamed("_usingPlugin", d) =>
+ global.reporter.error(d.pos, msg)
+ case d: DefDef if isNullaryMethodNamed("_usingPlugin", d) && isBundle =>
val msg = "Users cannot override _usingPlugin, it is for the compiler plugin's use only."
- global.globalError(d.pos, msg)
+ global.reporter.error(d.pos, msg)
case d: DefDef if isNullaryMethodNamed("cloneType", d) =>
- val msg = "Users cannot override cloneType. Let the compiler plugin generate it."
- global.globalError(d.pos, msg)
+ val prefix = if (isBundle) "Bundles" else "Records extending AutoCloneType"
+ val msg = s"$prefix cannot override cloneType. Let the compiler plugin generate it."
+ global.reporter.error(d.pos, msg)
case _ =>
}
(primaryConstructor, paramAccessors.toList)
}
- override def transform(tree: Tree): Tree = tree match {
-
- case bundle: ClassDef if isBundle(bundle.symbol) && !bundle.mods.hasFlag(Flag.ABSTRACT) =>
- // ==================== Generate _cloneTypeImpl ====================
- val (con, params) = getConstructorAndParams(bundle.impl.body)
- if (con.isEmpty) {
- global.reporter.warning(bundle.pos, "Unable to determine primary constructor!")
- return super.transform(tree)
- }
+ def warnOnCloneType(body: List[Tree]): Unit = {
+ body.foreach {
+ case d: DefDef if isNullaryMethodNamed("cloneType", d) =>
+ val msg = "It is no longer necessary to implement cloneType. " +
+ "Mix in chisel3.experimental.AutoCloneType to let the compiler plugin generate it. " +
+ "This will become an error in Chisel 3.6."
+ global.reporter.warning(d.pos, msg)
+ case _ => // Do nothing
+ }
+ }
- val constructor = con.get
- val thiz = gen.mkAttributedThis(bundle.symbol)
-
- // The params have spaces after them (Scalac implementation detail)
- val paramLookup: String => Symbol = params.map(sym => sym.name.toString.trim -> sym).toMap
-
- val cloneTypeImplOpt = if (!bundle.mods.hasFlag(Flag.ABSTRACT)) {
- // Create a this.<ref> for each field matching order of constructor arguments
- // List of Lists because we can have multiple parameter lists
- val conArgs: List[List[Tree]] =
- constructor.vparamss.map(_.map { vp =>
- val p = paramLookup(vp.name.toString)
- // Make this.<ref>
- val select = gen.mkAttributedSelect(thiz.asInstanceOf[Tree], p)
- // Clone any Data parameters to avoid field aliasing, need full clone to include direction
- val cloned = if (isData(vp.symbol)) cloneTypeFull(select.asInstanceOf[Tree]) else select
- // Need to splat varargs
- if (isVarArgs(vp.symbol)) q"$cloned: _*" else cloned
- })
-
- val tparamList = bundle.tparams.map { t => Ident(t.symbol) }
- val ttpe =
- if (tparamList.nonEmpty) AppliedTypeTree(Ident(bundle.symbol), tparamList) else Ident(bundle.symbol)
- val newUntyped = New(ttpe, conArgs)
- val neww = localTyper.typed(newUntyped)
-
- // Create the symbol for the method and have it be associated with the Bundle class
- val cloneTypeSym =
- bundle.symbol.newMethod(TermName("_cloneTypeImpl"), bundle.symbol.pos.focus, Flag.OVERRIDE | Flag.PROTECTED)
- // Handwritten cloneTypes don't have the Method flag set, unclear if it matters
- cloneTypeSym.resetFlag(Flags.METHOD)
- // Need to set the type to chisel3.Bundle for the override to work
- cloneTypeSym.setInfo(NullaryMethodType(bundleTpe))
-
- Some(localTyper.typed(DefDef(cloneTypeSym, neww)))
- } else {
- // Don't create if this Bundle is abstract
- None
- }
+ def generateAutoCloneType(record: ClassDef, thiz: global.This, isBundle: Boolean): Option[Tree] = {
+ val (con, params) = getConstructorAndParams(record.impl.body, isBundle)
+ if (con.isEmpty) {
+ global.reporter.warning(record.pos, "Unable to determine primary constructor!")
+ return None
+ }
- // ==================== Generate val elements ====================
+ val constructor = con.get
+
+ // The params have spaces after them (Scalac implementation detail)
+ val paramLookup: String => Symbol = params.map(sym => sym.name.toString.trim -> sym).toMap
+
+ // Create a this.<ref> for each field matching order of constructor arguments
+ // List of Lists because we can have multiple parameter lists
+ val conArgs: List[List[Tree]] =
+ constructor.vparamss.map(_.map { vp =>
+ val p = paramLookup(vp.name.toString)
+ // Make this.<ref>
+ val select = gen.mkAttributedSelect(thiz.asInstanceOf[Tree], p)
+ // Clone any Data parameters to avoid field aliasing, need full clone to include direction
+ val cloned = if (isData(vp.symbol)) cloneTypeFull(select.asInstanceOf[Tree]) else select
+ // Need to splat varargs
+ if (isVarArgs(vp.symbol)) q"$cloned: _*" else cloned
+ })
+
+ val tparamList = record.tparams.map { t => Ident(t.symbol) }
+ val ttpe =
+ if (tparamList.nonEmpty) AppliedTypeTree(Ident(record.symbol), tparamList) else Ident(record.symbol)
+ val newUntyped = New(ttpe, conArgs)
+ val neww = localTyper.typed(newUntyped)
+
+ // Create the symbol for the method and have it be associated with the Record class
+ val cloneTypeSym =
+ record.symbol.newMethod(TermName("_cloneTypeImpl"), record.symbol.pos.focus, Flag.OVERRIDE | Flag.PROTECTED)
+ // Handwritten cloneTypes don't have the Method flag set, unclear if it matters
+ cloneTypeSym.resetFlag(Flags.METHOD)
+
+ // Need to set the return type correctly for the override to work
+ // For binary compatibility reasons in 3.5, Bundles have to return chisel3.Bundle
+ val returnType = if (isBundle) bundleTpe else recordTpe
+ cloneTypeSym.setInfo(NullaryMethodType(returnType))
+
+ Some(localTyper.typed(DefDef(cloneTypeSym, neww)))
+ }
- /* Test to see if the bundle found is amenable to having it's elements
- * converted to an immediate form that will not require reflection
- */
- def isSupportedBundleType: Boolean = {
- arguments.genBundleElements && !bundle.mods.hasFlag(Flag.ABSTRACT)
+ def generateElements(bundle: ClassDef, thiz: global.This): Tree = {
+ /* extract the true fields from the super classes a given bundle
+ * depth argument can be helpful for debugging
+ */
+ def getAllBundleFields(bundleSymbol: Symbol, depth: Int = 0): List[(String, Tree)] = {
+
+ def isBundleField(member: Symbol): Boolean = {
+ if (!member.isAccessor) {
+ false
+ } else if (isData(member.tpe.typeSymbol)) {
+ true
+ } else if (isOptionOfData(member)) {
+ true
+ } else if (isSeqOfData(member)) {
+ // This field is passed along, even though it is illegal
+ // An error for this will be generated in `Bundle.elements`
+ // It would be possible here to check for Seq[Data] and make a compiler error, but
+ // that would be a API error difference. See reference in docs/chisel-plugin.md
+ // If Bundle is subclass of IgnoreSeqInBundle then don't pass this field along
+
+ !isIgnoreSeqInBundle(bundleSymbol)
+ } else {
+ // none of the above
+ false
+ }
}
- val elementsImplOpt = if (isSupportedBundleType) {
- /* extract the true fields from the super classes a given bundle
- * depth argument can be helpful for debugging
- */
- def getAllBundleFields(bundleSymbol: Symbol, depth: Int = 0): List[(String, Tree)] = {
-
- def isBundleField(member: Symbol): Boolean = {
- if (!member.isAccessor) {
- false
- } else if (isData(member.tpe.typeSymbol)) {
- true
- } else if (isOptionOfData(member)) {
- true
- } else if (isSeqOfData(member)) {
- // This field is passed along, even though it is illegal
- // An error for this will be generated in `Bundle.elements`
- // It would be possible here to check for Seq[Data] and make a compiler error, but
- // that would be a API error difference. See reference in docs/chisel-plugin.md
- // If Bundle is subclass of IgnoreSeqInBundle then don't pass this field along
-
- !isIgnoreSeqInBundle(bundleSymbol)
- } else {
- // none of the above
- false
- }
- }
+ val currentFields = bundleSymbol.info.members.flatMap {
- val currentFields = bundleSymbol.info.members.flatMap {
-
- case member if member.isPublic =>
- if (isBundleField(member)) {
- // The params have spaces after them (Scalac implementation detail)
- Some(member.name.toString.trim -> gen.mkAttributedSelect(thiz.asInstanceOf[Tree], member))
- } else {
- None
- }
-
- case _ => None
- }.toList
-
- val allParentFields = bundleSymbol.parentSymbols.flatMap { parentSymbol =>
- val fieldsFromParent = if (depth < 1 && !isExactBundle(bundleSymbol)) {
- val foundFields = getAllBundleFields(parentSymbol, depth + 1)
- foundFields
- } else {
- List()
- }
- fieldsFromParent
+ case member if member.isPublic =>
+ if (isBundleField(member)) {
+ // The params have spaces after them (Scalac implementation detail)
+ Some(member.name.toString.trim -> gen.mkAttributedSelect(thiz.asInstanceOf[Tree], member))
+ } else {
+ None
}
- allParentFields ++ currentFields
+
+ case _ => None
+ }.toList
+
+ val allParentFields = bundleSymbol.parentSymbols.flatMap { parentSymbol =>
+ val fieldsFromParent = if (depth < 1 && !isExactBundle(bundleSymbol)) {
+ val foundFields = getAllBundleFields(parentSymbol, depth + 1)
+ foundFields
+ } else {
+ List()
}
+ fieldsFromParent
+ }
+ allParentFields ++ currentFields
+ }
- val elementArgs = getAllBundleFields(bundle.symbol)
+ val elementArgs = getAllBundleFields(bundle.symbol)
- val elementsImplSym =
- bundle.symbol.newMethod(TermName("_elementsImpl"), bundle.symbol.pos.focus, Flag.OVERRIDE | Flag.PROTECTED)
- elementsImplSym.resetFlag(Flags.METHOD)
- elementsImplSym.setInfo(NullaryMethodType(itStringAnyTpe))
+ val elementsImplSym =
+ bundle.symbol.newMethod(TermName("_elementsImpl"), bundle.symbol.pos.focus, Flag.OVERRIDE | Flag.PROTECTED)
+ elementsImplSym.resetFlag(Flags.METHOD)
+ elementsImplSym.setInfo(NullaryMethodType(itStringAnyTpe))
- val elementsImpl = localTyper.typed(
- DefDef(elementsImplSym, q"scala.collection.immutable.Vector.apply[(String, Any)](..$elementArgs)")
- )
+ val elementsImpl = localTyper.typed(
+ DefDef(elementsImplSym, q"scala.collection.immutable.Vector.apply[(String, Any)](..$elementArgs)")
+ )
- Some(elementsImpl)
- } else {
- // No code generated for elements accessor
- None
+ elementsImpl
+ }
+
+ override def transform(tree: Tree): Tree = tree match {
+
+ case record: ClassDef if isARecord(record.symbol) && !record.mods.hasFlag(Flag.ABSTRACT) =>
+ val isBundle: Boolean = isABundle(record.symbol)
+ val isAutoCloneType: Boolean = isAnAutoCloneType(record.symbol)
+
+ if (!isAutoCloneType) {
+ warnOnCloneType(record.impl.body)
+ // Other than warning, there is nothing to do on Records that don't mixin AutoCloneType
+ return super.transform(record)
}
+ val thiz: global.This = gen.mkAttributedThis(record.symbol)
+
+ // ==================== Generate _cloneTypeImpl ====================
+ val cloneTypeImplOpt = generateAutoCloneType(record, thiz, isBundle)
+
+ // ==================== Generate val elements (Bundles only) ====================
+ val elementsImplOpt =
+ if (isBundle && arguments.genBundleElements) Some(generateElements(record, thiz)) else None
+
// ==================== Generate _usingPlugin ====================
- // Unclear why quasiquotes work here but didn't for cloneTypeSym, maybe they could.
- val usingPluginOpt = Some(localTyper.typed(q"override protected def _usingPlugin: Boolean = true"))
+ val usingPluginOpt = if (isBundle) {
+ // Unclear why quasiquotes work here but didn't for cloneTypeSym, maybe they could.
+ Some(localTyper.typed(q"override protected def _usingPlugin: Boolean = true"))
+ } else {
+ None
+ }
- val withMethods = deriveClassDef(bundle) { t =>
+ val withMethods = deriveClassDef(record) { t =>
deriveTemplate(t)(_ ++ cloneTypeImplOpt ++ usingPluginOpt ++ elementsImplOpt)
}
diff --git a/src/test/scala/chiselTests/AutoClonetypeSpec.scala b/src/test/scala/chiselTests/AutoClonetypeSpec.scala
index 5d2cd496..353ae58c 100644
--- a/src/test/scala/chiselTests/AutoClonetypeSpec.scala
+++ b/src/test/scala/chiselTests/AutoClonetypeSpec.scala
@@ -6,6 +6,8 @@ import chisel3._
import chisel3.testers.TestUtils
import chisel3.util.QueueIO
import chisel3.stage.ChiselStage.elaborate
+import chisel3.experimental.AutoCloneType
+import scala.collection.immutable.ListMap
class BundleWithIntArg(val i: Int) extends Bundle {
val out = UInt(i.W)
@@ -72,6 +74,25 @@ class InheritingBundle extends QueueIO(UInt(8.W), 8) {
val error = Output(Bool())
}
+class RecordAutoCloneType[T <: Data](gen: T) extends Record with AutoCloneType {
+ lazy val elements = ListMap("value" -> gen)
+ // This is a weird thing to do, but as only Bundles have these methods, it should be legal
+ protected def _elementsImpl: Iterable[(String, Any)] = elements
+ protected def _usingPlugin = false
+}
+
+// Records that don't mixin AutoCloneType should still be able to implement the related methods
+// NOTE: This is a very weird thing to do, don't do it.
+class RecordWithVerbotenMethods(w: Int) extends Record {
+ lazy val elements = ListMap("value" -> UInt(w.W))
+ override def cloneType: this.type = (new RecordWithVerbotenMethods(w)).asInstanceOf[this.type]
+ // Verboten methods
+ protected def _usingPlugin = false
+ protected override def _cloneTypeImpl = this.cloneType
+
+ protected def _elementsImpl: Iterable[(String, Any)] = Nil
+}
+
class AutoClonetypeSpec extends ChiselFlatSpec with Utils {
"Bundles with Scala args" should "not need clonetype" in {
@@ -400,4 +421,24 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils {
}
elaborate(new MyModule)
}
+
+ it should "support Records that mixin AutoCloneType" in {
+ class MyModule extends Module {
+ val gen = new RecordAutoCloneType(UInt(8.W))
+ val in = IO(Input(gen))
+ val out = IO(Output(gen))
+ out := in
+ }
+ elaborate(new MyModule)
+ }
+
+ it should "support Records that don't mixin AutoCloneType and use forbidden methods" in {
+ class MyModule extends Module {
+ val gen = new RecordWithVerbotenMethods(8)
+ val in = IO(Input(gen))
+ val out = IO(Output(gen))
+ out := in
+ }
+ elaborate(new MyModule)
+ }
}