Skip to content

Commit d37902d

Browse files
committed
Support polymorphic beta-reduction while inlining
1 parent 223c8b7 commit d37902d

File tree

5 files changed

+78
-44
lines changed

5 files changed

+78
-44
lines changed

compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ import NameKinds.{InlineAccessorName, InlineBinderName, InlineScrutineeName}
1212
import config.Printers.inlining
1313
import util.SimpleIdentityMap
1414

15-
import dotty.tools.dotc.transform.BetaReduce
16-
1715
import collection.mutable
1816

1917
/** A utility class offering methods for rewriting inlined code */
@@ -150,45 +148,6 @@ class InlineReducer(inliner: Inliner)(using Context):
150148
binding1.withSpan(call.span)
151149
}
152150

153-
/** Rewrite an application
154-
*
155-
* ((x1, ..., xn) => b)(e1, ..., en)
156-
*
157-
* to
158-
*
159-
* val/def x1 = e1; ...; val/def xn = en; b
160-
*
161-
* where `def` is used for call-by-name parameters. However, we shortcut any NoPrefix
162-
* refs among the ei's directly without creating an intermediate binding.
163-
*
164-
* This variant of beta-reduction preserves the integrity of `Inlined` tree nodes.
165-
*/
166-
def betaReduce(tree: Tree)(using Context): Tree = tree match {
167-
case Apply(Select(cl, nme.apply), args) if defn.isFunctionType(cl.tpe) =>
168-
val bindingsBuf = new mutable.ListBuffer[DefTree]
169-
def recur(cl: Tree): Option[Tree] = cl match
170-
case Block((ddef : DefDef) :: Nil, closure: Closure) if ddef.symbol == closure.meth.symbol =>
171-
ddef.tpe.widen match
172-
case mt: MethodType if ddef.paramss.head.length == args.length =>
173-
// TODO beta reduce PolyType
174-
Some(BetaReduce.reduceApplication(ddef, List(args), bindingsBuf))
175-
case _ => None
176-
case Block(stats, expr) if stats.forall(isPureBinding) =>
177-
recur(expr).map(cpy.Block(cl)(stats, _))
178-
case Inlined(call, bindings, expr) if bindings.forall(isPureBinding) =>
179-
recur(expr).map(cpy.Inlined(cl)(call, bindings, _))
180-
case Typed(expr, tpt) =>
181-
recur(expr)
182-
case _ => None
183-
recur(cl) match
184-
case Some(reduced) =>
185-
seq(bindingsBuf.result(), reduced).withSpan(tree.span)
186-
case None =>
187-
tree
188-
case _ =>
189-
tree
190-
}
191-
192151
/** The result type of reducing a match. It consists optionally of a list of bindings
193152
* for the pattern-bound variables and the RHS of the selected case.
194153
* Returns `None` if no case was selected.

compiler/src/dotty/tools/dotc/inlines/Inliner.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import collection.mutable
2121
import reporting.trace
2222
import util.Spans.Span
2323
import dotty.tools.dotc.transform.Splicer
24+
import dotty.tools.dotc.transform.BetaReduce
2425
import quoted.QuoteUtils
2526
import scala.annotation.constructorOnly
2627

@@ -811,7 +812,7 @@ class Inliner(val call: tpd.Tree)(using Context):
811812
case Quoted(Spliced(inner)) => inner
812813
case _ => tree
813814
val locked = ctx.typerState.ownedVars
814-
val res = cancelQuotes(constToLiteral(betaReduce(super.typedApply(tree, pt)))) match {
815+
val res = cancelQuotes(constToLiteral(BetaReduce(super.typedApply(tree, pt)))) match {
815816
case res: Apply if res.symbol == defn.QuotedRuntime_exprSplice
816817
&& StagingContext.level == 0
817818
&& !hasInliningErrors =>
@@ -824,7 +825,7 @@ class Inliner(val call: tpd.Tree)(using Context):
824825

825826
override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree =
826827
val locked = ctx.typerState.ownedVars
827-
val tree1 = inlineIfNeeded(constToLiteral(betaReduce(super.typedTypeApply(tree, pt))), pt, locked)
828+
val tree1 = inlineIfNeeded(constToLiteral(BetaReduce(super.typedTypeApply(tree, pt))), pt, locked)
828829
if tree1.symbol.isQuote then
829830
ctx.compilationUnit.needsStaging = true
830831
tree1
@@ -1005,7 +1006,7 @@ class Inliner(val call: tpd.Tree)(using Context):
10051006
super.transform(t1)
10061007
case t: Apply =>
10071008
val t1 = super.transform(t)
1008-
if (t1 `eq` t) t else reducer.betaReduce(t1)
1009+
if (t1 `eq` t) t else BetaReduce(t1)
10091010
case Block(Nil, expr) =>
10101011
super.transform(expr)
10111012
case _ =>

compiler/src/dotty/tools/dotc/transform/BetaReduce.scala

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,68 @@ object BetaReduce:
5656
val name: String = "betaReduce"
5757
val description: String = "reduce closure applications"
5858

59+
/** Rewrite an application
60+
*
61+
* ((x1, ..., xn) => b)(e1, ..., en)
62+
*
63+
* to
64+
*
65+
* val/def x1 = e1; ...; val/def xn = en; b
66+
*
67+
* where `def` is used for call-by-name parameters. However, we shortcut any NoPrefix
68+
* refs among the ei's directly without creating an intermediate binding.
69+
*
70+
* Similarly, rewrites type applications
71+
*
72+
* ([X1, ..., Xm] => (x1, ..., xn) => b).apply[T1, .., Tm](e1, ..., en)
73+
*
74+
* to
75+
*
76+
* type X1 = T1; ...; type Xm = Tm;val/def x1 = e1; ...; val/def xn = en; b
77+
*
78+
* This beta-reduction preserves the integrity of `Inlined` tree nodes.
79+
*/
80+
def apply(tree: Tree)(using Context): Tree =
81+
val bindingsBuf = new ListBuffer[DefTree]
82+
def recur(fn: Tree, argss: List[List[Tree]]): Option[Tree] = fn match
83+
case Block((ddef : DefDef) :: Nil, closure: Closure) if ddef.symbol == closure.meth.symbol =>
84+
ddef.tpe.widen match // TODO can these guards be removed?
85+
case mt: MethodType if ddef.paramss.head.length == argss.head.length =>
86+
Some(reduceApplication(ddef, argss, bindingsBuf))
87+
case _ => None
88+
case Block((TypeDef(_, template: Template)) :: Nil, Typed(Apply(Select(New(_), _), _), _)) if template.constr.rhs.isEmpty =>
89+
template.body match
90+
case (ddef: DefDef) :: Nil =>
91+
ddef.tpe.widen match // TODO can these guards be removed?
92+
case mt: MethodType if ddef.paramss.head.length == argss.head.length =>
93+
Some(reduceApplication(ddef, argss, bindingsBuf))
94+
case mt: PolyType if ddef.paramss.head.length == argss.head.length && ddef.paramss.last.length == argss.last.length =>
95+
Some(reduceApplication(ddef, argss, bindingsBuf))
96+
case _ => None
97+
case _ => None
98+
case Block(stats, expr) if stats.forall(isPureBinding) =>
99+
recur(expr, argss).map(cpy.Block(fn)(stats, _))
100+
case Inlined(call, bindings, expr) if bindings.forall(isPureBinding) =>
101+
recur(expr, argss).map(cpy.Inlined(fn)(call, bindings, _))
102+
case Typed(expr, tpt) =>
103+
recur(expr, argss)
104+
case _ => None
105+
tree match
106+
case Apply(Select(fn, nme.apply), args) if defn.isFunctionType(fn.tpe) =>
107+
recur(fn, List(args)) match
108+
case Some(reduced) =>
109+
seq(bindingsBuf.result(), reduced).withSpan(tree.span)
110+
case None =>
111+
tree
112+
case Apply(TypeApply(Select(fn, nme.apply), targs), args) if fn.tpe.typeSymbol eq dotc.core.Symbols.defn.PolyFunctionClass =>
113+
recur(fn, List(targs, args)) match
114+
case Some(reduced) =>
115+
seq(bindingsBuf.result(), reduced).withSpan(tree.span)
116+
case None =>
117+
tree
118+
case _ =>
119+
tree
120+
59121
/** Beta-reduces a call to `fn` with arguments `argSyms` or returns `tree` */
60122
def apply(original: Tree, fn: Tree, argss: List[List[Tree]])(using Context): Tree =
61123
fn match
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
type X = Int
3+
{
4+
println(1)
5+
1
6+
}
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
transparent inline def foo(inline f: [X] => X => X): Int = f[Int](1)
2+
3+
@main def Test: Unit =
4+
val code = compiletime.codeOf(foo([X] => (x: X) => { println(x); x }))
5+
println(code)

0 commit comments

Comments
 (0)