diff options
| author | mergify[bot] | 2022-10-17 22:50:55 +0000 |
|---|---|---|
| committer | GitHub | 2022-10-17 22:50:55 +0000 |
| commit | 1957f5ef5c43439144cf779a343707872ca92d6a (patch) | |
| tree | a13298b8b9fbe0d2130800e25e2faf75e5891487 /plugin/src/main/scala | |
| parent | 1e9f4e99167d1bc132a1a1a04e6987c2161c4d0e (diff) | |
Add opt-in AutoCloneType for Records (backport #2781) (#2785)
* Add opt-in AutoCloneType for Records (#2781)
There is a new trait, chisel3.experimental.AutoCloneType that is mixed
in to Bundle and can optionally be mixed in to user-defined Records. The
compiler plugin prints a deprecation warning on any user-defined
implementation of cloneType, telling the user to mix in AutoCloneType
before upgrading to 3.6.
(cherry picked from commit a234fd48ac8f5942c38fef5797292014e407b586)
# Conflicts:
# core/src/main/scala/chisel3/Aggregate.scala
# plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala
* Resolve backport conflicts
* Do not make MixedVec extend AutoCloneType
It is a binary incompatible change that can wait for 3.6.
* Waive MiMa false positives
Co-authored-by: Jack Koenig <koenig@sifive.com>
Diffstat (limited to 'plugin/src/main/scala')
| -rw-r--r-- | plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala | 278 |
1 files changed, 152 insertions, 126 deletions
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) } |
