Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,199 @@

package chisel3.experimental.hierarchy

import chisel3.experimental.{BaseModule, SourceInfo}
import scala.quoted.*

private[chisel3] trait InstantiateIntf { self: Instantiate.type =>
// TODO implement

/** Create an `Instance` of a `Module`
*
* This is similar to `Module(...)` except that it returns an `Instance[_]` object.
*
* @param con module construction, must be actual call to constructor (`new MyModule(...)`)
* @return constructed module `Instance`
*/
inline def apply[A <: BaseModule](inline con: A): Instance[A] = ${ InstantiateIntfMacros.instanceMacro[A]('con) }

inline def definition[A <: BaseModule](inline con: A): Definition[A] = ${
InstantiateIntfMacros.definitionMacro[A]('con)
}

def _instance[K, A <: BaseModule](
args: K,
f: K => A,
tt: AnyRef
)(
using sourceInfo: SourceInfo
): Instance[A] = {
_instanceImpl(args, f, tt)
}

/** Internal method for creating a Definition from extracted arguments
* This is not part of the public API, do not call directly!
*/
def _definition[K, A <: BaseModule](
args: K,
f: K => A,
tt: AnyRef
): Definition[A] = _definitionImpl(args, f, tt)
}

private object InstantiateIntfMacros {
import scala.quoted.*

private def extractAndBuild[A <: BaseModule: Type](
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment describing what this returns? It appears to be the arguments as a tuple and then the constructor function value, but it'd be good to have that in English.

con: Expr[A]
)(using q: Quotes): (Expr[Any], Expr[_]) = {
import q.reflect.*
def unwrapInlined(term: Term): Term = term match {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment describing would be good

case Inlined(_, _, expansion) => unwrapInlined(expansion)
case other => other
}

def collectArgs(
term: Term,
accFlat: List[Term],
accStructured: List[List[Term]]
): (Term, List[Term], List[List[Term]]) = term match {
case app @ Apply(inner, newArgs) =>
val isImplicit = inner.tpe.widen match {
case mt: MethodType => mt.isImplicit
case _ => false
}

if (isImplicit) {
collectArgs(inner, accFlat, accStructured)
} else {
collectArgs(inner, newArgs ::: accFlat, newArgs :: accStructured)
}
Comment on lines +61 to +70
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we dropping implicit arguments? That does not seem right

case other => (other, accFlat, accStructured)
}

val unwrapped = unwrapInlined(con.asTerm)
val (core, args, argStructure) = collectArgs(unwrapped, Nil, Nil)

val (tpt, typeArgs, fullConstructorTerm) = core match {
case Select(New(tpt), _) =>
(tpt, None, unwrapped)
case TypeApply(Select(New(tpt), _), targs) =>
(tpt, Some(targs), unwrapped)
Comment on lines +78 to +81
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these two cases?

case _ =>
report.errorAndAbort(
s"Invalid arguments: ${con.show}",
con.asTerm.pos
)
}

// Helper to match the original parameter list structure by distributing flat args
def matchStructure(
structure: List[List[Term]],
flatArgs: List[Term]
): List[List[Term]] = {
val it = flatArgs.iterator
structure.map(_.map(_ => it.next()))
}

def buildConstructorLambdaMultiList(
paramTypes: List[TypeRepr],
reconstructArgStructure: List[Any] => List[List[Term]],
paramName: String
): Expr[_] = {
Lambda(
Symbol.spliceOwner,
MethodType(List(paramName))(_ => paramTypes, _ => TypeRepr.of[A]),
(sym, params) => {
val argss = reconstructArgStructure(params)
// Build nested Apply nodes for multiple parameter lists
val base = typeArgs match {
case Some(targs) => TypeApply(Select(New(tpt), tpt.tpe.typeSymbol.primaryConstructor), targs)
case None => Select(New(tpt), tpt.tpe.typeSymbol.primaryConstructor)
}
val result = argss.foldLeft(base)((acc, args) => Apply(acc, args))
result.asExprOf[A].asTerm.changeOwner(sym)
}
).asExpr
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the mix of vals and defs hard to follow, can you collect all of the defs separately from the vals? Maybe as separate private defs outside of extractAndBuild or at least at the top of the function?

val argExprs = args.map(_.asExpr)

val argsTuple: Expr[Any] = argExprs.size match {
case 0 => '{ () }
case 1 => argExprs.head
case _ => Expr.ofTupleFromSeq(argExprs)
}

val constructorFunc: Expr[_] = argExprs.size match {
case 0 =>
Lambda(
Symbol.spliceOwner,
MethodType(List("x"))(_ => List(TypeRepr.of[Unit]), _ => TypeRepr.of[A]),
(sym, _) => fullConstructorTerm.changeOwner(sym)
).asExpr
case 1 =>
val argTpe = args.head.tpe.widen
buildConstructorLambdaMultiList(
List(argTpe),
params => {
val singleArg = params.head.asInstanceOf[Term]
matchStructure(argStructure, List(singleArg))
},
"arg"
)
case n =>
val argTypes = args.map(_.tpe.widen)
val tupleTypeRepr = argTypes match {
case List(t1, t2) =>
AppliedType(TypeRepr.of[Tuple2].typeSymbol.typeRef, List(t1, t2))
case List(t1, t2, t3) =>
AppliedType(TypeRepr.of[Tuple3].typeSymbol.typeRef, List(t1, t2, t3))
case _ =>
AppliedType(defn.TupleClass(n).typeRef, argTypes)
Comment on lines +147 to +152
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it standard to have to handle tuple 2 and tuple 3 specially? If so please add a comment.

}
buildConstructorLambdaMultiList(
List(tupleTypeRepr),
params => {
val flatArgs = (1 to n).map(i => Select.unique(params.head.asInstanceOf[Term], s"_$i")).toList
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The repeated use of params.head.asInstanceOf[Term] looks like a bug, I'm assuming it isn't. Can you hoist it out of the map and give it a meaningful name and possibly comment describing what it is?

matchStructure(argStructure, flatArgs)
},
"tupArg"
)
}

(argsTuple, constructorFunc)
}

// Transform `Instantiate(new MyModule(arg1, arg2))` into
// `Instantiate._instance((arg1, arg2), args => new MyModule(args._1, args._2), typeToken)(using sourceInfo)`
def instanceMacro[A <: BaseModule: Type](con: Expr[A])(using q: Quotes): Expr[Instance[A]] = {
import q.reflect.*
val sourceInfoExpr = Expr.summon[SourceInfo].getOrElse {
report.errorAndAbort("Instantiate.apply requires an implicit SourceInfo")
}
Comment on lines +171 to +173
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just thread this through the user API instead so the user can pass it manually if they want?

val (argsTuple, constructorFunc) = extractAndBuild[A](con)
val typeRepr = TypeRepr.of[A]
val typeToken = Expr(typeRepr.show)

'{
chisel3.experimental.hierarchy.Instantiate._instance(
$argsTuple.asInstanceOf[Any],
$constructorFunc.asInstanceOf[Any => A],
$typeToken.asInstanceOf[AnyRef]
)(using $sourceInfoExpr)
}
}

def definitionMacro[A <: BaseModule: Type](con: Expr[A])(using q: Quotes): Expr[core.Definition[A]] = {
import q.reflect.*
val (argsTuple, constructorFunc) = extractAndBuild[A](con)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does instanceMacro have a source locator but this doesn't?

val typeRepr = TypeRepr.of[A]
val typeToken = Expr(typeRepr.show)
'{
chisel3.experimental.hierarchy.Instantiate._definition(
$argsTuple.asInstanceOf[Any],
$constructorFunc.asInstanceOf[Any => A],
$typeToken.asInstanceOf[AnyRef]
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package chiselTests
package experimental.hierarchy

import chisel3._
import chisel3.aop.Select
import chisel3.util.Valid
import chisel3.properties._
import chisel3.experimental.hierarchy._
Expand Down Expand Up @@ -229,7 +228,7 @@ class InstantiateSpec extends AnyFunSpec with Matchers with FileCheck {
describe("Module classes that take only implicit arguments") {
it("should be Instantiate-able if there are only a single implicit argument") {
emitCHIRRTL(new Top {
implicit val n = 3
implicit val n: Int = 3
val inst0 = Instantiate(new OneImplicitArg)
val inst1 = Instantiate(new OneImplicitArg)
}).fileCheck()(
Expand All @@ -241,8 +240,8 @@ class InstantiateSpec extends AnyFunSpec with Matchers with FileCheck {

it("should be Instantiate-able if there are multiple implicit arguments") {
emitCHIRRTL(new Top {
implicit val n = 3
implicit val str = "4"
implicit val n: Int = 3
implicit val str: String = "4"
val inst0 = Instantiate(new TwoImplicitArgs)
val inst1 = Instantiate(new TwoImplicitArgs)
}).fileCheck()(
Expand All @@ -254,8 +253,8 @@ class InstantiateSpec extends AnyFunSpec with Matchers with FileCheck {

it("should be Instantiate-able when arguments are passed manually") {
emitCHIRRTL(new Top {
implicit val n = 5
implicit val str = "6"
implicit val n: Int = 5
implicit val str: String = "6"
val inst0 = Instantiate(new TwoImplicitArgs)
val inst1 = Instantiate(new TwoImplicitArgs()(n, str))
}).fileCheck()(
Expand All @@ -269,7 +268,7 @@ class InstantiateSpec extends AnyFunSpec with Matchers with FileCheck {
describe("Module classes that take a single argument list") {
it("should be Instantiate-able when there is only a single argument") {
emitCHIRRTL(new Top {
val n = 3
val n: Int = 3
val inst0 = Instantiate(new OneArg(3))
val inst1 = Instantiate(new OneArg(n))
}).fileCheck()(
Expand Down Expand Up @@ -484,8 +483,9 @@ class InstantiateSpec extends AnyFunSpec with Matchers with FileCheck {

describe("Instantiate") {
it("should provide source locators for module instances") {
// Materialize the source info so we can use it in the check
implicit val info = implicitly[chisel3.experimental.SourceInfo]
// Make a source info so we can use it in the check
implicit val info: chisel3.experimental.SourceInfo =
chisel3.experimental.SourceLine("InstantiateSpec.scala", 1, 2)
val chirrtl = emitCHIRRTL(new Top {
val inst = Instantiate(new OneArg(3))
})
Expand Down
Loading