-
Notifications
You must be signed in to change notification settings - Fork 649
[Scala3] Add Instantiate API #5278
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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]( | ||
| con: Expr[A] | ||
| )(using q: Quotes): (Expr[Any], Expr[_]) = { | ||
| import q.reflect.* | ||
| def unwrapInlined(term: Term): Term = term match { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The repeated use of |
||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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] | ||
| ) | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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.