From 1d81119e4b50d1b130ea5df6f4ba076b7f27c9ac Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 23 Mar 2020 09:29:02 -0700 Subject: Add NoChiselNamePrefix to ignore instances in @chiselName (#1383) Add trait chisel3.experimental.NoChiselNamePrefix which causes @chiselName to skip naming of the instance effectively preventing it from prefixing any vals inside the instance. It can be applied to classes such that all instances of that class have this property, or to individual instances (via creating an anonymous class inline). Also add basic ScalaDoc for NoChiselNamePrefix and chiselName.--- .../main/scala/chisel3/experimental/package.scala | 56 ++++++ .../src/main/scala/chisel3/internal/Namer.scala | 2 + .../internal/naming/NamingAnnotations.scala | 205 +++++++++++++++++++++ .../internal/sourceinfo/NamingAnnotations.scala | 205 --------------------- .../scala/chiselTests/NamingAnnotationTest.scala | 32 ++++ 5 files changed, 295 insertions(+), 205 deletions(-) create mode 100644 coreMacros/src/main/scala/chisel3/internal/naming/NamingAnnotations.scala delete mode 100644 coreMacros/src/main/scala/chisel3/internal/sourceinfo/NamingAnnotations.scala diff --git a/chiselFrontend/src/main/scala/chisel3/experimental/package.scala b/chiselFrontend/src/main/scala/chisel3/experimental/package.scala index 7ade2cb3..985f7715 100644 --- a/chiselFrontend/src/main/scala/chisel3/experimental/package.scala +++ b/chiselFrontend/src/main/scala/chisel3/experimental/package.scala @@ -71,7 +71,63 @@ package object experimental { // scalastyle:ignore object.name class dump extends chisel3.internal.naming.dump // scalastyle:ignore class.name class treedump extends chisel3.internal.naming.treedump // scalastyle:ignore class.name + /** Experimental macro for naming Chisel hardware values + * + * By default, Chisel uses reflection for naming which only works for public fields of `Bundle` + * and `Module` classes. Applying this macro annotation to a `class` or `object` enables Chisel + * to name any hardware values within the annotated `class` or `object. + * + * @example {{{ + * import chisel3._ + * import chisel3.experimental.chiselName + * + * @chiselName + * class MyModule extends Module { + * val io = IO(new Bundle { + * val in = Input(UInt(8.W)) + * val out = Output(UInt(8.W)) + * }) + * def createReg(): Unit = { + * // @chiselName allows Chisel to name this Reg + * val myReg = RegInit(io.in) + * io.out := myReg + * } + * createReg() + * } + * }}} + */ class chiselName extends chisel3.internal.naming.chiselName // scalastyle:ignore class.name + /** Do not name instances of this type in [[chiselName]] + * + * By default, `chiselName` will include `val` names of instances of annotated classes as a + * prefix in final naming. Mixing in this trait to a `class`, `object`, or anonymous `class` + * instances will exclude the `val` name from `chiselName` naming. + * + * @example {{{ + * import chisel3._ + * import chisel3.experimental.{chiselName, NoChiselNamePrefix} + * + * // Note that this is not a Module + * @chiselName + * class Counter(w: Int) { + * val myReg = RegInit(0.U(w.W)) + * myReg := myReg + 1.U + * } + * + * @chiselName + * class MyModule extends Module { + * val io = IO(new Bundle { + * val out = Output(UInt(8.W)) + * }) + * // Name of myReg will be "counter0_myReg" + * val counter0 = new Counter(8) + * // Name of myReg will be "myReg" + * val counter1 = new Counter(8) with NoChiselNamePrefix + * io.out := counter0.myReg + counter1.myReg + * } + * }}} + */ + trait NoChiselNamePrefix object BundleLiterals { implicit class AddBundleLiteralConstructor[T <: Bundle](x: T) { diff --git a/chiselFrontend/src/main/scala/chisel3/internal/Namer.scala b/chiselFrontend/src/main/scala/chisel3/internal/Namer.scala index 353e51fd..999971a4 100644 --- a/chiselFrontend/src/main/scala/chisel3/internal/Namer.scala +++ b/chiselFrontend/src/main/scala/chisel3/internal/Namer.scala @@ -3,6 +3,7 @@ // This file contains part of the implementation of the naming static annotation system. package chisel3.internal.naming +import chisel3.experimental.NoChiselNamePrefix import scala.collection.mutable.Stack import scala.collection.mutable.ListBuffer @@ -88,6 +89,7 @@ class NamingContext extends NamingContextInterface { def name[T](obj: T, name: String): T = { assert(!closed, "Can't name elements after name_prefix called") obj match { + case _: NoChiselNamePrefix => // Don't name things with NoChiselNamePrefix case ref: AnyRef => items += ((ref, name)) case _ => } diff --git a/coreMacros/src/main/scala/chisel3/internal/naming/NamingAnnotations.scala b/coreMacros/src/main/scala/chisel3/internal/naming/NamingAnnotations.scala new file mode 100644 index 00000000..bf4879ec --- /dev/null +++ b/coreMacros/src/main/scala/chisel3/internal/naming/NamingAnnotations.scala @@ -0,0 +1,205 @@ +// See LICENSE for license details. + +// Transform implementations for name-propagation related annotations. +// +// Helpful references: +// http://docs.scala-lang.org/overviews/quasiquotes/syntax-summary.html#definitions +// for quasiquote structures of various Scala structures +// http://jsuereth.com/scala/2009/02/05/leveraging-annotations-in-scala.html +// use of Transformer +// http://www.scala-lang.org/old/sites/default/files/sids/rytz/Wed,%202010-01-27,%2015:10/annots.pdf +// general annotations reference + +package chisel3.internal.naming + +import scala.reflect.macros.whitebox.Context +import scala.annotation.{StaticAnnotation, compileTimeOnly} +import scala.language.experimental.macros + +// Workaround for https://github.com/sbt/sbt/issues/3966 +object DebugTransforms +class DebugTransforms(val c: Context) { + import c.universe._ + + /** Passthrough transform that prints the annottee for debugging purposes. + * No guarantees are made on what this annotation does, and it may very well change over time. + * + * The print is warning level to make it visually easier to spot, as well as a reminder that + * this annotation should not make it to production / committed code. + */ + def dump(annottees: c.Tree*): c.Tree = { + val combined = annottees.map({ tree => show(tree) }).mkString("\r\n\r\n") + annottees.foreach(tree => c.warning(c.enclosingPosition, s"Debug dump:\n$combined")) + q"..$annottees" + } + + /** Passthrough transform that prints the annottee as a tree for debugging purposes. + * No guarantees are made on what this annotation does, and it may very well change over time. + * + * The print is warning level to make it visually easier to spot, as well as a reminder that + * this annotation should not make it to production / committed code. + */ + def treedump(annottees: c.Tree*): c.Tree = { + val combined = annottees.map({ tree => showRaw(tree) }).mkString("\r\n") + annottees.foreach(tree => c.warning(c.enclosingPosition, s"Debug tree dump:\n$combined")) + q"..$annottees" + } +} + +// Workaround for https://github.com/sbt/sbt/issues/3966 +object NamingTransforms +class NamingTransforms(val c: Context) { + import c.universe._ + + val globalNamingStack = q"_root_.chisel3.internal.DynamicNamingStack" + + /** Base transformer that provides the val name transform. + * Should not be instantiated, since by default this will recurse everywhere and break the + * naming context variable bounds. + */ + trait ValNameTransformer extends Transformer { + val contextVar: TermName + + override def transform(tree: Tree): Tree = tree match { + // Intentionally not prefixed with $mods, since modifiers usually mean the val definition + // is in a non-transformable location, like as a parameter list. + // TODO: is this exhaustive / correct in all cases? + case q"val $tname: $tpt = $expr" => { + val TermName(tnameStr: String) = tname + val transformedExpr = super.transform(expr) + q"val $tname: $tpt = $contextVar.name($transformedExpr, $tnameStr)" + } + case other => super.transform(other) + } + } + + /** Module-specific val name transform, containing logic to prevent from recursing into inner + * classes and applies the naming transform on inner functions. + */ + class ClassBodyTransformer(val contextVar: TermName) extends ValNameTransformer { + override def transform(tree: Tree): Tree = tree match { + case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => // scalastyle:ignore line.size.limit + tree // don't recurse into inner classes + case q"$mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => + tree // don't recurse into inner classes + case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" => { + val Modifiers(_, _, annotations) = mods + // don't apply naming transform twice + val containsChiselName = annotations.map({q"new chiselName()" equalsStructure _}).fold(false)({_||_}) + // transforming overloaded initializers causes errors, and the transform isn't helpful + val isInitializer = tname == TermName("") + if (containsChiselName || isInitializer) { + tree + } else { + // apply chiselName transform by default + val transformedExpr = transformHierarchicalMethod(expr) + q"$mods def $tname[..$tparams](...$paramss): $tpt = $transformedExpr" + } + } + case other => super.transform(other) + } + } + + /** Method-specific val name transform, handling the return case. + */ + class MethodTransformer(val contextVar: TermName) extends ValNameTransformer { + override def transform(tree: Tree): Tree = tree match { + // TODO: better error messages when returning nothing + case q"return $expr" => q"return $globalNamingStack.popReturnContext($expr, $contextVar)" + // Do not recurse into methods + case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" => tree + case other => super.transform(other) + } + } + + /** Applies the val name transform to a class body. + * Closes context on top level or return local context to englobing context. + * Closing context only makes sense when top level a Module. + * A Module is always the naming top level. + * Transformed classes can be either Module or standard class. + */ + def transformClassBody(stats: List[c.Tree]): Tree = { + val contextVar = TermName(c.freshName("namingContext")) + val transformedBody = (new ClassBodyTransformer(contextVar)).transformTrees(stats) + // Note: passing "this" to popReturnContext is mandatory for propagation through non-module classes + q""" + val $contextVar = $globalNamingStack.pushContext() + ..$transformedBody + if($globalNamingStack.length == 1){ + $contextVar.namePrefix("") + } + $globalNamingStack.popReturnContext(this, $contextVar) + """ + } + + /** Applies the val name transform to a method body, doing additional bookkeeping with the + * context to allow names to propagate and prefix through the function call stack. + */ + def transformHierarchicalMethod(expr: c.Tree): Tree = { + val contextVar = TermName(c.freshName("namingContext")) + val transformedBody = (new MethodTransformer(contextVar)).transform(expr) + + q"""{ + val $contextVar = $globalNamingStack.pushContext() + $globalNamingStack.popReturnContext($transformedBody, $contextVar) + } + """ + } + + /** Applies naming transforms to vals in the annotated module or method. + * + * For methods, a hierarchical naming transform is used, where it will try to give objects names + * based on the call stack, assuming all functions on the stack are annotated as such and return + * a non-AnyVal object. Does not recurse into inner functions. + * + * For modules, this serves as the root of the call stack hierarchy for naming purposes. Methods + * will have chiselName annotations (non-recursively), but this does NOT affect inner classes. + * + * Basically rewrites all instances of: + * val name = expr + * to: + * val name = context.name(expr, name) + */ + def chiselName(annottees: c.Tree*): c.Tree = { + var namedElts: Int = 0 + + val transformed = annottees.map(annottee => annottee match { + // scalastyle:off line.size.limit + case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => { + val transformedStats = transformClassBody(stats) + namedElts += 1 + q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$transformedStats }" + } + case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" => { + annottee // Don't fail noisly when a companion object is passed in with the actual class def + } + // Currently disallow on traits, this won't work well with inheritance. + case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" => { + val transformedExpr = transformHierarchicalMethod(expr) + namedElts += 1 + q"$mods def $tname[..$tparams](...$paramss): $tpt = $transformedExpr" + } + case other => c.abort(c.enclosingPosition, s"@chiselName annotion may only be used on classes and methods, got ${showCode(other)}") + }) + + if (namedElts != 1) { + c.abort(c.enclosingPosition, s"@chiselName annotation did not match exactly one valid tree, got:\r\n${annottees.map(tree => showCode(tree)).mkString("\r\n\r\n")}") + } + // scalastyle:on line.size.limit + + q"..$transformed" + } +} + +@compileTimeOnly("enable macro paradise to expand macro annotations") +class dump extends StaticAnnotation { // scalastyle:ignore class.name + def macroTransform(annottees: Any*): Any = macro chisel3.internal.naming.DebugTransforms.dump +} +@compileTimeOnly("enable macro paradise to expand macro annotations") +class treedump extends StaticAnnotation { // scalastyle:ignore class.name + def macroTransform(annottees: Any*): Any = macro chisel3.internal.naming.DebugTransforms.treedump +} +@compileTimeOnly("enable macro paradise to expand macro annotations") +class chiselName extends StaticAnnotation { // scalastyle:ignore class.name + def macroTransform(annottees: Any*): Any = macro chisel3.internal.naming.NamingTransforms.chiselName +} diff --git a/coreMacros/src/main/scala/chisel3/internal/sourceinfo/NamingAnnotations.scala b/coreMacros/src/main/scala/chisel3/internal/sourceinfo/NamingAnnotations.scala deleted file mode 100644 index bf4879ec..00000000 --- a/coreMacros/src/main/scala/chisel3/internal/sourceinfo/NamingAnnotations.scala +++ /dev/null @@ -1,205 +0,0 @@ -// See LICENSE for license details. - -// Transform implementations for name-propagation related annotations. -// -// Helpful references: -// http://docs.scala-lang.org/overviews/quasiquotes/syntax-summary.html#definitions -// for quasiquote structures of various Scala structures -// http://jsuereth.com/scala/2009/02/05/leveraging-annotations-in-scala.html -// use of Transformer -// http://www.scala-lang.org/old/sites/default/files/sids/rytz/Wed,%202010-01-27,%2015:10/annots.pdf -// general annotations reference - -package chisel3.internal.naming - -import scala.reflect.macros.whitebox.Context -import scala.annotation.{StaticAnnotation, compileTimeOnly} -import scala.language.experimental.macros - -// Workaround for https://github.com/sbt/sbt/issues/3966 -object DebugTransforms -class DebugTransforms(val c: Context) { - import c.universe._ - - /** Passthrough transform that prints the annottee for debugging purposes. - * No guarantees are made on what this annotation does, and it may very well change over time. - * - * The print is warning level to make it visually easier to spot, as well as a reminder that - * this annotation should not make it to production / committed code. - */ - def dump(annottees: c.Tree*): c.Tree = { - val combined = annottees.map({ tree => show(tree) }).mkString("\r\n\r\n") - annottees.foreach(tree => c.warning(c.enclosingPosition, s"Debug dump:\n$combined")) - q"..$annottees" - } - - /** Passthrough transform that prints the annottee as a tree for debugging purposes. - * No guarantees are made on what this annotation does, and it may very well change over time. - * - * The print is warning level to make it visually easier to spot, as well as a reminder that - * this annotation should not make it to production / committed code. - */ - def treedump(annottees: c.Tree*): c.Tree = { - val combined = annottees.map({ tree => showRaw(tree) }).mkString("\r\n") - annottees.foreach(tree => c.warning(c.enclosingPosition, s"Debug tree dump:\n$combined")) - q"..$annottees" - } -} - -// Workaround for https://github.com/sbt/sbt/issues/3966 -object NamingTransforms -class NamingTransforms(val c: Context) { - import c.universe._ - - val globalNamingStack = q"_root_.chisel3.internal.DynamicNamingStack" - - /** Base transformer that provides the val name transform. - * Should not be instantiated, since by default this will recurse everywhere and break the - * naming context variable bounds. - */ - trait ValNameTransformer extends Transformer { - val contextVar: TermName - - override def transform(tree: Tree): Tree = tree match { - // Intentionally not prefixed with $mods, since modifiers usually mean the val definition - // is in a non-transformable location, like as a parameter list. - // TODO: is this exhaustive / correct in all cases? - case q"val $tname: $tpt = $expr" => { - val TermName(tnameStr: String) = tname - val transformedExpr = super.transform(expr) - q"val $tname: $tpt = $contextVar.name($transformedExpr, $tnameStr)" - } - case other => super.transform(other) - } - } - - /** Module-specific val name transform, containing logic to prevent from recursing into inner - * classes and applies the naming transform on inner functions. - */ - class ClassBodyTransformer(val contextVar: TermName) extends ValNameTransformer { - override def transform(tree: Tree): Tree = tree match { - case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => // scalastyle:ignore line.size.limit - tree // don't recurse into inner classes - case q"$mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => - tree // don't recurse into inner classes - case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" => { - val Modifiers(_, _, annotations) = mods - // don't apply naming transform twice - val containsChiselName = annotations.map({q"new chiselName()" equalsStructure _}).fold(false)({_||_}) - // transforming overloaded initializers causes errors, and the transform isn't helpful - val isInitializer = tname == TermName("") - if (containsChiselName || isInitializer) { - tree - } else { - // apply chiselName transform by default - val transformedExpr = transformHierarchicalMethod(expr) - q"$mods def $tname[..$tparams](...$paramss): $tpt = $transformedExpr" - } - } - case other => super.transform(other) - } - } - - /** Method-specific val name transform, handling the return case. - */ - class MethodTransformer(val contextVar: TermName) extends ValNameTransformer { - override def transform(tree: Tree): Tree = tree match { - // TODO: better error messages when returning nothing - case q"return $expr" => q"return $globalNamingStack.popReturnContext($expr, $contextVar)" - // Do not recurse into methods - case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" => tree - case other => super.transform(other) - } - } - - /** Applies the val name transform to a class body. - * Closes context on top level or return local context to englobing context. - * Closing context only makes sense when top level a Module. - * A Module is always the naming top level. - * Transformed classes can be either Module or standard class. - */ - def transformClassBody(stats: List[c.Tree]): Tree = { - val contextVar = TermName(c.freshName("namingContext")) - val transformedBody = (new ClassBodyTransformer(contextVar)).transformTrees(stats) - // Note: passing "this" to popReturnContext is mandatory for propagation through non-module classes - q""" - val $contextVar = $globalNamingStack.pushContext() - ..$transformedBody - if($globalNamingStack.length == 1){ - $contextVar.namePrefix("") - } - $globalNamingStack.popReturnContext(this, $contextVar) - """ - } - - /** Applies the val name transform to a method body, doing additional bookkeeping with the - * context to allow names to propagate and prefix through the function call stack. - */ - def transformHierarchicalMethod(expr: c.Tree): Tree = { - val contextVar = TermName(c.freshName("namingContext")) - val transformedBody = (new MethodTransformer(contextVar)).transform(expr) - - q"""{ - val $contextVar = $globalNamingStack.pushContext() - $globalNamingStack.popReturnContext($transformedBody, $contextVar) - } - """ - } - - /** Applies naming transforms to vals in the annotated module or method. - * - * For methods, a hierarchical naming transform is used, where it will try to give objects names - * based on the call stack, assuming all functions on the stack are annotated as such and return - * a non-AnyVal object. Does not recurse into inner functions. - * - * For modules, this serves as the root of the call stack hierarchy for naming purposes. Methods - * will have chiselName annotations (non-recursively), but this does NOT affect inner classes. - * - * Basically rewrites all instances of: - * val name = expr - * to: - * val name = context.name(expr, name) - */ - def chiselName(annottees: c.Tree*): c.Tree = { - var namedElts: Int = 0 - - val transformed = annottees.map(annottee => annottee match { - // scalastyle:off line.size.limit - case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => { - val transformedStats = transformClassBody(stats) - namedElts += 1 - q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$transformedStats }" - } - case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" => { - annottee // Don't fail noisly when a companion object is passed in with the actual class def - } - // Currently disallow on traits, this won't work well with inheritance. - case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" => { - val transformedExpr = transformHierarchicalMethod(expr) - namedElts += 1 - q"$mods def $tname[..$tparams](...$paramss): $tpt = $transformedExpr" - } - case other => c.abort(c.enclosingPosition, s"@chiselName annotion may only be used on classes and methods, got ${showCode(other)}") - }) - - if (namedElts != 1) { - c.abort(c.enclosingPosition, s"@chiselName annotation did not match exactly one valid tree, got:\r\n${annottees.map(tree => showCode(tree)).mkString("\r\n\r\n")}") - } - // scalastyle:on line.size.limit - - q"..$transformed" - } -} - -@compileTimeOnly("enable macro paradise to expand macro annotations") -class dump extends StaticAnnotation { // scalastyle:ignore class.name - def macroTransform(annottees: Any*): Any = macro chisel3.internal.naming.DebugTransforms.dump -} -@compileTimeOnly("enable macro paradise to expand macro annotations") -class treedump extends StaticAnnotation { // scalastyle:ignore class.name - def macroTransform(annottees: Any*): Any = macro chisel3.internal.naming.DebugTransforms.treedump -} -@compileTimeOnly("enable macro paradise to expand macro annotations") -class chiselName extends StaticAnnotation { // scalastyle:ignore class.name - def macroTransform(annottees: Any*): Any = macro chisel3.internal.naming.NamingTransforms.chiselName -} diff --git a/src/test/scala/chiselTests/NamingAnnotationTest.scala b/src/test/scala/chiselTests/NamingAnnotationTest.scala index e35c77c1..a02dfe56 100644 --- a/src/test/scala/chiselTests/NamingAnnotationTest.scala +++ b/src/test/scala/chiselTests/NamingAnnotationTest.scala @@ -203,6 +203,32 @@ class PartialNamedModule extends NamedModuleTester { val test = innerNamedFunction() } +@chiselName +class NoChiselNamePrefixTester extends NamedModuleTester { + @chiselName + class NoChiselNamePrefixClass extends chisel3.experimental.NoChiselNamePrefix { + val a = expectName(1.U +& 2.U, "a") + } + val inst = new NoChiselNamePrefixClass + @chiselName + class NormalClass { + val b = 1.U +& 2.U + } + val foo = new NormalClass + expectName(foo.b, "foo_b") + val bar = new NormalClass with chisel3.experimental.NoChiselNamePrefix + expectName(bar.b, "b") + + // Check that we're not matching by name but actual type + trait NoChiselNamePrefix + @chiselName + class FakeNoChiselNamePrefix extends NoChiselNamePrefix { + val c = 1.U +& 2.U + } + val fizz = new FakeNoChiselNamePrefix + expectName(fizz.c, "fizz_c") +} + /** A simple test that checks the recursive function val naming annotation both compiles and * generates the expected names. @@ -240,4 +266,10 @@ class NamingAnnotationSpec extends ChiselPropSpec { property("NonBuilderFunction should run outside a Builder context") { NonNamedHelper.NonBuilderFunction() should be (2) } + + property("NoChiselNamePrefix should prevent prefixing when using @chiselName") { + var module: NoChiselNamePrefixTester = null + elaborate { module = new NoChiselNamePrefixTester; module } + assert(module.getNameFailures().isEmpty) + } } -- cgit v1.2.3