From e02c9e1086fb78b0e78039fbd62d5980c1bd0c8b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 5 Jan 2023 17:51:38 +0000 Subject: [PATCH 1/8] Beta-reduce directly applied PolymorphicFunction --- .../tools/dotc/inlines/InlineReducer.scala | 5 +- .../tools/dotc/transform/BetaReduce.scala | 53 ++++++++++++++----- .../tools/dotc/transform/InlinePatterns.scala | 3 +- .../tools/dotc/transform/PickleQuotes.scala | 2 +- .../quoted/runtime/impl/QuotesImpl.scala | 3 +- .../backend/jvm/InlineBytecodeTests.scala | 26 +++++++++ 6 files changed, 73 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala index 42e86b71eff8..80a1546e0356 100644 --- a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala +++ b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala @@ -165,12 +165,13 @@ class InlineReducer(inliner: Inliner)(using Context): */ def betaReduce(tree: Tree)(using Context): Tree = tree match { case Apply(Select(cl, nme.apply), args) if defn.isFunctionType(cl.tpe) => - val bindingsBuf = new mutable.ListBuffer[ValDef] + val bindingsBuf = new mutable.ListBuffer[DefTree] def recur(cl: Tree): Option[Tree] = cl match case Block((ddef : DefDef) :: Nil, closure: Closure) if ddef.symbol == closure.meth.symbol => ddef.tpe.widen match case mt: MethodType if ddef.paramss.head.length == args.length => - Some(BetaReduce.reduceApplication(ddef, args, bindingsBuf)) + // TODO beta reduce PolyType + Some(BetaReduce.reduceApplication(ddef, List(args), bindingsBuf)) case _ => None case Block(stats, expr) if stats.forall(isPureBinding) => recur(expr).map(cpy.Block(cl)(stats, _)) diff --git a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala index 7ac3dc972ad1..00bb83175627 100644 --- a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala +++ b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala @@ -40,13 +40,16 @@ class BetaReduce extends MiniPhase: override def transformApply(app: Apply)(using Context): Tree = app.fun match case Select(fn, nme.apply) if defn.isFunctionType(fn.tpe) => - val app1 = BetaReduce(app, fn, app.args) + val app1 = BetaReduce(app, fn, List(app.args)) + if app1 ne app then report.log(i"beta reduce $app -> $app1") + app1 + case TypeApply(Select(fn, nme.apply), targs) if fn.tpe.typeSymbol eq defn.PolyFunctionClass => + val app1 = BetaReduce(app, fn, List(targs, app.args)) if app1 ne app then report.log(i"beta reduce $app -> $app1") app1 case _ => app - object BetaReduce: import ast.tpd._ @@ -54,18 +57,24 @@ object BetaReduce: val description: String = "reduce closure applications" /** Beta-reduces a call to `fn` with arguments `argSyms` or returns `tree` */ - def apply(original: Tree, fn: Tree, args: List[Tree])(using Context): Tree = + def apply(original: Tree, fn: Tree, argss: List[List[Tree]])(using Context): Tree = fn match case Typed(expr, _) => - BetaReduce(original, expr, args) + BetaReduce(original, expr, argss) case Block((anonFun: DefDef) :: Nil, closure: Closure) => - BetaReduce(anonFun, args) + BetaReduce(anonFun, argss) + case Block((TypeDef(_, template: Template)) :: Nil, Typed(Apply(Select(New(_), _), _), _)) if template.constr.rhs.isEmpty => + template.body match + case (anonFun: DefDef) :: Nil => + BetaReduce(anonFun, argss) + case _ => + original case Block(stats, expr) => - val tree = BetaReduce(original, expr, args) + val tree = BetaReduce(original, expr, argss) if tree eq original then original else cpy.Block(fn)(stats, tree) case Inlined(call, bindings, expr) => - val tree = BetaReduce(original, expr, args) + val tree = BetaReduce(original, expr, argss) if tree eq original then original else cpy.Inlined(fn)(call, bindings, tree) case _ => @@ -73,16 +82,32 @@ object BetaReduce: end apply /** Beta-reduces a call to `ddef` with arguments `args` */ - def apply(ddef: DefDef, args: List[Tree])(using Context) = - val bindings = new ListBuffer[ValDef]() - val expansion1 = reduceApplication(ddef, args, bindings) + def apply(ddef: DefDef, argss: List[List[Tree]])(using Context) = + val bindings = new ListBuffer[DefTree]() + val expansion1 = reduceApplication(ddef, argss, bindings) val bindings1 = bindings.result() seq(bindings1, expansion1) /** Beta-reduces a call to `ddef` with arguments `args` and registers new bindings */ - def reduceApplication(ddef: DefDef, args: List[Tree], bindings: ListBuffer[ValDef])(using Context): Tree = - val vparams = ddef.termParamss.iterator.flatten.toList + def reduceApplication(ddef: DefDef, argss: List[List[Tree]], bindings: ListBuffer[DefTree])(using Context): Tree = + assert(argss.size == 1 || argss.size == 2) + val targs = if argss.size == 2 then argss.head else Nil + val args = argss.last + val tparams = ddef.leadingTypeParams + val vparams = ddef.termParamss.flatten + assert(targs.hasSameLengthAs(tparams)) assert(args.hasSameLengthAs(vparams)) + + val targSyms = + for (targ, tparam) <- targs.zip(tparams) yield + targ.tpe.dealias match + case ref @ TypeRef(NoPrefix, _) => + ref.symbol + case _ => + val binding = TypeDef(newSymbol(ctx.owner, tparam.name, EmptyFlags, targ.tpe, coord = targ.span)).withSpan(targ.span) + bindings += binding + binding.symbol + val argSyms = for (arg, param) <- args.zip(vparams) yield arg.tpe.dealias match @@ -99,8 +124,8 @@ object BetaReduce: val expansion = TreeTypeMap( oldOwners = ddef.symbol :: Nil, newOwners = ctx.owner :: Nil, - substFrom = vparams.map(_.symbol), - substTo = argSyms + substFrom = (tparams ::: vparams).map(_.symbol), + substTo = targSyms ::: argSyms ).transform(ddef.rhs) val expansion1 = new TreeMap { diff --git a/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala b/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala index 6edb60a77245..b1d3bb68cf76 100644 --- a/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala +++ b/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala @@ -51,11 +51,12 @@ class InlinePatterns extends MiniPhase: case Apply(App(fn, argss), args) => (fn, argss :+ args) case _ => (app, Nil) + // TODO merge with BetaReduce.scala private def betaReduce(tree: Apply, fn: Tree, name: Name, args: List[Tree])(using Context): Tree = fn match case Block(TypeDef(_, template: Template) :: Nil, Apply(Select(New(_),_), Nil)) if template.constr.rhs.isEmpty => template.body match - case List(ddef @ DefDef(`name`, _, _, _)) => BetaReduce(ddef, args) + case List(ddef @ DefDef(`name`, _, _, _)) => BetaReduce(ddef, List(args)) case _ => tree case _ => tree diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 21fc27cec0dd..732d8d41b8d6 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -322,7 +322,7 @@ object PickleQuotes { } val Block(List(ddef: DefDef), _) = splice: @unchecked // TODO: beta reduce inner closure? Or wait until BetaReduce phase? - BetaReduce(ddef, spliceArgs).select(nme.apply).appliedTo(args(2).asInstance(quotesType)) + BetaReduce(ddef, List(spliceArgs)).select(nme.apply).appliedTo(args(2).asInstance(quotesType)) } CaseDef(Literal(Constant(idx)), EmptyTree, rhs) } diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 1ec13ba832c9..7cbf05871e08 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -362,8 +362,9 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object Term extends TermModule: def betaReduce(tree: Term): Option[Term] = tree match + // TODO support TypeApply. Would fix #15968. case app @ tpd.Apply(tpd.Select(fn, nme.apply), args) if dotc.core.Symbols.defn.isFunctionType(fn.tpe) => - val app1 = dotc.transform.BetaReduce(app, fn, args) + val app1 = dotc.transform.BetaReduce(app, fn, List(args)) if app1 eq app then None else Some(app1.withSpan(tree.span)) case tpd.Block(Nil, expr) => diff --git a/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala index 33e898718b33..a9cec06880a2 100644 --- a/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala @@ -582,6 +582,32 @@ class InlineBytecodeTests extends DottyBytecodeTest { } } + @Test def beta_reduce_polymorphic_function = { + val source = """class Test: + | def test = + | ([Z] => (arg: Z) => { val a: Z = arg; a }).apply[Int](2) + """.stripMargin + + checkBCode(source) { dir => + val clsIn = dir.lookupName("Test.class", directory = false).input + val clsNode = loadClassNode(clsIn) + + val fun = getMethod(clsNode, "test") + val instructions = instructionsFromMethod(fun) + val expected = + List( + Op(ICONST_2), + VarOp(ISTORE, 1), + VarOp(ILOAD, 1), + Op(IRETURN) + ) + + assert(instructions == expected, + "`i was not properly beta-reduced in `test`\n" + diffInstructions(instructions, expected)) + + } + } + @Test def i9456 = { val source = """class Foo { | def test: Int = inline2(inline1(2.+)) From 223c8b7d697638b9a1fd971552753b78d78da0e7 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 9 Jan 2023 09:32:33 +0100 Subject: [PATCH 2/8] `Expr.betaReduce` support for polymorphic function Fixes #15968 --- .../scala/quoted/runtime/impl/QuotesImpl.scala | 5 ++++- tests/run-macros/i15968.check | 5 +++++ tests/run-macros/i15968/Macro_1.scala | 15 +++++++++++++++ tests/run-macros/i15968/Test_2.scala | 3 +++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 tests/run-macros/i15968.check create mode 100644 tests/run-macros/i15968/Macro_1.scala create mode 100644 tests/run-macros/i15968/Test_2.scala diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 7cbf05871e08..277cfd9f8cfb 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -362,11 +362,14 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object Term extends TermModule: def betaReduce(tree: Term): Option[Term] = tree match - // TODO support TypeApply. Would fix #15968. case app @ tpd.Apply(tpd.Select(fn, nme.apply), args) if dotc.core.Symbols.defn.isFunctionType(fn.tpe) => val app1 = dotc.transform.BetaReduce(app, fn, List(args)) if app1 eq app then None else Some(app1.withSpan(tree.span)) + case app @ tpd.Apply(tpd.TypeApply(tpd.Select(fn, nme.apply), targs), args) if fn.tpe.typeSymbol eq dotc.core.Symbols.defn.PolyFunctionClass => + val app1 = dotc.transform.BetaReduce(app, fn, List(targs, app.args)) + if app1 eq app then None + else Some(app1.withSpan(tree.span)) case tpd.Block(Nil, expr) => for e <- betaReduce(expr) yield tpd.cpy.Block(tree)(Nil, e) case tpd.Inlined(_, Nil, expr) => diff --git a/tests/run-macros/i15968.check b/tests/run-macros/i15968.check new file mode 100644 index 000000000000..c7f3847d404c --- /dev/null +++ b/tests/run-macros/i15968.check @@ -0,0 +1,5 @@ +{ + type Z = java.lang.String + "foo".toString() +} +"foo".toString() diff --git a/tests/run-macros/i15968/Macro_1.scala b/tests/run-macros/i15968/Macro_1.scala new file mode 100644 index 000000000000..ea2728840d6e --- /dev/null +++ b/tests/run-macros/i15968/Macro_1.scala @@ -0,0 +1,15 @@ +import scala.quoted.* + +inline def macroPolyFun[A](inline arg: A, inline f: [Z] => Z => String): String = + ${ macroPolyFunImpl[A]('arg, 'f) } + +private def macroPolyFunImpl[A: Type](arg: Expr[A], f: Expr[[Z] => Z => String])(using Quotes): Expr[String] = + Expr(Expr.betaReduce('{ $f($arg) }).show) + + +inline def macroFun[A](inline arg: A, inline f: A => String): String = + ${ macroFunImpl[A]('arg, 'f) } + +private def macroFunImpl[A: Type](arg: Expr[A], f: Expr[A => String])(using Quotes): Expr[String] = + Expr(Expr.betaReduce('{ $f($arg) }).show) + diff --git a/tests/run-macros/i15968/Test_2.scala b/tests/run-macros/i15968/Test_2.scala new file mode 100644 index 000000000000..6c6826f96b34 --- /dev/null +++ b/tests/run-macros/i15968/Test_2.scala @@ -0,0 +1,3 @@ +@main def Test: Unit = + println(macroPolyFun("foo", [Z] => (arg: Z) => arg.toString)) + println(macroFun("foo", arg => arg.toString)) From d37902d9983397e1bf6ce6ad34bdb00788fbb661 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 9 Jan 2023 10:21:46 +0100 Subject: [PATCH 3/8] Support polymorphic beta-reduction while inlining --- .../tools/dotc/inlines/InlineReducer.scala | 41 ------------ .../dotty/tools/dotc/inlines/Inliner.scala | 7 ++- .../tools/dotc/transform/BetaReduce.scala | 62 +++++++++++++++++++ .../inline-beta-reduce-polyfunction.check | 7 +++ .../inline-beta-reduce-polyfunction.scala | 5 ++ 5 files changed, 78 insertions(+), 44 deletions(-) create mode 100644 tests/run-macros/inline-beta-reduce-polyfunction.check create mode 100644 tests/run-macros/inline-beta-reduce-polyfunction.scala diff --git a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala index 80a1546e0356..547160340238 100644 --- a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala +++ b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala @@ -12,8 +12,6 @@ import NameKinds.{InlineAccessorName, InlineBinderName, InlineScrutineeName} import config.Printers.inlining import util.SimpleIdentityMap -import dotty.tools.dotc.transform.BetaReduce - import collection.mutable /** A utility class offering methods for rewriting inlined code */ @@ -150,45 +148,6 @@ class InlineReducer(inliner: Inliner)(using Context): binding1.withSpan(call.span) } - /** Rewrite an application - * - * ((x1, ..., xn) => b)(e1, ..., en) - * - * to - * - * val/def x1 = e1; ...; val/def xn = en; b - * - * where `def` is used for call-by-name parameters. However, we shortcut any NoPrefix - * refs among the ei's directly without creating an intermediate binding. - * - * This variant of beta-reduction preserves the integrity of `Inlined` tree nodes. - */ - def betaReduce(tree: Tree)(using Context): Tree = tree match { - case Apply(Select(cl, nme.apply), args) if defn.isFunctionType(cl.tpe) => - val bindingsBuf = new mutable.ListBuffer[DefTree] - def recur(cl: Tree): Option[Tree] = cl match - case Block((ddef : DefDef) :: Nil, closure: Closure) if ddef.symbol == closure.meth.symbol => - ddef.tpe.widen match - case mt: MethodType if ddef.paramss.head.length == args.length => - // TODO beta reduce PolyType - Some(BetaReduce.reduceApplication(ddef, List(args), bindingsBuf)) - case _ => None - case Block(stats, expr) if stats.forall(isPureBinding) => - recur(expr).map(cpy.Block(cl)(stats, _)) - case Inlined(call, bindings, expr) if bindings.forall(isPureBinding) => - recur(expr).map(cpy.Inlined(cl)(call, bindings, _)) - case Typed(expr, tpt) => - recur(expr) - case _ => None - recur(cl) match - case Some(reduced) => - seq(bindingsBuf.result(), reduced).withSpan(tree.span) - case None => - tree - case _ => - tree - } - /** The result type of reducing a match. It consists optionally of a list of bindings * for the pattern-bound variables and the RHS of the selected case. * Returns `None` if no case was selected. diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index c71648984664..40b0b383a4e3 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -21,6 +21,7 @@ import collection.mutable import reporting.trace import util.Spans.Span import dotty.tools.dotc.transform.Splicer +import dotty.tools.dotc.transform.BetaReduce import quoted.QuoteUtils import scala.annotation.constructorOnly @@ -811,7 +812,7 @@ class Inliner(val call: tpd.Tree)(using Context): case Quoted(Spliced(inner)) => inner case _ => tree val locked = ctx.typerState.ownedVars - val res = cancelQuotes(constToLiteral(betaReduce(super.typedApply(tree, pt)))) match { + val res = cancelQuotes(constToLiteral(BetaReduce(super.typedApply(tree, pt)))) match { case res: Apply if res.symbol == defn.QuotedRuntime_exprSplice && StagingContext.level == 0 && !hasInliningErrors => @@ -824,7 +825,7 @@ class Inliner(val call: tpd.Tree)(using Context): override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = val locked = ctx.typerState.ownedVars - val tree1 = inlineIfNeeded(constToLiteral(betaReduce(super.typedTypeApply(tree, pt))), pt, locked) + val tree1 = inlineIfNeeded(constToLiteral(BetaReduce(super.typedTypeApply(tree, pt))), pt, locked) if tree1.symbol.isQuote then ctx.compilationUnit.needsStaging = true tree1 @@ -1005,7 +1006,7 @@ class Inliner(val call: tpd.Tree)(using Context): super.transform(t1) case t: Apply => val t1 = super.transform(t) - if (t1 `eq` t) t else reducer.betaReduce(t1) + if (t1 `eq` t) t else BetaReduce(t1) case Block(Nil, expr) => super.transform(expr) case _ => diff --git a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala index 00bb83175627..32e26bcbc883 100644 --- a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala +++ b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala @@ -56,6 +56,68 @@ object BetaReduce: val name: String = "betaReduce" val description: String = "reduce closure applications" + /** Rewrite an application + * + * ((x1, ..., xn) => b)(e1, ..., en) + * + * to + * + * val/def x1 = e1; ...; val/def xn = en; b + * + * where `def` is used for call-by-name parameters. However, we shortcut any NoPrefix + * refs among the ei's directly without creating an intermediate binding. + * + * Similarly, rewrites type applications + * + * ([X1, ..., Xm] => (x1, ..., xn) => b).apply[T1, .., Tm](e1, ..., en) + * + * to + * + * type X1 = T1; ...; type Xm = Tm;val/def x1 = e1; ...; val/def xn = en; b + * + * This beta-reduction preserves the integrity of `Inlined` tree nodes. + */ + def apply(tree: Tree)(using Context): Tree = + val bindingsBuf = new ListBuffer[DefTree] + def recur(fn: Tree, argss: List[List[Tree]]): Option[Tree] = fn match + case Block((ddef : DefDef) :: Nil, closure: Closure) if ddef.symbol == closure.meth.symbol => + ddef.tpe.widen match // TODO can these guards be removed? + case mt: MethodType if ddef.paramss.head.length == argss.head.length => + Some(reduceApplication(ddef, argss, bindingsBuf)) + case _ => None + case Block((TypeDef(_, template: Template)) :: Nil, Typed(Apply(Select(New(_), _), _), _)) if template.constr.rhs.isEmpty => + template.body match + case (ddef: DefDef) :: Nil => + ddef.tpe.widen match // TODO can these guards be removed? + case mt: MethodType if ddef.paramss.head.length == argss.head.length => + Some(reduceApplication(ddef, argss, bindingsBuf)) + case mt: PolyType if ddef.paramss.head.length == argss.head.length && ddef.paramss.last.length == argss.last.length => + Some(reduceApplication(ddef, argss, bindingsBuf)) + case _ => None + case _ => None + case Block(stats, expr) if stats.forall(isPureBinding) => + recur(expr, argss).map(cpy.Block(fn)(stats, _)) + case Inlined(call, bindings, expr) if bindings.forall(isPureBinding) => + recur(expr, argss).map(cpy.Inlined(fn)(call, bindings, _)) + case Typed(expr, tpt) => + recur(expr, argss) + case _ => None + tree match + case Apply(Select(fn, nme.apply), args) if defn.isFunctionType(fn.tpe) => + recur(fn, List(args)) match + case Some(reduced) => + seq(bindingsBuf.result(), reduced).withSpan(tree.span) + case None => + tree + case Apply(TypeApply(Select(fn, nme.apply), targs), args) if fn.tpe.typeSymbol eq dotc.core.Symbols.defn.PolyFunctionClass => + recur(fn, List(targs, args)) match + case Some(reduced) => + seq(bindingsBuf.result(), reduced).withSpan(tree.span) + case None => + tree + case _ => + tree + /** Beta-reduces a call to `fn` with arguments `argSyms` or returns `tree` */ def apply(original: Tree, fn: Tree, argss: List[List[Tree]])(using Context): Tree = fn match diff --git a/tests/run-macros/inline-beta-reduce-polyfunction.check b/tests/run-macros/inline-beta-reduce-polyfunction.check new file mode 100644 index 000000000000..7793e273864f --- /dev/null +++ b/tests/run-macros/inline-beta-reduce-polyfunction.check @@ -0,0 +1,7 @@ +{ + type X = Int + { + println(1) + 1 + } +} diff --git a/tests/run-macros/inline-beta-reduce-polyfunction.scala b/tests/run-macros/inline-beta-reduce-polyfunction.scala new file mode 100644 index 000000000000..60ef889e7260 --- /dev/null +++ b/tests/run-macros/inline-beta-reduce-polyfunction.scala @@ -0,0 +1,5 @@ +transparent inline def foo(inline f: [X] => X => X): Int = f[Int](1) + +@main def Test: Unit = + val code = compiletime.codeOf(foo([X] => (x: X) => { println(x); x })) + println(code) \ No newline at end of file From a6de5e4e7eae9d550dd07962b9efddb62075e6a0 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 9 Jan 2023 10:36:58 +0100 Subject: [PATCH 4/8] Reuse beta-reduction logic --- .../tools/dotc/transform/BetaReduce.scala | 40 ++----------------- .../quoted/runtime/impl/QuotesImpl.scala | 13 ++---- 2 files changed, 8 insertions(+), 45 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala index 32e26bcbc883..565d04599ca5 100644 --- a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala +++ b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala @@ -38,17 +38,10 @@ class BetaReduce extends MiniPhase: override def description: String = BetaReduce.description - override def transformApply(app: Apply)(using Context): Tree = app.fun match - case Select(fn, nme.apply) if defn.isFunctionType(fn.tpe) => - val app1 = BetaReduce(app, fn, List(app.args)) - if app1 ne app then report.log(i"beta reduce $app -> $app1") - app1 - case TypeApply(Select(fn, nme.apply), targs) if fn.tpe.typeSymbol eq defn.PolyFunctionClass => - val app1 = BetaReduce(app, fn, List(targs, app.args)) - if app1 ne app then report.log(i"beta reduce $app -> $app1") - app1 - case _ => - app + override def transformApply(app: Apply)(using Context): Tree = + val app1 = BetaReduce(app) + if app1 ne app then report.log(i"beta reduce $app -> $app1") + app1 object BetaReduce: import ast.tpd._ @@ -118,31 +111,6 @@ object BetaReduce: case _ => tree - /** Beta-reduces a call to `fn` with arguments `argSyms` or returns `tree` */ - def apply(original: Tree, fn: Tree, argss: List[List[Tree]])(using Context): Tree = - fn match - case Typed(expr, _) => - BetaReduce(original, expr, argss) - case Block((anonFun: DefDef) :: Nil, closure: Closure) => - BetaReduce(anonFun, argss) - case Block((TypeDef(_, template: Template)) :: Nil, Typed(Apply(Select(New(_), _), _), _)) if template.constr.rhs.isEmpty => - template.body match - case (anonFun: DefDef) :: Nil => - BetaReduce(anonFun, argss) - case _ => - original - case Block(stats, expr) => - val tree = BetaReduce(original, expr, argss) - if tree eq original then original - else cpy.Block(fn)(stats, tree) - case Inlined(call, bindings, expr) => - val tree = BetaReduce(original, expr, argss) - if tree eq original then original - else cpy.Inlined(fn)(call, bindings, tree) - case _ => - original - end apply - /** Beta-reduces a call to `ddef` with arguments `args` */ def apply(ddef: DefDef, argss: List[List[Tree]])(using Context) = val bindings = new ListBuffer[DefTree]() diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 277cfd9f8cfb..15ed447fd680 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -362,20 +362,15 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object Term extends TermModule: def betaReduce(tree: Term): Option[Term] = tree match - case app @ tpd.Apply(tpd.Select(fn, nme.apply), args) if dotc.core.Symbols.defn.isFunctionType(fn.tpe) => - val app1 = dotc.transform.BetaReduce(app, fn, List(args)) - if app1 eq app then None - else Some(app1.withSpan(tree.span)) - case app @ tpd.Apply(tpd.TypeApply(tpd.Select(fn, nme.apply), targs), args) if fn.tpe.typeSymbol eq dotc.core.Symbols.defn.PolyFunctionClass => - val app1 = dotc.transform.BetaReduce(app, fn, List(targs, app.args)) - if app1 eq app then None - else Some(app1.withSpan(tree.span)) case tpd.Block(Nil, expr) => for e <- betaReduce(expr) yield tpd.cpy.Block(tree)(Nil, e) case tpd.Inlined(_, Nil, expr) => betaReduce(expr) case _ => - None + val tree1 = dotc.transform.BetaReduce(tree) + if tree1 eq tree then None + else Some(tree1.withSpan(tree.span)) + end Term given TermMethods: TermMethods with From 43c6ba7d05d3f16e16c650ec54b531402aa641bd Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 9 Jan 2023 11:04:56 +0100 Subject: [PATCH 5/8] Refactor BetaReduce logic --- .../src/dotty/tools/dotc/transform/BetaReduce.scala | 13 +------------ .../dotty/tools/dotc/transform/InlinePatterns.scala | 12 +++++++++--- .../dotty/tools/dotc/transform/PickleQuotes.scala | 5 ++++- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala index 565d04599ca5..c33a1d6d8b10 100644 --- a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala +++ b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala @@ -111,22 +111,11 @@ object BetaReduce: case _ => tree - /** Beta-reduces a call to `ddef` with arguments `args` */ - def apply(ddef: DefDef, argss: List[List[Tree]])(using Context) = - val bindings = new ListBuffer[DefTree]() - val expansion1 = reduceApplication(ddef, argss, bindings) - val bindings1 = bindings.result() - seq(bindings1, expansion1) - /** Beta-reduces a call to `ddef` with arguments `args` and registers new bindings */ def reduceApplication(ddef: DefDef, argss: List[List[Tree]], bindings: ListBuffer[DefTree])(using Context): Tree = - assert(argss.size == 1 || argss.size == 2) - val targs = if argss.size == 2 then argss.head else Nil - val args = argss.last + val (targs, args) = argss.flatten.partition(_.isType) val tparams = ddef.leadingTypeParams val vparams = ddef.termParamss.flatten - assert(targs.hasSameLengthAs(tparams)) - assert(args.hasSameLengthAs(vparams)) val targSyms = for (targ, tparam) <- targs.zip(tparams) yield diff --git a/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala b/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala index b1d3bb68cf76..798f34757b35 100644 --- a/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala +++ b/compiler/src/dotty/tools/dotc/transform/InlinePatterns.scala @@ -8,6 +8,8 @@ import Symbols._, Contexts._, Types._, Decorators._ import NameOps._ import Names._ +import scala.collection.mutable.ListBuffer + /** Rewrite an application * * {new { def unapply(x0: X0)(x1: X1,..., xn: Xn) = b }}.unapply(y0)(y1, ..., yn) @@ -38,7 +40,7 @@ class InlinePatterns extends MiniPhase: if app.symbol.name.isUnapplyName && !app.tpe.isInstanceOf[MethodicType] then app match case App(Select(fn, name), argss) => - val app1 = betaReduce(app, fn, name, argss.flatten) + val app1 = betaReduce(app, fn, name, argss) if app1 ne app then report.log(i"beta reduce $app -> $app1") app1 case _ => @@ -52,11 +54,15 @@ class InlinePatterns extends MiniPhase: case _ => (app, Nil) // TODO merge with BetaReduce.scala - private def betaReduce(tree: Apply, fn: Tree, name: Name, args: List[Tree])(using Context): Tree = + private def betaReduce(tree: Apply, fn: Tree, name: Name, argss: List[List[Tree]])(using Context): Tree = fn match case Block(TypeDef(_, template: Template) :: Nil, Apply(Select(New(_),_), Nil)) if template.constr.rhs.isEmpty => template.body match - case List(ddef @ DefDef(`name`, _, _, _)) => BetaReduce(ddef, List(args)) + case List(ddef @ DefDef(`name`, _, _, _)) => + val bindings = new ListBuffer[DefTree]() + val expansion1 = BetaReduce.reduceApplication(ddef, argss, bindings) + val bindings1 = bindings.result() + seq(bindings1, expansion1) case _ => tree case _ => tree diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 732d8d41b8d6..c56bac4d66af 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -322,7 +322,10 @@ object PickleQuotes { } val Block(List(ddef: DefDef), _) = splice: @unchecked // TODO: beta reduce inner closure? Or wait until BetaReduce phase? - BetaReduce(ddef, List(spliceArgs)).select(nme.apply).appliedTo(args(2).asInstance(quotesType)) + BetaReduce( + splice + .select(nme.apply).appliedToArgs(spliceArgs)) + .select(nme.apply).appliedTo(args(2).asInstance(quotesType)) } CaseDef(Literal(Constant(idx)), EmptyTree, rhs) } From f7f61c088d41297277993fc15586a81b3dfb531a Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 9 Jan 2023 11:29:03 +0100 Subject: [PATCH 6/8] Update documentation --- compiler/src/dotty/tools/dotc/transform/BetaReduce.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala index c33a1d6d8b10..2a9cb7c8e9a7 100644 --- a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala +++ b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala @@ -13,13 +13,14 @@ import scala.collection.mutable.ListBuffer /** Rewrite an application * - * (((x1, ..., xn) => b): T)(y1, ..., yn) + * (([X1, ..., Xm] => (x1, ..., xn) => b): T)[T1, ..., Tm](y1, ..., yn) * * where * * - all yi are pure references without a prefix * - the closure can also be contextual or erased, but cannot be a SAM type - * _ the type ascription ...: T is optional + * - the type parameters Xi and type arguments Ti are optional + * - the type ascription ...: T is optional * * to * From 60c2d70aadcb620e495832a630dda0670fa93f0f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 9 Jan 2023 11:39:35 +0100 Subject: [PATCH 7/8] Remove unnecessary guards Number of arguments should be correct or it would not typecheck. --- .../src/dotty/tools/dotc/transform/BetaReduce.scala | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala index 2a9cb7c8e9a7..bdfd4ec76515 100644 --- a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala +++ b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala @@ -75,19 +75,10 @@ object BetaReduce: val bindingsBuf = new ListBuffer[DefTree] def recur(fn: Tree, argss: List[List[Tree]]): Option[Tree] = fn match case Block((ddef : DefDef) :: Nil, closure: Closure) if ddef.symbol == closure.meth.symbol => - ddef.tpe.widen match // TODO can these guards be removed? - case mt: MethodType if ddef.paramss.head.length == argss.head.length => - Some(reduceApplication(ddef, argss, bindingsBuf)) - case _ => None + Some(reduceApplication(ddef, argss, bindingsBuf)) case Block((TypeDef(_, template: Template)) :: Nil, Typed(Apply(Select(New(_), _), _), _)) if template.constr.rhs.isEmpty => template.body match - case (ddef: DefDef) :: Nil => - ddef.tpe.widen match // TODO can these guards be removed? - case mt: MethodType if ddef.paramss.head.length == argss.head.length => - Some(reduceApplication(ddef, argss, bindingsBuf)) - case mt: PolyType if ddef.paramss.head.length == argss.head.length && ddef.paramss.last.length == argss.last.length => - Some(reduceApplication(ddef, argss, bindingsBuf)) - case _ => None + case (ddef: DefDef) :: Nil => Some(reduceApplication(ddef, argss, bindingsBuf)) case _ => None case Block(stats, expr) if stats.forall(isPureBinding) => recur(expr, argss).map(cpy.Block(fn)(stats, _)) From db2d3eb9d04dc10f8db8649541d73059b2d91f3b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 17 Jan 2023 14:04:17 +0100 Subject: [PATCH 8/8] Support beta reduction of functions with opaque types --- .../tools/dotc/transform/BetaReduce.scala | 2 ++ .../backend/jvm/InlineBytecodeTests.scala | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala index bdfd4ec76515..97dc4697db6d 100644 --- a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala +++ b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala @@ -86,6 +86,8 @@ object BetaReduce: recur(expr, argss).map(cpy.Inlined(fn)(call, bindings, _)) case Typed(expr, tpt) => recur(expr, argss) + case TypeApply(Select(expr, nme.asInstanceOfPM), List(tpt)) => + recur(expr, argss) case _ => None tree match case Apply(Select(fn, nme.apply), args) if defn.isFunctionType(fn.tpe) => diff --git a/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala index a9cec06880a2..5f9318c0c3f0 100644 --- a/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala @@ -608,6 +608,37 @@ class InlineBytecodeTests extends DottyBytecodeTest { } } + @Test def beta_reduce_function_of_opaque_types = { + val source = """object foo: + | opaque type T = Int + | inline def apply(inline op: T => T): T = op(2) + | + |class Test: + | def test = foo { n => n } + """.stripMargin + + checkBCode(source) { dir => + val clsIn = dir.lookupName("Test.class", directory = false).input + val clsNode = loadClassNode(clsIn) + + val fun = getMethod(clsNode, "test") + val instructions = instructionsFromMethod(fun) + val expected = + List( + Field(GETSTATIC, "foo$", "MODULE$", "Lfoo$;"), + VarOp(ASTORE, 1), + VarOp(ALOAD, 1), + VarOp(ASTORE, 2), + Op(ICONST_2), + Op(IRETURN), + ) + + assert(instructions == expected, + "`i was not properly beta-reduced in `test`\n" + diffInstructions(instructions, expected)) + + } + } + @Test def i9456 = { val source = """class Foo { | def test: Int = inline2(inline1(2.+))