From e80e9a3ba775efd2121b17a39b6d712e1cf8fac2 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 10 Feb 2021 17:19:36 -0800 Subject: [plugin] Split ChiselComponent into its own file --- .../chisel3/internal/plugin/ChiselComponent.scala | 214 +++++++++++++++++++++ .../chisel3/internal/plugin/ChiselPlugin.scala | 213 +------------------- 2 files changed, 217 insertions(+), 210 deletions(-) create mode 100644 plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala (limited to 'plugin') diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala new file mode 100644 index 00000000..f86b8322 --- /dev/null +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal.plugin + +import scala.collection.mutable +import scala.reflect.internal.Flags +import scala.tools.nsc +import scala.tools.nsc.{Global, Phase} +import scala.tools.nsc.plugins.PluginComponent +import scala.tools.nsc.transform.TypingTransformers + +// The component of the chisel plugin. Not sure exactly what the difference is between +// a Plugin and a PluginComponent. +class ChiselComponent(val global: Global) extends PluginComponent with TypingTransformers { + import global._ + val runsAfter: List[String] = List[String]("typer") + val phaseName: String = "chiselcomponent" + def newPhase(_prev: Phase): ChiselComponentPhase = new ChiselComponentPhase(_prev) + class ChiselComponentPhase(prev: Phase) extends StdPhase(prev) { + override def name: String = phaseName + def apply(unit: CompilationUnit): Unit = { + // This plugin doesn't work on Scala 2.11. Rather than complicate the sbt build flow, + // instead we just check the version and if its an early Scala version, the plugin does nothing + if(scala.util.Properties.versionNumberString.split('.')(1).toInt >= 12) { + unit.body = new MyTypingTransformer(unit).transform(unit.body) + } + } + } + + class MyTypingTransformer(unit: CompilationUnit) + extends TypingTransformer(unit) { + + private def shouldMatchGen(bases: Tree*): Type => Boolean = { + val cache = mutable.HashMap.empty[Type, Boolean] + val baseTypes = bases.map(inferType) + + // If subtype of one of the base types, it's a match! + def terminate(t: Type): Boolean = baseTypes.exists(t <:< _) + + // Recurse through subtype hierarchy finding containers + // Seen is only updated when we recurse into type parameters, thus it is typically small + def recShouldMatch(s: Type, seen: Set[Type]): Boolean = { + def outerMatches(t: Type): Boolean = { + val str = t.toString + str.startsWith("Option[") || str.startsWith("Iterable[") + } + if (terminate(s)) { + true + } else if (seen.contains(s)) { + false + } else if (outerMatches(s)) { + // These are type parameters, loops *are* possible here + recShouldMatch(s.typeArgs.head, seen + s) + } else { + // This is the standard inheritance hierarchy, Scalac catches loops here + s.parents.exists( p => recShouldMatch(p, seen) ) + } + } + + // If doesn't match container pattern, exit early + def earlyExit(t: Type): Boolean = { + !(t.matchesPattern(inferType(tq"Iterable[_]")) || t.matchesPattern(inferType(tq"Option[_]"))) + } + + // Return function so that it captures the cache + { q: Type => + cache.getOrElseUpdate(q, { + // First check if a match, then check early exit, then recurse + if(terminate(q)){ + true + } else if(earlyExit(q)) { + false + } else { + recShouldMatch(q, Set.empty) + } + }) + } + } + + + private val shouldMatchData : Type => Boolean = shouldMatchGen(tq"chisel3.Data") + private val shouldMatchDataOrMem : Type => Boolean = shouldMatchGen(tq"chisel3.Data", tq"chisel3.MemBase[_]") + private val shouldMatchModule : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.BaseModule") + + // Given a type tree, infer the type and return it + private def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe + + // Indicates whether a ValDef is properly formed to get name + private def okVal(dd: ValDef): Boolean = { + + // These were found through trial and error + def okFlags(mods: Modifiers): Boolean = { + val badFlags = Set( + Flag.PARAM, + Flag.SYNTHETIC, + Flag.DEFERRED, + Flags.TRIEDCOOKING, + Flags.CASEACCESSOR, + Flags.PARAMACCESSOR + ) + badFlags.forall{ x => !mods.hasFlag(x)} + } + + // Ensure expression isn't null, as you can't call `null.autoName("myname")` + val isNull = dd.rhs match { + case Literal(Constant(null)) => true + case _ => false + } + + okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree + } + // TODO Unify with okVal + private def okUnapply(dd: ValDef): Boolean = { + + // These were found through trial and error + def okFlags(mods: Modifiers): Boolean = { + val badFlags = Set( + Flag.PARAM, + Flag.DEFERRED, + Flags.TRIEDCOOKING, + Flags.CASEACCESSOR, + Flags.PARAMACCESSOR + ) + val goodFlags = Set( + Flag.SYNTHETIC, + Flag.ARTIFACT + ) + goodFlags.forall(f => mods.hasFlag(f)) && badFlags.forall(f => !mods.hasFlag(f)) + } + + // Ensure expression isn't null, as you can't call `null.autoName("myname")` + val isNull = dd.rhs match { + case Literal(Constant(null)) => true + case _ => false + } + val tpe = inferType(dd.tpt) + definitions.isTupleType(tpe) && okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree + } + + private def findUnapplyNames(tree: Tree): Option[List[String]] = { + val applyArgs: Option[List[Tree]] = tree match { + case Match(_, List(CaseDef(_, _, Apply(_, args)))) => Some(args) + case _ => None + } + applyArgs.flatMap { args => + var ok = true + val result = mutable.ListBuffer[String]() + args.foreach { + case Ident(TermName(name)) => result += name + // Anything unexpected and we abort + case _ => ok = false + } + if (ok) Some(result.toList) else None + } + } + + // Whether this val is directly enclosed by a Bundle type + private def inBundle(dd: ValDef): Boolean = { + dd.symbol.logicallyEnclosingMember.thisType <:< inferType(tq"chisel3.Bundle") + } + + private def stringFromTermName(name: TermName): String = + name.toString.trim() // Remove trailing space (Scalac implementation detail) + + // Method called by the compiler to modify source tree + override def transform(tree: Tree): Tree = tree match { + // Check if a subtree is a candidate + case dd @ ValDef(mods, name, tpt, rhs) if okVal(dd) => + val tpe = inferType(tpt) + // If a Data and in a Bundle, just get the name but not a prefix + if (shouldMatchData(tpe) && inBundle(dd)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) // chisel3.internal.plugin.autoNameRecursively + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + } + // If a Data or a Memory, get the name and a prefix + else if (shouldMatchDataOrMem(tpe)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) + val prefixed = q"chisel3.experimental.prefix.apply[$tpt](name=$str)(f=$newRHS)" + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($prefixed)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + // If an instance, just get a name but no prefix + } else if (shouldMatchModule(tpe)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + } else { + // Otherwise, continue + super.transform(tree) + } + case dd @ ValDef(mods, name, tpt, rhs @ Match(_, _)) if okUnapply(dd) => + val tpe = inferType(tpt) + val fieldsOfInterest: List[Boolean] = tpe.typeArgs.map(shouldMatchData) + // Only transform if at least one field is of interest + if (fieldsOfInterest.reduce(_ || _)) { + findUnapplyNames(rhs) match { + case Some(names) => + val onames: List[Option[String]] = fieldsOfInterest.zip(names).map { case (ok, name) => if (ok) Some(name) else None } + val named = q"chisel3.internal.plugin.autoNameRecursivelyProduct($onames)($rhs)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + case None => // It's not clear how this could happen but we don't want to crash + super.transform(tree) + } + } else { + super.transform(tree) + } + // Otherwise, continue + case _ => super.transform(tree) + } + } +} \ No newline at end of file diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala index 59be7588..f8a1752c 100644 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala @@ -1,15 +1,10 @@ -// See LICENSE for license details. +// SPDX-License-Identifier: Apache-2.0 package chisel3.internal.plugin import scala.tools.nsc -import nsc.{Global, Phase} -import nsc.plugins.Plugin -import nsc.plugins.PluginComponent -import scala.reflect.internal.Flags -import scala.tools.nsc.transform.TypingTransformers - -import scala.collection.mutable +import nsc.Global +import nsc.plugins.{Plugin, PluginComponent} // The plugin to be run by the Scala compiler during compilation of Chisel code class ChiselPlugin(val global: Global) extends Plugin { @@ -18,206 +13,4 @@ class ChiselPlugin(val global: Global) extends Plugin { val components: List[PluginComponent] = List[PluginComponent](new ChiselComponent(global)) } -// The component of the chisel plugin. Not sure exactly what the difference is between -// a Plugin and a PluginComponent. -class ChiselComponent(val global: Global) extends PluginComponent with TypingTransformers { - import global._ - val runsAfter: List[String] = List[String]("typer") - val phaseName: String = "chiselcomponent" - def newPhase(_prev: Phase): ChiselComponentPhase = new ChiselComponentPhase(_prev) - class ChiselComponentPhase(prev: Phase) extends StdPhase(prev) { - override def name: String = phaseName - def apply(unit: CompilationUnit): Unit = { - // This plugin doesn't work on Scala 2.11. Rather than complicate the sbt build flow, - // instead we just check the version and if its an early Scala version, the plugin does nothing - if(scala.util.Properties.versionNumberString.split('.')(1).toInt >= 12) { - unit.body = new MyTypingTransformer(unit).transform(unit.body) - } - } - } - - class MyTypingTransformer(unit: CompilationUnit) - extends TypingTransformer(unit) { - - private def shouldMatchGen(bases: Tree*): Type => Boolean = { - val cache = mutable.HashMap.empty[Type, Boolean] - val baseTypes = bases.map(inferType) - - // If subtype of one of the base types, it's a match! - def terminate(t: Type): Boolean = baseTypes.exists(t <:< _) - - // Recurse through subtype hierarchy finding containers - // Seen is only updated when we recurse into type parameters, thus it is typically small - def recShouldMatch(s: Type, seen: Set[Type]): Boolean = { - def outerMatches(t: Type): Boolean = { - val str = t.toString - str.startsWith("Option[") || str.startsWith("Iterable[") - } - if (terminate(s)) { - true - } else if (seen.contains(s)) { - false - } else if (outerMatches(s)) { - // These are type parameters, loops *are* possible here - recShouldMatch(s.typeArgs.head, seen + s) - } else { - // This is the standard inheritance hierarchy, Scalac catches loops here - s.parents.exists( p => recShouldMatch(p, seen) ) - } - } - - // If doesn't match container pattern, exit early - def earlyExit(t: Type): Boolean = { - !(t.matchesPattern(inferType(tq"Iterable[_]")) || t.matchesPattern(inferType(tq"Option[_]"))) - } - - // Return function so that it captures the cache - { q: Type => - cache.getOrElseUpdate(q, { - // First check if a match, then check early exit, then recurse - if(terminate(q)){ - true - } else if(earlyExit(q)) { - false - } else { - recShouldMatch(q, Set.empty) - } - }) - } - } - - - private val shouldMatchData : Type => Boolean = shouldMatchGen(tq"chisel3.Data") - private val shouldMatchDataOrMem : Type => Boolean = shouldMatchGen(tq"chisel3.Data", tq"chisel3.MemBase[_]") - private val shouldMatchModule : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.BaseModule") - - // Given a type tree, infer the type and return it - private def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe - // Indicates whether a ValDef is properly formed to get name - private def okVal(dd: ValDef): Boolean = { - - // These were found through trial and error - def okFlags(mods: Modifiers): Boolean = { - val badFlags = Set( - Flag.PARAM, - Flag.SYNTHETIC, - Flag.DEFERRED, - Flags.TRIEDCOOKING, - Flags.CASEACCESSOR, - Flags.PARAMACCESSOR - ) - badFlags.forall{ x => !mods.hasFlag(x)} - } - - // Ensure expression isn't null, as you can't call `null.autoName("myname")` - val isNull = dd.rhs match { - case Literal(Constant(null)) => true - case _ => false - } - - okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree - } - // TODO Unify with okVal - private def okUnapply(dd: ValDef): Boolean = { - - // These were found through trial and error - def okFlags(mods: Modifiers): Boolean = { - val badFlags = Set( - Flag.PARAM, - Flag.DEFERRED, - Flags.TRIEDCOOKING, - Flags.CASEACCESSOR, - Flags.PARAMACCESSOR - ) - val goodFlags = Set( - Flag.SYNTHETIC, - Flag.ARTIFACT - ) - goodFlags.forall(f => mods.hasFlag(f)) && badFlags.forall(f => !mods.hasFlag(f)) - } - - // Ensure expression isn't null, as you can't call `null.autoName("myname")` - val isNull = dd.rhs match { - case Literal(Constant(null)) => true - case _ => false - } - val tpe = inferType(dd.tpt) - definitions.isTupleType(tpe) && okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree - } - - private def findUnapplyNames(tree: Tree): Option[List[String]] = { - val applyArgs: Option[List[Tree]] = tree match { - case Match(_, List(CaseDef(_, _, Apply(_, args)))) => Some(args) - case _ => None - } - applyArgs.flatMap { args => - var ok = true - val result = mutable.ListBuffer[String]() - args.foreach { - case Ident(TermName(name)) => result += name - // Anything unexpected and we abort - case _ => ok = false - } - if (ok) Some(result.toList) else None - } - } - - // Whether this val is directly enclosed by a Bundle type - private def inBundle(dd: ValDef): Boolean = { - dd.symbol.logicallyEnclosingMember.thisType <:< inferType(tq"chisel3.Bundle") - } - - private def stringFromTermName(name: TermName): String = - name.toString.trim() // Remove trailing space (Scalac implementation detail) - - // Method called by the compiler to modify source tree - override def transform(tree: Tree): Tree = tree match { - // Check if a subtree is a candidate - case dd @ ValDef(mods, name, tpt, rhs) if okVal(dd) => - val tpe = inferType(tpt) - // If a Data and in a Bundle, just get the name but not a prefix - if (shouldMatchData(tpe) && inBundle(dd)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) // chisel3.internal.plugin.autoNameRecursively - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - } - // If a Data or a Memory, get the name and a prefix - else if (shouldMatchDataOrMem(tpe)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) - val prefixed = q"chisel3.experimental.prefix.apply[$tpt](name=$str)(f=$newRHS)" - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($prefixed)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - // If an instance, just get a name but no prefix - } else if (shouldMatchModule(tpe)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - } else { - // Otherwise, continue - super.transform(tree) - } - case dd @ ValDef(mods, name, tpt, rhs @ Match(_, _)) if okUnapply(dd) => - val tpe = inferType(tpt) - val fieldsOfInterest: List[Boolean] = tpe.typeArgs.map(shouldMatchData) - // Only transform if at least one field is of interest - if (fieldsOfInterest.reduce(_ || _)) { - findUnapplyNames(rhs) match { - case Some(names) => - val onames: List[Option[String]] = fieldsOfInterest.zip(names).map { case (ok, name) => if (ok) Some(name) else None } - val named = q"chisel3.internal.plugin.autoNameRecursivelyProduct($onames)($rhs)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - case None => // It's not clear how this could happen but we don't want to crash - super.transform(tree) - } - } else { - super.transform(tree) - } - // Otherwise, continue - case _ => super.transform(tree) - } - } -} -- cgit v1.2.3 From 1494231212425fd09f915d819102ca5cdef0dfcf Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 11 Feb 2021 18:12:48 -0800 Subject: [plugin] Implement autoclonetype in the compiler plugin --- .../chisel3/internal/plugin/BundleComponent.scala | 125 +++++++++++++++++++++ .../chisel3/internal/plugin/ChiselComponent.scala | 7 +- .../chisel3/internal/plugin/ChiselPlugin.scala | 6 +- 3 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala (limited to 'plugin') diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala new file mode 100644 index 00000000..0711e08c --- /dev/null +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal.plugin + +import scala.collection.mutable +import scala.tools.nsc +import scala.tools.nsc.{Global, Phase} +import scala.tools.nsc.plugins.PluginComponent +import scala.tools.nsc.symtab.Flags +import scala.tools.nsc.transform.TypingTransformers + +// TODO This component could also implement val elements in Bundles +private[plugin] class BundleComponent(val global: Global) extends PluginComponent with TypingTransformers { + import global._ + + val phaseName: String = "chiselbundlephase" + val runsAfter: List[String] = "typer" :: Nil + def newPhase(prev: Phase): Phase = new BundlePhase(prev) + + private class BundlePhase(prev: Phase) extends StdPhase(prev) { + override def name: String = phaseName + def apply(unit: CompilationUnit): Unit = { + // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, + // instead we just check the version and if its an early Scala version, the plugin does nothing + val scalaVersion = scala.util.Properties.versionNumberString.split('.') + if (scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12) { + unit.body = new MyTypingTransformer(unit).transform(unit.body) + } + } + } + + private class MyTypingTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { + + def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe + + val bundleTpe = inferType(tq"chisel3.Bundle") + val dataTpe = inferType(tq"chisel3.Data") + + // Not cached because it should only be run once per class (thus once per Type) + def isBundle(sym: Symbol): Boolean = sym.tpe <:< bundleTpe + + val isDataCache = new mutable.HashMap[Type, Boolean] + // Cached because this is run on every argument to every Bundle + def isData(sym: Symbol): Boolean = isDataCache.getOrElseUpdate(sym.tpe, sym.tpe <:< dataTpe) + + def cloneTypeFull(tree: Tree): Tree = + localTyper.typed(q"chisel3.experimental.DataMirror.internal.chiselTypeClone[${tree.tpe}]($tree)") + + def isNullaryMethodNamed(name: String, defdef: DefDef): Boolean = + defdef.name.decodedName.toString == name && defdef.tparams.isEmpty && defdef.vparamss.isEmpty + + def getConstructorAndParams(body: List[Tree]): (Option[DefDef], Seq[Symbol]) = { + val paramAccessors = mutable.ListBuffer[Symbol]() + var primaryConstructor: Option[DefDef] = None + body.foreach { + case acc: ValDef if acc.symbol.isParamAccessor => + paramAccessors += acc.symbol + case con: DefDef if con.symbol.isPrimaryConstructor => + primaryConstructor = Some(con) + case d: DefDef if isNullaryMethodNamed("_cloneTypeImpl", d) => + val msg = "Users cannot override _cloneTypeImpl. Let the compiler plugin generate it. If you must, override cloneType instead." + global.globalError(d.pos, msg) + case d: DefDef if isNullaryMethodNamed("_usingPlugin", d) => + val msg = "Users cannot override _usingPlugin, it is for the compiler plugin's use only." + global.globalError(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) + } + 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 + + // Create a this. 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. + val select = gen.mkAttributedSelect(thiz, p) + // Clone any Data parameters to avoid field aliasing, need full clone to include direction + if (isData(vp.symbol)) cloneTypeFull(select) else select + }) + + val ttpe = Ident(bundle.symbol) + val neww = localTyper.typed(New(ttpe, conArgs)) + + // 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)) + + val cloneTypeImpl = localTyper.typed(DefDef(cloneTypeSym, neww)) + + // ==================== Generate _usingPlugin ==================== + // Unclear why quasiquotes work here but didn't for cloneTypeSym, maybe they could. + val usingPlugin = localTyper.typed(q"override protected def _usingPlugin: Boolean = true") + + val withMethods = deriveClassDef(bundle) { t => + deriveTemplate(t)(_ :+ cloneTypeImpl :+ usingPlugin) + } + + super.transform(localTyper.typed(withMethods)) + + case _ => super.transform(tree) + } + } +} diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala index f86b8322..b1302ba3 100644 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala @@ -19,9 +19,10 @@ class ChiselComponent(val global: Global) extends PluginComponent with TypingTra class ChiselComponentPhase(prev: Phase) extends StdPhase(prev) { override def name: String = phaseName def apply(unit: CompilationUnit): Unit = { - // This plugin doesn't work on Scala 2.11. Rather than complicate the sbt build flow, + // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, // instead we just check the version and if its an early Scala version, the plugin does nothing - if(scala.util.Properties.versionNumberString.split('.')(1).toInt >= 12) { + val scalaVersion = scala.util.Properties.versionNumberString.split('.') + if (scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12) { unit.body = new MyTypingTransformer(unit).transform(unit.body) } } @@ -211,4 +212,4 @@ class ChiselComponent(val global: Global) extends PluginComponent with TypingTra case _ => super.transform(tree) } } -} \ No newline at end of file +} diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala index f8a1752c..d5eb21cc 100644 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala @@ -10,7 +10,9 @@ import nsc.plugins.{Plugin, PluginComponent} class ChiselPlugin(val global: Global) extends Plugin { val name = "chiselplugin" val description = "Plugin for Chisel 3 Hardware Description Language" - val components: List[PluginComponent] = List[PluginComponent](new ChiselComponent(global)) + val components: List[PluginComponent] = List[PluginComponent]( + new ChiselComponent(global), + new BundleComponent(global) + ) } - -- cgit v1.2.3 From 3bea6167159737b379f37031c3beef27337be06d Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Fri, 12 Mar 2021 15:33:58 -0800 Subject: [plugin] Disable BundleComponent by default, add option to enable --- .../chisel3/internal/plugin/BundleComponent.scala | 15 +++++++++++++-- .../chisel3/internal/plugin/ChiselPlugin.scala | 21 ++++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) (limited to 'plugin') diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala index 0711e08c..96851e95 100644 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala @@ -10,7 +10,9 @@ import scala.tools.nsc.symtab.Flags import scala.tools.nsc.transform.TypingTransformers // TODO This component could also implement val elements in Bundles -private[plugin] class BundleComponent(val global: Global) extends PluginComponent with TypingTransformers { +private[plugin] class BundleComponent(val global: Global, arguments: ChiselPluginArguments) + extends PluginComponent + with TypingTransformers { import global._ val phaseName: String = "chiselbundlephase" @@ -23,8 +25,17 @@ private[plugin] class BundleComponent(val global: Global) extends PluginComponen // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, // instead we just check the version and if its an early Scala version, the plugin does nothing val scalaVersion = scala.util.Properties.versionNumberString.split('.') - if (scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12) { + val scalaVersionOk = scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12 + if (scalaVersionOk && arguments.useBundlePlugin) { unit.body = new MyTypingTransformer(unit).transform(unit.body) + } else { + val reason = if (!scalaVersionOk) { + s"invalid Scala version '${scala.util.Properties.versionNumberString}'" + } else { + s"not enabled via '${arguments.useBundlePluginFullOpt}'" + } + // Enable this with scalacOption '-Ylog:chiselbundlephase' + global.log(s"Skipping BundleComponent on account of $reason.") } } } diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala index d5eb21cc..a0651e1d 100644 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala @@ -6,13 +6,32 @@ import scala.tools.nsc import nsc.Global import nsc.plugins.{Plugin, PluginComponent} +private[plugin] case class ChiselPluginArguments(var useBundlePlugin: Boolean = false) { + def useBundlePluginOpt = "useBundlePlugin" + def useBundlePluginFullOpt = s"-P:chiselplugin:$useBundlePluginOpt" +} + // The plugin to be run by the Scala compiler during compilation of Chisel code class ChiselPlugin(val global: Global) extends Plugin { val name = "chiselplugin" val description = "Plugin for Chisel 3 Hardware Description Language" + private val arguments = ChiselPluginArguments() val components: List[PluginComponent] = List[PluginComponent]( new ChiselComponent(global), - new BundleComponent(global) + new BundleComponent(global, arguments) ) + + override def init(options: List[String], error: String => Unit): Boolean = { + for (option <- options) { + if (option == arguments.useBundlePluginOpt) { + arguments.useBundlePlugin = true + } else { + error(s"Option not understood: '$option'") + } + } + true + } + + } -- cgit v1.2.3 From 26461d500f402310cb3adf914b636be3a3a8e442 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 23 Mar 2021 14:54:57 -0700 Subject: Make plugin autoclonetype always on (#1826) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- .../src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'plugin') diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala index a0651e1d..23082329 100644 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala @@ -5,8 +5,9 @@ package chisel3.internal.plugin import scala.tools.nsc import nsc.Global import nsc.plugins.{Plugin, PluginComponent} +import scala.reflect.internal.util.NoPosition -private[plugin] case class ChiselPluginArguments(var useBundlePlugin: Boolean = false) { +private[plugin] case class ChiselPluginArguments(var useBundlePlugin: Boolean = true) { def useBundlePluginOpt = "useBundlePlugin" def useBundlePluginFullOpt = s"-P:chiselplugin:$useBundlePluginOpt" } @@ -24,7 +25,8 @@ class ChiselPlugin(val global: Global) extends Plugin { override def init(options: List[String], error: String => Unit): Boolean = { for (option <- options) { if (option == arguments.useBundlePluginOpt) { - arguments.useBundlePlugin = true + val msg = s"'${arguments.useBundlePluginFullOpt}' is now default behavior, you can stop using the scalacOption." + global.reporter.warning(NoPosition, msg) } else { error(s"Option not understood: '$option'") } -- cgit v1.2.3 From c5861176887bfa529277e686df09a42aeceb6cd7 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 29 Apr 2021 16:18:06 -0700 Subject: Scala 2.13 support (#1751) --- .../chisel3/internal/plugin/BundleComponent.scala | 136 ------------- .../chisel3/internal/plugin/ChiselComponent.scala | 215 --------------------- .../chisel3/internal/plugin/ChiselPlugin.scala | 39 ---- .../chisel3/internal/plugin/BundleComponent.scala | 136 +++++++++++++ .../chisel3/internal/plugin/ChiselComponent.scala | 215 +++++++++++++++++++++ .../chisel3/internal/plugin/ChiselPlugin.scala | 39 ++++ 6 files changed, 390 insertions(+), 390 deletions(-) delete mode 100644 plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala delete mode 100644 plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala delete mode 100644 plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala create mode 100644 plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala create mode 100644 plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala create mode 100644 plugin/src/main/scala/chisel3/internal/plugin/ChiselPlugin.scala (limited to 'plugin') diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala deleted file mode 100644 index 96851e95..00000000 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chisel3.internal.plugin - -import scala.collection.mutable -import scala.tools.nsc -import scala.tools.nsc.{Global, Phase} -import scala.tools.nsc.plugins.PluginComponent -import scala.tools.nsc.symtab.Flags -import scala.tools.nsc.transform.TypingTransformers - -// TODO This component could also implement val elements in Bundles -private[plugin] class BundleComponent(val global: Global, arguments: ChiselPluginArguments) - extends PluginComponent - with TypingTransformers { - import global._ - - val phaseName: String = "chiselbundlephase" - val runsAfter: List[String] = "typer" :: Nil - def newPhase(prev: Phase): Phase = new BundlePhase(prev) - - private class BundlePhase(prev: Phase) extends StdPhase(prev) { - override def name: String = phaseName - def apply(unit: CompilationUnit): Unit = { - // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, - // instead we just check the version and if its an early Scala version, the plugin does nothing - val scalaVersion = scala.util.Properties.versionNumberString.split('.') - val scalaVersionOk = scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12 - if (scalaVersionOk && arguments.useBundlePlugin) { - unit.body = new MyTypingTransformer(unit).transform(unit.body) - } else { - val reason = if (!scalaVersionOk) { - s"invalid Scala version '${scala.util.Properties.versionNumberString}'" - } else { - s"not enabled via '${arguments.useBundlePluginFullOpt}'" - } - // Enable this with scalacOption '-Ylog:chiselbundlephase' - global.log(s"Skipping BundleComponent on account of $reason.") - } - } - } - - private class MyTypingTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { - - def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe - - val bundleTpe = inferType(tq"chisel3.Bundle") - val dataTpe = inferType(tq"chisel3.Data") - - // Not cached because it should only be run once per class (thus once per Type) - def isBundle(sym: Symbol): Boolean = sym.tpe <:< bundleTpe - - val isDataCache = new mutable.HashMap[Type, Boolean] - // Cached because this is run on every argument to every Bundle - def isData(sym: Symbol): Boolean = isDataCache.getOrElseUpdate(sym.tpe, sym.tpe <:< dataTpe) - - def cloneTypeFull(tree: Tree): Tree = - localTyper.typed(q"chisel3.experimental.DataMirror.internal.chiselTypeClone[${tree.tpe}]($tree)") - - def isNullaryMethodNamed(name: String, defdef: DefDef): Boolean = - defdef.name.decodedName.toString == name && defdef.tparams.isEmpty && defdef.vparamss.isEmpty - - def getConstructorAndParams(body: List[Tree]): (Option[DefDef], Seq[Symbol]) = { - val paramAccessors = mutable.ListBuffer[Symbol]() - var primaryConstructor: Option[DefDef] = None - body.foreach { - case acc: ValDef if acc.symbol.isParamAccessor => - paramAccessors += acc.symbol - case con: DefDef if con.symbol.isPrimaryConstructor => - primaryConstructor = Some(con) - case d: DefDef if isNullaryMethodNamed("_cloneTypeImpl", d) => - val msg = "Users cannot override _cloneTypeImpl. Let the compiler plugin generate it. If you must, override cloneType instead." - global.globalError(d.pos, msg) - case d: DefDef if isNullaryMethodNamed("_usingPlugin", d) => - val msg = "Users cannot override _usingPlugin, it is for the compiler plugin's use only." - global.globalError(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) - } - 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 - - // Create a this. 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. - val select = gen.mkAttributedSelect(thiz, p) - // Clone any Data parameters to avoid field aliasing, need full clone to include direction - if (isData(vp.symbol)) cloneTypeFull(select) else select - }) - - val ttpe = Ident(bundle.symbol) - val neww = localTyper.typed(New(ttpe, conArgs)) - - // 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)) - - val cloneTypeImpl = localTyper.typed(DefDef(cloneTypeSym, neww)) - - // ==================== Generate _usingPlugin ==================== - // Unclear why quasiquotes work here but didn't for cloneTypeSym, maybe they could. - val usingPlugin = localTyper.typed(q"override protected def _usingPlugin: Boolean = true") - - val withMethods = deriveClassDef(bundle) { t => - deriveTemplate(t)(_ :+ cloneTypeImpl :+ usingPlugin) - } - - super.transform(localTyper.typed(withMethods)) - - case _ => super.transform(tree) - } - } -} diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala deleted file mode 100644 index b1302ba3..00000000 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala +++ /dev/null @@ -1,215 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chisel3.internal.plugin - -import scala.collection.mutable -import scala.reflect.internal.Flags -import scala.tools.nsc -import scala.tools.nsc.{Global, Phase} -import scala.tools.nsc.plugins.PluginComponent -import scala.tools.nsc.transform.TypingTransformers - -// The component of the chisel plugin. Not sure exactly what the difference is between -// a Plugin and a PluginComponent. -class ChiselComponent(val global: Global) extends PluginComponent with TypingTransformers { - import global._ - val runsAfter: List[String] = List[String]("typer") - val phaseName: String = "chiselcomponent" - def newPhase(_prev: Phase): ChiselComponentPhase = new ChiselComponentPhase(_prev) - class ChiselComponentPhase(prev: Phase) extends StdPhase(prev) { - override def name: String = phaseName - def apply(unit: CompilationUnit): Unit = { - // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, - // instead we just check the version and if its an early Scala version, the plugin does nothing - val scalaVersion = scala.util.Properties.versionNumberString.split('.') - if (scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12) { - unit.body = new MyTypingTransformer(unit).transform(unit.body) - } - } - } - - class MyTypingTransformer(unit: CompilationUnit) - extends TypingTransformer(unit) { - - private def shouldMatchGen(bases: Tree*): Type => Boolean = { - val cache = mutable.HashMap.empty[Type, Boolean] - val baseTypes = bases.map(inferType) - - // If subtype of one of the base types, it's a match! - def terminate(t: Type): Boolean = baseTypes.exists(t <:< _) - - // Recurse through subtype hierarchy finding containers - // Seen is only updated when we recurse into type parameters, thus it is typically small - def recShouldMatch(s: Type, seen: Set[Type]): Boolean = { - def outerMatches(t: Type): Boolean = { - val str = t.toString - str.startsWith("Option[") || str.startsWith("Iterable[") - } - if (terminate(s)) { - true - } else if (seen.contains(s)) { - false - } else if (outerMatches(s)) { - // These are type parameters, loops *are* possible here - recShouldMatch(s.typeArgs.head, seen + s) - } else { - // This is the standard inheritance hierarchy, Scalac catches loops here - s.parents.exists( p => recShouldMatch(p, seen) ) - } - } - - // If doesn't match container pattern, exit early - def earlyExit(t: Type): Boolean = { - !(t.matchesPattern(inferType(tq"Iterable[_]")) || t.matchesPattern(inferType(tq"Option[_]"))) - } - - // Return function so that it captures the cache - { q: Type => - cache.getOrElseUpdate(q, { - // First check if a match, then check early exit, then recurse - if(terminate(q)){ - true - } else if(earlyExit(q)) { - false - } else { - recShouldMatch(q, Set.empty) - } - }) - } - } - - - private val shouldMatchData : Type => Boolean = shouldMatchGen(tq"chisel3.Data") - private val shouldMatchDataOrMem : Type => Boolean = shouldMatchGen(tq"chisel3.Data", tq"chisel3.MemBase[_]") - private val shouldMatchModule : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.BaseModule") - - // Given a type tree, infer the type and return it - private def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe - - // Indicates whether a ValDef is properly formed to get name - private def okVal(dd: ValDef): Boolean = { - - // These were found through trial and error - def okFlags(mods: Modifiers): Boolean = { - val badFlags = Set( - Flag.PARAM, - Flag.SYNTHETIC, - Flag.DEFERRED, - Flags.TRIEDCOOKING, - Flags.CASEACCESSOR, - Flags.PARAMACCESSOR - ) - badFlags.forall{ x => !mods.hasFlag(x)} - } - - // Ensure expression isn't null, as you can't call `null.autoName("myname")` - val isNull = dd.rhs match { - case Literal(Constant(null)) => true - case _ => false - } - - okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree - } - // TODO Unify with okVal - private def okUnapply(dd: ValDef): Boolean = { - - // These were found through trial and error - def okFlags(mods: Modifiers): Boolean = { - val badFlags = Set( - Flag.PARAM, - Flag.DEFERRED, - Flags.TRIEDCOOKING, - Flags.CASEACCESSOR, - Flags.PARAMACCESSOR - ) - val goodFlags = Set( - Flag.SYNTHETIC, - Flag.ARTIFACT - ) - goodFlags.forall(f => mods.hasFlag(f)) && badFlags.forall(f => !mods.hasFlag(f)) - } - - // Ensure expression isn't null, as you can't call `null.autoName("myname")` - val isNull = dd.rhs match { - case Literal(Constant(null)) => true - case _ => false - } - val tpe = inferType(dd.tpt) - definitions.isTupleType(tpe) && okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree - } - - private def findUnapplyNames(tree: Tree): Option[List[String]] = { - val applyArgs: Option[List[Tree]] = tree match { - case Match(_, List(CaseDef(_, _, Apply(_, args)))) => Some(args) - case _ => None - } - applyArgs.flatMap { args => - var ok = true - val result = mutable.ListBuffer[String]() - args.foreach { - case Ident(TermName(name)) => result += name - // Anything unexpected and we abort - case _ => ok = false - } - if (ok) Some(result.toList) else None - } - } - - // Whether this val is directly enclosed by a Bundle type - private def inBundle(dd: ValDef): Boolean = { - dd.symbol.logicallyEnclosingMember.thisType <:< inferType(tq"chisel3.Bundle") - } - - private def stringFromTermName(name: TermName): String = - name.toString.trim() // Remove trailing space (Scalac implementation detail) - - // Method called by the compiler to modify source tree - override def transform(tree: Tree): Tree = tree match { - // Check if a subtree is a candidate - case dd @ ValDef(mods, name, tpt, rhs) if okVal(dd) => - val tpe = inferType(tpt) - // If a Data and in a Bundle, just get the name but not a prefix - if (shouldMatchData(tpe) && inBundle(dd)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) // chisel3.internal.plugin.autoNameRecursively - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - } - // If a Data or a Memory, get the name and a prefix - else if (shouldMatchDataOrMem(tpe)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) - val prefixed = q"chisel3.experimental.prefix.apply[$tpt](name=$str)(f=$newRHS)" - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($prefixed)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - // If an instance, just get a name but no prefix - } else if (shouldMatchModule(tpe)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - } else { - // Otherwise, continue - super.transform(tree) - } - case dd @ ValDef(mods, name, tpt, rhs @ Match(_, _)) if okUnapply(dd) => - val tpe = inferType(tpt) - val fieldsOfInterest: List[Boolean] = tpe.typeArgs.map(shouldMatchData) - // Only transform if at least one field is of interest - if (fieldsOfInterest.reduce(_ || _)) { - findUnapplyNames(rhs) match { - case Some(names) => - val onames: List[Option[String]] = fieldsOfInterest.zip(names).map { case (ok, name) => if (ok) Some(name) else None } - val named = q"chisel3.internal.plugin.autoNameRecursivelyProduct($onames)($rhs)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - case None => // It's not clear how this could happen but we don't want to crash - super.transform(tree) - } - } else { - super.transform(tree) - } - // Otherwise, continue - case _ => super.transform(tree) - } - } -} diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala deleted file mode 100644 index 23082329..00000000 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chisel3.internal.plugin - -import scala.tools.nsc -import nsc.Global -import nsc.plugins.{Plugin, PluginComponent} -import scala.reflect.internal.util.NoPosition - -private[plugin] case class ChiselPluginArguments(var useBundlePlugin: Boolean = true) { - def useBundlePluginOpt = "useBundlePlugin" - def useBundlePluginFullOpt = s"-P:chiselplugin:$useBundlePluginOpt" -} - -// The plugin to be run by the Scala compiler during compilation of Chisel code -class ChiselPlugin(val global: Global) extends Plugin { - val name = "chiselplugin" - val description = "Plugin for Chisel 3 Hardware Description Language" - private val arguments = ChiselPluginArguments() - val components: List[PluginComponent] = List[PluginComponent]( - new ChiselComponent(global), - new BundleComponent(global, arguments) - ) - - override def init(options: List[String], error: String => Unit): Boolean = { - for (option <- options) { - if (option == arguments.useBundlePluginOpt) { - val msg = s"'${arguments.useBundlePluginFullOpt}' is now default behavior, you can stop using the scalacOption." - global.reporter.warning(NoPosition, msg) - } else { - error(s"Option not understood: '$option'") - } - } - true - } - - -} - diff --git a/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala b/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala new file mode 100644 index 00000000..96851e95 --- /dev/null +++ b/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal.plugin + +import scala.collection.mutable +import scala.tools.nsc +import scala.tools.nsc.{Global, Phase} +import scala.tools.nsc.plugins.PluginComponent +import scala.tools.nsc.symtab.Flags +import scala.tools.nsc.transform.TypingTransformers + +// TODO This component could also implement val elements in Bundles +private[plugin] class BundleComponent(val global: Global, arguments: ChiselPluginArguments) + extends PluginComponent + with TypingTransformers { + import global._ + + val phaseName: String = "chiselbundlephase" + val runsAfter: List[String] = "typer" :: Nil + def newPhase(prev: Phase): Phase = new BundlePhase(prev) + + private class BundlePhase(prev: Phase) extends StdPhase(prev) { + override def name: String = phaseName + def apply(unit: CompilationUnit): Unit = { + // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, + // instead we just check the version and if its an early Scala version, the plugin does nothing + val scalaVersion = scala.util.Properties.versionNumberString.split('.') + val scalaVersionOk = scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12 + if (scalaVersionOk && arguments.useBundlePlugin) { + unit.body = new MyTypingTransformer(unit).transform(unit.body) + } else { + val reason = if (!scalaVersionOk) { + s"invalid Scala version '${scala.util.Properties.versionNumberString}'" + } else { + s"not enabled via '${arguments.useBundlePluginFullOpt}'" + } + // Enable this with scalacOption '-Ylog:chiselbundlephase' + global.log(s"Skipping BundleComponent on account of $reason.") + } + } + } + + private class MyTypingTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { + + def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe + + val bundleTpe = inferType(tq"chisel3.Bundle") + val dataTpe = inferType(tq"chisel3.Data") + + // Not cached because it should only be run once per class (thus once per Type) + def isBundle(sym: Symbol): Boolean = sym.tpe <:< bundleTpe + + val isDataCache = new mutable.HashMap[Type, Boolean] + // Cached because this is run on every argument to every Bundle + def isData(sym: Symbol): Boolean = isDataCache.getOrElseUpdate(sym.tpe, sym.tpe <:< dataTpe) + + def cloneTypeFull(tree: Tree): Tree = + localTyper.typed(q"chisel3.experimental.DataMirror.internal.chiselTypeClone[${tree.tpe}]($tree)") + + def isNullaryMethodNamed(name: String, defdef: DefDef): Boolean = + defdef.name.decodedName.toString == name && defdef.tparams.isEmpty && defdef.vparamss.isEmpty + + def getConstructorAndParams(body: List[Tree]): (Option[DefDef], Seq[Symbol]) = { + val paramAccessors = mutable.ListBuffer[Symbol]() + var primaryConstructor: Option[DefDef] = None + body.foreach { + case acc: ValDef if acc.symbol.isParamAccessor => + paramAccessors += acc.symbol + case con: DefDef if con.symbol.isPrimaryConstructor => + primaryConstructor = Some(con) + case d: DefDef if isNullaryMethodNamed("_cloneTypeImpl", d) => + val msg = "Users cannot override _cloneTypeImpl. Let the compiler plugin generate it. If you must, override cloneType instead." + global.globalError(d.pos, msg) + case d: DefDef if isNullaryMethodNamed("_usingPlugin", d) => + val msg = "Users cannot override _usingPlugin, it is for the compiler plugin's use only." + global.globalError(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) + } + 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 + + // Create a this. 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. + val select = gen.mkAttributedSelect(thiz, p) + // Clone any Data parameters to avoid field aliasing, need full clone to include direction + if (isData(vp.symbol)) cloneTypeFull(select) else select + }) + + val ttpe = Ident(bundle.symbol) + val neww = localTyper.typed(New(ttpe, conArgs)) + + // 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)) + + val cloneTypeImpl = localTyper.typed(DefDef(cloneTypeSym, neww)) + + // ==================== Generate _usingPlugin ==================== + // Unclear why quasiquotes work here but didn't for cloneTypeSym, maybe they could. + val usingPlugin = localTyper.typed(q"override protected def _usingPlugin: Boolean = true") + + val withMethods = deriveClassDef(bundle) { t => + deriveTemplate(t)(_ :+ cloneTypeImpl :+ usingPlugin) + } + + super.transform(localTyper.typed(withMethods)) + + case _ => super.transform(tree) + } + } +} diff --git a/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala b/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala new file mode 100644 index 00000000..b1302ba3 --- /dev/null +++ b/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal.plugin + +import scala.collection.mutable +import scala.reflect.internal.Flags +import scala.tools.nsc +import scala.tools.nsc.{Global, Phase} +import scala.tools.nsc.plugins.PluginComponent +import scala.tools.nsc.transform.TypingTransformers + +// The component of the chisel plugin. Not sure exactly what the difference is between +// a Plugin and a PluginComponent. +class ChiselComponent(val global: Global) extends PluginComponent with TypingTransformers { + import global._ + val runsAfter: List[String] = List[String]("typer") + val phaseName: String = "chiselcomponent" + def newPhase(_prev: Phase): ChiselComponentPhase = new ChiselComponentPhase(_prev) + class ChiselComponentPhase(prev: Phase) extends StdPhase(prev) { + override def name: String = phaseName + def apply(unit: CompilationUnit): Unit = { + // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, + // instead we just check the version and if its an early Scala version, the plugin does nothing + val scalaVersion = scala.util.Properties.versionNumberString.split('.') + if (scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12) { + unit.body = new MyTypingTransformer(unit).transform(unit.body) + } + } + } + + class MyTypingTransformer(unit: CompilationUnit) + extends TypingTransformer(unit) { + + private def shouldMatchGen(bases: Tree*): Type => Boolean = { + val cache = mutable.HashMap.empty[Type, Boolean] + val baseTypes = bases.map(inferType) + + // If subtype of one of the base types, it's a match! + def terminate(t: Type): Boolean = baseTypes.exists(t <:< _) + + // Recurse through subtype hierarchy finding containers + // Seen is only updated when we recurse into type parameters, thus it is typically small + def recShouldMatch(s: Type, seen: Set[Type]): Boolean = { + def outerMatches(t: Type): Boolean = { + val str = t.toString + str.startsWith("Option[") || str.startsWith("Iterable[") + } + if (terminate(s)) { + true + } else if (seen.contains(s)) { + false + } else if (outerMatches(s)) { + // These are type parameters, loops *are* possible here + recShouldMatch(s.typeArgs.head, seen + s) + } else { + // This is the standard inheritance hierarchy, Scalac catches loops here + s.parents.exists( p => recShouldMatch(p, seen) ) + } + } + + // If doesn't match container pattern, exit early + def earlyExit(t: Type): Boolean = { + !(t.matchesPattern(inferType(tq"Iterable[_]")) || t.matchesPattern(inferType(tq"Option[_]"))) + } + + // Return function so that it captures the cache + { q: Type => + cache.getOrElseUpdate(q, { + // First check if a match, then check early exit, then recurse + if(terminate(q)){ + true + } else if(earlyExit(q)) { + false + } else { + recShouldMatch(q, Set.empty) + } + }) + } + } + + + private val shouldMatchData : Type => Boolean = shouldMatchGen(tq"chisel3.Data") + private val shouldMatchDataOrMem : Type => Boolean = shouldMatchGen(tq"chisel3.Data", tq"chisel3.MemBase[_]") + private val shouldMatchModule : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.BaseModule") + + // Given a type tree, infer the type and return it + private def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe + + // Indicates whether a ValDef is properly formed to get name + private def okVal(dd: ValDef): Boolean = { + + // These were found through trial and error + def okFlags(mods: Modifiers): Boolean = { + val badFlags = Set( + Flag.PARAM, + Flag.SYNTHETIC, + Flag.DEFERRED, + Flags.TRIEDCOOKING, + Flags.CASEACCESSOR, + Flags.PARAMACCESSOR + ) + badFlags.forall{ x => !mods.hasFlag(x)} + } + + // Ensure expression isn't null, as you can't call `null.autoName("myname")` + val isNull = dd.rhs match { + case Literal(Constant(null)) => true + case _ => false + } + + okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree + } + // TODO Unify with okVal + private def okUnapply(dd: ValDef): Boolean = { + + // These were found through trial and error + def okFlags(mods: Modifiers): Boolean = { + val badFlags = Set( + Flag.PARAM, + Flag.DEFERRED, + Flags.TRIEDCOOKING, + Flags.CASEACCESSOR, + Flags.PARAMACCESSOR + ) + val goodFlags = Set( + Flag.SYNTHETIC, + Flag.ARTIFACT + ) + goodFlags.forall(f => mods.hasFlag(f)) && badFlags.forall(f => !mods.hasFlag(f)) + } + + // Ensure expression isn't null, as you can't call `null.autoName("myname")` + val isNull = dd.rhs match { + case Literal(Constant(null)) => true + case _ => false + } + val tpe = inferType(dd.tpt) + definitions.isTupleType(tpe) && okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree + } + + private def findUnapplyNames(tree: Tree): Option[List[String]] = { + val applyArgs: Option[List[Tree]] = tree match { + case Match(_, List(CaseDef(_, _, Apply(_, args)))) => Some(args) + case _ => None + } + applyArgs.flatMap { args => + var ok = true + val result = mutable.ListBuffer[String]() + args.foreach { + case Ident(TermName(name)) => result += name + // Anything unexpected and we abort + case _ => ok = false + } + if (ok) Some(result.toList) else None + } + } + + // Whether this val is directly enclosed by a Bundle type + private def inBundle(dd: ValDef): Boolean = { + dd.symbol.logicallyEnclosingMember.thisType <:< inferType(tq"chisel3.Bundle") + } + + private def stringFromTermName(name: TermName): String = + name.toString.trim() // Remove trailing space (Scalac implementation detail) + + // Method called by the compiler to modify source tree + override def transform(tree: Tree): Tree = tree match { + // Check if a subtree is a candidate + case dd @ ValDef(mods, name, tpt, rhs) if okVal(dd) => + val tpe = inferType(tpt) + // If a Data and in a Bundle, just get the name but not a prefix + if (shouldMatchData(tpe) && inBundle(dd)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) // chisel3.internal.plugin.autoNameRecursively + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + } + // If a Data or a Memory, get the name and a prefix + else if (shouldMatchDataOrMem(tpe)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) + val prefixed = q"chisel3.experimental.prefix.apply[$tpt](name=$str)(f=$newRHS)" + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($prefixed)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + // If an instance, just get a name but no prefix + } else if (shouldMatchModule(tpe)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + } else { + // Otherwise, continue + super.transform(tree) + } + case dd @ ValDef(mods, name, tpt, rhs @ Match(_, _)) if okUnapply(dd) => + val tpe = inferType(tpt) + val fieldsOfInterest: List[Boolean] = tpe.typeArgs.map(shouldMatchData) + // Only transform if at least one field is of interest + if (fieldsOfInterest.reduce(_ || _)) { + findUnapplyNames(rhs) match { + case Some(names) => + val onames: List[Option[String]] = fieldsOfInterest.zip(names).map { case (ok, name) => if (ok) Some(name) else None } + val named = q"chisel3.internal.plugin.autoNameRecursivelyProduct($onames)($rhs)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + case None => // It's not clear how this could happen but we don't want to crash + super.transform(tree) + } + } else { + super.transform(tree) + } + // Otherwise, continue + case _ => super.transform(tree) + } + } +} diff --git a/plugin/src/main/scala/chisel3/internal/plugin/ChiselPlugin.scala b/plugin/src/main/scala/chisel3/internal/plugin/ChiselPlugin.scala new file mode 100644 index 00000000..23082329 --- /dev/null +++ b/plugin/src/main/scala/chisel3/internal/plugin/ChiselPlugin.scala @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal.plugin + +import scala.tools.nsc +import nsc.Global +import nsc.plugins.{Plugin, PluginComponent} +import scala.reflect.internal.util.NoPosition + +private[plugin] case class ChiselPluginArguments(var useBundlePlugin: Boolean = true) { + def useBundlePluginOpt = "useBundlePlugin" + def useBundlePluginFullOpt = s"-P:chiselplugin:$useBundlePluginOpt" +} + +// The plugin to be run by the Scala compiler during compilation of Chisel code +class ChiselPlugin(val global: Global) extends Plugin { + val name = "chiselplugin" + val description = "Plugin for Chisel 3 Hardware Description Language" + private val arguments = ChiselPluginArguments() + val components: List[PluginComponent] = List[PluginComponent]( + new ChiselComponent(global), + new BundleComponent(global, arguments) + ) + + override def init(options: List[String], error: String => Unit): Boolean = { + for (option <- options) { + if (option == arguments.useBundlePluginOpt) { + val msg = s"'${arguments.useBundlePluginFullOpt}' is now default behavior, you can stop using the scalacOption." + global.reporter.warning(NoPosition, msg) + } else { + error(s"Option not understood: '$option'") + } + } + true + } + + +} + -- cgit v1.2.3 From 9fa8da227569455a77596355aeb114f9c164510a Mon Sep 17 00:00:00 2001 From: Adam Izraelevitz Date: Sun, 5 Sep 2021 12:11:32 -0700 Subject: Add Definition and Instance API (#2045) This introduces a new experimental API for module instantiation that disentagles elaborating the definition (or implementation) from instantiation of a given module. This solves Chisel's longstanding reliance on "Deduplication" for generating Verilog with multiple instances of the same module. The new API resides in package chisel3.experimental.hierarchy. Please see the hierarchy ScalaDoc, documentation, and tests for examples of use. Co-authored-by: Jack Koenig Co-authored-by: Megan Wachs Co-authored-by: Schuyler Eldridge --- plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'plugin') diff --git a/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala b/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala index b1302ba3..af22e6a7 100644 --- a/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala +++ b/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala @@ -82,6 +82,7 @@ class ChiselComponent(val global: Global) extends PluginComponent with TypingTra private val shouldMatchData : Type => Boolean = shouldMatchGen(tq"chisel3.Data") private val shouldMatchDataOrMem : Type => Boolean = shouldMatchGen(tq"chisel3.Data", tq"chisel3.MemBase[_]") private val shouldMatchModule : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.BaseModule") + private val shouldMatchInstance : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.hierarchy.Instance[_]") // Given a type tree, infer the type and return it private def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe @@ -188,6 +189,11 @@ class ChiselComponent(val global: Global) extends PluginComponent with TypingTra val newRHS = transform(rhs) val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + } else if (shouldMatchInstance(tpe)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) } else { // Otherwise, continue super.transform(tree) -- cgit v1.2.3 From 1ae18008e18dfe20983a6ba3b2b950f66245e69b Mon Sep 17 00:00:00 2001 From: Adam Izraelevitz Date: Wed, 15 Sep 2021 11:49:29 -0700 Subject: Fix higher-kinded types for autoclonetype (#2121) --- plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'plugin') diff --git a/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala b/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala index 96851e95..5fe63991 100644 --- a/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala +++ b/plugin/src/main/scala/chisel3/internal/plugin/BundleComponent.scala @@ -108,8 +108,10 @@ private[plugin] class BundleComponent(val global: Global, arguments: ChiselPlugi if (isData(vp.symbol)) cloneTypeFull(select) else select }) - val ttpe = Ident(bundle.symbol) - val neww = localTyper.typed(New(ttpe, conArgs)) + 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) -- cgit v1.2.3