From 05fede95dbdd5dbdb73a93d0f3d066411404408b Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 2 Dec 2016 17:47:44 +0100 Subject: [PATCH 1/3] Initial implementation of function specialization Add phases and initial replacement for super Replace all existing combinations of Function1 with specialized version Do transformations on symbol level too Refactor transformations to be more idiomatic Add dispatch to specialized applys Add forwarding method for generic case Don't specialize Function1 tree when invalid to Write test to check for specialized apply Remove `DispatchToSpecializedApply` phase SpecializeFunction1: don't roll over parents, use mapConserve Rewrite to handle all specialized functions Don't remove parents not being specialized Add plain function tests to NameOps and Definitions Rewrite `SpecializeFunctions` from `DenotTransformer` to `InfoTransformer` Add `MiniPhaseTransform` to add specialized methods to FunctionN Add synthetic bridge when compiling FunctionN Fix ordering of specialized names and type parameterized apply Add parent types explicitly when specializing When a class directly extends a specialized function class, we need to replace the parent with the specialized interface. In other cases we don't replace it, even if the parent of a parent has a specialized apply - the symbols would propagate anyway. Make `ThisName` recursive on `self.ThisName` Make sure specialized functions get the correct name --- .../src/dotty/tools/dotc/core/NameOps.scala | 21 +- .../src/dotty/tools/dotc/core/Names.scala | 2 +- .../DispatchToSpecializedApply.scala | 29 +++ .../dotc/transform/SpecializeFunctions.scala | 190 ++++++++++++++++++ .../transform/SpecializedApplyMethods.scala | 96 +++++++++ .../tools/backend/jvm/DottyBytecodeTest.scala | 45 +++++ .../transform/SpecializeFunctionsTests.scala | 142 +++++++++++++ tests/run/t2857.scala | 2 - 8 files changed, 520 insertions(+), 7 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/DispatchToSpecializedApply.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala create mode 100644 compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index ed6e54ab9deb..241a1e273f0a 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -276,16 +276,29 @@ object NameOps { case nme.clone_ => nme.clone_ } - def specializedFor(classTargs: List[Type], classTargsNames: List[Name], methodTargs: List[Type], methodTarsNames: List[Name])(using Context): N = { - - val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => defn.typeTag(x._1)) - val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => defn.typeTag(x._1)) + /** This method is to be used on **type parameters** from a class, since + * this method does sorting based on their names + */ + def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): name.ThisName = { + val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => typeToTag(x._1)) + val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => typeToTag(x._1)) likeSpacedN(name ++ nme.specializedTypeNames.prefix ++ methodTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.separator ++ classTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.suffix) } + /** Use for specializing function names ONLY and use it if you are **not** + * creating specialized name from type parameters. The order of names will + * be: + * + * `<...>` + */ + def specializedFunction(ret: Types.Type, args: List[Types.Type])(implicit ctx: Context): name.ThisName = + name ++ nme.specializedTypeNames.prefix ++ + nme.specializedTypeNames.separator ++ typeToTag(ret) ++ + args.map(typeToTag).fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.suffix + /** If name length exceeds allowable limit, replace part of it by hash */ def compactified(using Context): TermName = termName(compactify(name.toString)) diff --git a/compiler/src/dotty/tools/dotc/core/Names.scala b/compiler/src/dotty/tools/dotc/core/Names.scala index acfbd411a0ce..6d019dcc15b7 100644 --- a/compiler/src/dotty/tools/dotc/core/Names.scala +++ b/compiler/src/dotty/tools/dotc/core/Names.scala @@ -35,7 +35,7 @@ object Names { abstract class Name extends Designator, Showable derives CanEqual { /** A type for names of the same kind as this name */ - type ThisName <: Name + type ThisName <: Name { type ThisName = self.ThisName } /** Is this name a type name? */ def isTypeName: Boolean diff --git a/compiler/src/dotty/tools/dotc/transform/DispatchToSpecializedApply.scala b/compiler/src/dotty/tools/dotc/transform/DispatchToSpecializedApply.scala new file mode 100644 index 000000000000..b3b612bd84fa --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/DispatchToSpecializedApply.scala @@ -0,0 +1,29 @@ +package dotty.tools +package dotc +package transform + +import TreeTransforms.{ MiniPhaseTransform, TransformerInfo } +import core._ +import Contexts.Context, Types._, Decorators._, Symbols._, DenotTransformers._ +import Denotations._, SymDenotations._, Scopes._, StdNames._, NameOps._, Names._ + +class DispatchToSpecializedApply extends MiniPhaseTransform { + import ast.Trees._ + import ast.tpd + + val phaseName = "dispatchToSpecializedApply" + + override def transformApply(tree: tpd.Apply)(implicit ctx: Context, info: TransformerInfo) = + tree match { + case Apply(select @ Select(id, nme.apply), arg :: Nil) => + val params = List(arg.tpe, tree.tpe) + val specializedApply = nme.apply.specializedFor(params, params.map(_.typeSymbol.name)) + val hasOverridenSpecializedApply = id.tpe.decls.iterator.exists { sym => + sym.is(Flags.Override) && (sym.name eq specializedApply) + } + + if (hasOverridenSpecializedApply) tpd.Apply(tpd.Select(id, specializedApply), arg :: Nil) + else tree + case _ => tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala new file mode 100644 index 000000000000..d41772067cbd --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala @@ -0,0 +1,190 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms.{ MiniPhaseTransform, TransformerInfo } +import ast.Trees._, ast.tpd, core._ +import Contexts.Context, Types._, Decorators._, Symbols._, DenotTransformers._ +import SymDenotations._, Scopes._, StdNames._, NameOps._, Names._ + +import scala.collection.mutable + +/** Specializes classes that inherit from `FunctionN` where there exists a + * specialized form. + */ +class SpecializeFunctions extends MiniPhaseTransform with InfoTransformer { + import ast.tpd._ + val phaseName = "specializeFunctions" + + private[this] var _blacklistedSymbols: List[Symbol] = _ + + private def blacklistedSymbols(implicit ctx: Context): List[Symbol] = { + if (_blacklistedSymbols eq null) _blacklistedSymbols = List( + ctx.getClassIfDefined("scala.math.Ordering").asClass.membersNamed("Ops".toTypeName).first.symbol + ) + + _blacklistedSymbols + } + + /** Transforms the type to include decls for specialized applys and replace + * the class parents with specialized versions. + */ + def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { + case tp: ClassInfo if !sym.is(Flags.Package) && (tp.decls ne EmptyScope) => { + var newApplys = Map.empty[Name, Symbol] + + val newParents = tp.parents.mapConserve { parent => + List(0, 1, 2, 3).flatMap { arity => + val func = defn.FunctionClass(arity) + if (!parent.derivesFrom(func)) Nil + else { + val typeParams = tp.typeRef.baseArgInfos(func) + val interface = specInterface(typeParams) + + if (interface.exists) { + if (tp.decls.lookup(nme.apply).exists) { + val specializedMethodName = nme.apply.specializedFunction(typeParams.last, typeParams.init) + newApplys = newApplys + (specializedMethodName -> interface) + } + + if (parent.isRef(func)) List(interface.typeRef) + else Nil + } + else Nil + } + } + .headOption + .getOrElse(parent) + } + + def newDecls = + if (newApplys.isEmpty) tp.decls + else + newApplys.toList.map { case (name, interface) => + ctx.newSymbol( + sym, + name, + Flags.Override | Flags.Method, + interface.info.decls.lookup(name).info + ) + } + .foldLeft(tp.decls.cloneScope) { + (scope, sym) => scope.enter(sym); scope + } + + tp.derivedClassInfo( + classParents = newParents, + decls = newDecls + ) + } + + case _ => tp + } + + /** Transforms the `Template` of the classes to contain forwarders from the + * generic applys to the specialized ones. Also replaces parents of the + * class on the tree level and inserts the specialized applys in the + * template body. + */ + override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo) = { + val applyBuf = new mutable.ListBuffer[Tree] + val newBody = tree.body.mapConserve { + case dt: DefDef if dt.name == nme.apply && dt.vparamss.length == 1 => { + val specName = nme.apply.specializedFunction( + dt.tpe.widen.finalResultType, + dt.vparamss.head.map(_.symbol.info) + ) + + val specializedApply = tree.symbol.enclosingClass.info.decls.lookup(specName)//member(specName).symbol + //val specializedApply = tree.symbol.enclosingClass.info.member(specName).symbol + + if (false) { + println(tree.symbol.enclosingClass.show) + println("'" + specName.show + "'") + println(specializedApply) + println(specializedApply.exists) + } + + + if (specializedApply.exists) { + val apply = specializedApply.asTerm + val specializedDecl = + polyDefDef(apply, trefs => vrefss => { + dt.rhs + .changeOwner(dt.symbol, apply) + .subst(dt.vparamss.flatten.map(_.symbol), vrefss.flatten.map(_.symbol)) + }) + applyBuf += specializedDecl + + // create a forwarding to the specialized apply + cpy.DefDef(dt)(rhs = { + tpd + .ref(apply) + .appliedToArgs(dt.vparamss.head.map(vparam => ref(vparam.symbol))) + }) + } else dt + } + case x => x + } + + val missing: List[TypeTree] = List(0, 1, 2, 3).flatMap { arity => + val func = defn.FunctionClass(arity) + val tr = tree.symbol.enclosingClass.typeRef + + if (!tr.parents.exists(_.isRef(func))) Nil + else { + val typeParams = tr.baseArgInfos(func) + val interface = specInterface(typeParams) + + if (interface.exists) List(interface.info) + else Nil + } + }.map(TypeTree) + + cpy.Template(tree)( + parents = tree.parents ++ missing, + body = applyBuf.toList ++ newBody + ) + } + + /** Dispatch to specialized `apply`s in user code when available */ + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = + tree match { + case app @ Apply(fun, args) + if fun.symbol.name == nme.apply && + fun.symbol.owner.derivesFrom(defn.FunctionClass(args.length)) + => { + val params = (fun.tpe.widen.firstParamTypes :+ tree.tpe).map(_.widenSingleton.dealias) + val specializedApply = specializedName(nme.apply, params) + + if (!params.exists(_.isInstanceOf[ExprType]) && fun.symbol.owner.info.decls.lookup(specializedApply).exists) { + val newSel = fun match { + case Select(qual, _) => + qual.select(specializedApply) + case _ => { + (fun.tpe: @unchecked) match { + case TermRef(prefix: ThisType, name) => + tpd.This(prefix.cls).select(specializedApply) + case TermRef(prefix: NamedType, name) => + tpd.ref(prefix).select(specializedApply) + } + } + } + + newSel.appliedToArgs(args) + } + else tree + } + case _ => tree + } + + @inline private def specializedName(name: Name, args: List[Type])(implicit ctx: Context) = + name.specializedFor(args, args.map(_.typeSymbol.name), Nil, Nil) + + @inline private def specInterface(typeParams: List[Type])(implicit ctx: Context) = { + val specName = + ("JFunction" + (typeParams.length - 1)).toTermName + .specializedFunction(typeParams.last, typeParams.init) + + ctx.getClassIfDefined("scala.compat.java8.".toTermName ++ specName) + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala b/compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala new file mode 100644 index 000000000000..2afee5a397ec --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala @@ -0,0 +1,96 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms.{ MiniPhaseTransform, TransformerInfo } +import ast.Trees._, ast.tpd, core._ +import Contexts.Context, Types._, Decorators._, Symbols._, DenotTransformers._ +import SymDenotations._, Scopes._, StdNames._, NameOps._, Names._ + +/** This phase synthesizes specialized methods for FunctionN, this is done + * since there are no scala signatures in the bytecode for the specialized + * methods. + * + * We know which specializations exist for the different arities, therefore we + * can hardcode them. This should, however be removed once we're using a + * different standard library. + */ +class SpecializedApplyMethods extends MiniPhaseTransform with InfoTransformer { + import ast.tpd._ + + val phaseName = "specializedApplyMethods" + + private[this] var func0Applys: List[Symbol] = _ + private[this] var func1Applys: List[Symbol] = _ + private[this] var func2Applys: List[Symbol] = _ + private[this] var func0: Symbol = _ + private[this] var func1: Symbol = _ + private[this] var func2: Symbol = _ + + private def init()(implicit ctx: Context): Unit = if (func0Applys eq null) { + val definitions = ctx.definitions + import definitions._ + + def specApply(sym: Symbol, args: List[Type], ret: Type)(implicit ctx: Context) = { + val name = nme.apply.specializedFunction(ret, args) + ctx.newSymbol(sym, name, Flags.Method, MethodType(args, ret)) + } + + func0 = FunctionClass(0) + func0Applys = for (r <- ScalaValueTypes.toList) yield specApply(func0, Nil, r) + + func1 = FunctionClass(1) + func1Applys = for { + r <- List(UnitType, BooleanType, IntType, FloatType, LongType, DoubleType) + t1 <- List(IntType, LongType, FloatType, DoubleType) + } yield specApply(func1, List(t1), r) + + func2 = FunctionClass(2) + func2Applys = for { + r <- List(UnitType, BooleanType, IntType, FloatType, LongType, DoubleType) + t1 <- List(IntType, LongType, DoubleType) + t2 <- List(IntType, LongType, DoubleType) + } yield specApply(func2, List(t1, t2), r) + } + + /** Add symbols for specialized methods to FunctionN */ + def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { + case tp: ClassInfo if defn.isPlainFunctionClass(sym) => { + init() + val newDecls = sym.name.functionArity match { + case 0 => func0Applys.foldLeft(tp.decls.cloneScope) { + (decls, sym) => decls.enter(sym); decls + } + case 1 => func1Applys.foldLeft(tp.decls.cloneScope) { + (decls, sym) => decls.enter(sym); decls + } + case 2 => func2Applys.foldLeft(tp.decls.cloneScope) { + (decls, sym) => decls.enter(sym); decls + } + case _ => tp.decls + } + + tp.derivedClassInfo(decls = newDecls) + } + case _ => tp + } + + /** Create bridge methods for FunctionN with specialized applys */ + override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo) = { + val owner = tree.symbol.owner + val additionalSymbols = + if (owner eq func0) func0Applys + else if (owner eq func1) func1Applys + else if (owner eq func2) func2Applys + else Nil + + if (additionalSymbols eq Nil) tree + else cpy.Template(tree)(body = tree.body ++ additionalSymbols.map { apply => + DefDef(apply.asTerm, { vparamss => + This(owner.asClass) + .select(nme.apply) + .appliedToArgss(vparamss) + .ensureConforms(apply.info.finalResultType) + }) + }) + } +} diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala index f6887fc3d459..771dd6adf132 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala @@ -74,6 +74,14 @@ trait DottyBytecodeTest { cn } + /** Finds a class with `cls` as name in `dir`, throws if it can't find it */ + def findClass(cls: String, dir: Directory) = { + val clsIn = dir.lookupName(s"$cls.class", directory = false).input + val clsNode = loadClassNode(clsIn) + assert(clsNode.name == cls, s"inspecting wrong class: ${clsNode.name}") + clsNode + } + protected def getMethod(classNode: ClassNode, name: String): MethodNode = classNode.methods.asScala.find(_.name == name) getOrElse sys.error(s"Didn't find method '$name' in class '${classNode.name}'") @@ -212,6 +220,43 @@ trait DottyBytecodeTest { s"Wrong number of null checks ($actualChecks), expected: $expectedChecks" ) } + + def assertBoxing(nodeName: String, methods: java.lang.Iterable[MethodNode])(implicit source: String): Unit = + methods.asScala.find(_.name == nodeName) + .map { node => + val (ins, boxed) = boxingInstructions(node) + if (!boxed) fail("No boxing in:\n" + boxingError(ins, source)) + } + .getOrElse(fail("Could not find constructor for object `Test`")) + + private def boxingError(ins: List[_], source: String) = + s"""|---------------------------------- + |${ins.mkString("\n")} + |---------------------------------- + |From code: + |$source + |----------------------------------""".stripMargin + + + protected def assertNoBoxing(nodeName: String, methods: java.lang.Iterable[MethodNode])(implicit source: String): Unit = + methods.asScala.find(_.name == nodeName) + .map { node => + val (ins, boxed) = boxingInstructions(node) + if (boxed) fail(boxingError(ins, source)) + } + .getOrElse(fail("Could not find constructor for object `Test`")) + + protected def boxingInstructions(method: MethodNode): (List[_], Boolean) = { + val ins = instructionsFromMethod(method) + val boxed = ins.exists { + case Invoke(op, owner, name, desc, itf) => + owner.toLowerCase.contains("box") || name.toLowerCase.contains("box") + case _ => false + } + + (ins, boxed) + } + } object DottyBytecodeTest { extension [T](l: List[T]) def stringLines = l.mkString("\n") diff --git a/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala b/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala new file mode 100644 index 000000000000..9605c8fbfdd7 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala @@ -0,0 +1,142 @@ +package dotty.tools +package dotc +package transform + +import org.junit.Assert._ +import org.junit.Test + +import dotty.tools.backend.jvm.DottyBytecodeTest + +class SpecializeFunctionsTests extends DottyBytecodeTest { + + import dotty.tools.backend.jvm.ASMConverters._ + import dotty.tools.backend.jvm.AsmNode._ + import scala.collection.JavaConverters._ + import scala.tools.asm.tree.MethodNode + + @Test def specializeParentIntToInt = { + val source = """ + |class Foo extends Function1[Int, Int] { + | def apply(i: Int) = i + |} + """.stripMargin + + checkBCode(source) { dir => + val applys = + findClass("Foo", dir).methods.asScala.collect { + case m if m.name == "apply$mcII$sp" => m + case m if m.name == "apply" => m + } + .map(_.name) + .toList + + assert( + // there should be two "apply", one generic and the one overwritten and + // then the specialized one + applys.length == 3, + s"Wrong number of specialized applys, actual length: ${applys.length} $applys" + ) + assert(applys.contains("apply"), "Foo did not contain `apply` forwarder method") + assert(applys.contains("apply$mcII$sp"), "Foo did not contain specialized apply") + } + } + + @Test def specializeFunction2Applys = { + val source = + """|class Func2 extends Function2[Int, Int, Int] { + | def apply(i: Int, j: Int): Int = i + j + |}""".stripMargin + + checkBCode(source) { dir => + val apps = + findClass("Func2", dir).methods.asScala.collect { + case m if m.name == "apply$mcIII$sp" => m + case m if m.name == "apply" => m + } + .map(_.name) + .toList + + assert( + apps.length == 3, + s"Wrong number of specialized applys, actual length: ${apps.length} - $apps" + ) + assert(apps.contains("apply"), "Func3 did not contain `apply` forwarder method") + assert(apps.contains("apply$mcIII$sp"), "Func3 did not contain specialized apply") + } + } + + @Test def noBoxingSpecFunction0 = { + implicit val source: String = + """|object Test { + | class Func0 extends Function0[Int] { + | def apply() = 1337 + | } + | + | (new Func0: Function0[Int])() + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def boxingFunction1 = { + implicit val source: String = + """|object Test { + | class Func1 extends Function1[Char, Int] { + | def apply(c: Char) = c.toInt + | } + | + | (new Func1: Function1[Char, Int])('c') + |}""".stripMargin + + checkBCode(source) { dir => + assertBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def noBoxingSpecFunction1 = { + implicit val source: String = + """|object Test { + | class Func1 extends Function1[Int, Int] { + | def apply(i: Int) = i + 1 + | } + | + | (new Func1: Function1[Int, Int])(1) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def noBoxingSpecFunction2 = { + implicit val source: String = + """|object Test { + | class Func2 extends Function2[Int, Int, Int] { + | def apply(i: Int, j: Int) = i + j + | } + | + | (new Func2: Function2[Int, Int, Int])(1300, 37) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def boxingFunction2 = { + implicit val source: String = + """|object Test { + | class Func2 extends Function2[Char, Char, Char] { + | def apply(c1: Char, c2: Char) = c1 + | } + | + | (new Func2: Function2[Char, Char, Char])('c', 'd') + |}""".stripMargin + + checkBCode(source) { dir => + assertBoxing("", findClass("Test$", dir).methods) + } + } +} diff --git a/tests/run/t2857.scala b/tests/run/t2857.scala index c523227c4f6c..37c342048333 100644 --- a/tests/run/t2857.scala +++ b/tests/run/t2857.scala @@ -5,5 +5,3 @@ object Test extends App { m.removeBinding(6, "Foo") println(m.contains(6)) } - - From 9b43b28b7cdc02a7e9b23ad8276399a6541acc95 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Wed, 4 Oct 2017 07:39:03 +0200 Subject: [PATCH 2/3] Second version of function specialization Fix compilation errors Don't change parents in `specializeFunctions` Cleanup Move type to JVM tag conversion to Definitions Add more tests for function specialization Pass by name should not introduce boxing Cleanup No specialization for Function3 Adapt to recent changes in transformers and phases Address review comments Optimize phase `SpecializeFunctions` - Stop immediately if the type doesn't derive from `Function{0,1,2}` - We don't need to check if any of the parents derives from `Function{0,1,2}`, we can just check if the type derives from it. --- .../src/dotty/tools/dotc/core/NameOps.scala | 12 +- .../src/dotty/tools/dotc/core/Names.scala | 2 +- .../dotc/transform/SpecializeFunctions.scala | 222 ++++++++---------- .../transform/SpecializedApplyMethods.scala | 28 +-- .../transform/SpecializeFunctionsTests.scala | 91 ++++++- 5 files changed, 204 insertions(+), 151 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 241a1e273f0a..27e209aba04e 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -279,9 +279,9 @@ object NameOps { /** This method is to be used on **type parameters** from a class, since * this method does sorting based on their names */ - def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): name.ThisName = { - val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => typeToTag(x._1)) - val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => typeToTag(x._1)) + def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): Name = { + val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => defn.typeTag(x._1)) + val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => defn.typeTag(x._1)) likeSpacedN(name ++ nme.specializedTypeNames.prefix ++ methodTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.separator ++ @@ -294,10 +294,10 @@ object NameOps { * * `<...>` */ - def specializedFunction(ret: Types.Type, args: List[Types.Type])(implicit ctx: Context): name.ThisName = + def specializedFunction(ret: Types.Type, args: List[Types.Type])(implicit ctx: Context): Name = name ++ nme.specializedTypeNames.prefix ++ - nme.specializedTypeNames.separator ++ typeToTag(ret) ++ - args.map(typeToTag).fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.suffix + nme.specializedTypeNames.separator ++ defn.typeTag(ret) ++ + args.map(defn.typeTag).fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.suffix /** If name length exceeds allowable limit, replace part of it by hash */ def compactified(using Context): TermName = termName(compactify(name.toString)) diff --git a/compiler/src/dotty/tools/dotc/core/Names.scala b/compiler/src/dotty/tools/dotc/core/Names.scala index 6d019dcc15b7..acfbd411a0ce 100644 --- a/compiler/src/dotty/tools/dotc/core/Names.scala +++ b/compiler/src/dotty/tools/dotc/core/Names.scala @@ -35,7 +35,7 @@ object Names { abstract class Name extends Designator, Showable derives CanEqual { /** A type for names of the same kind as this name */ - type ThisName <: Name { type ThisName = self.ThisName } + type ThisName <: Name /** Is this name a type name? */ def isTypeName: Boolean diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala index d41772067cbd..f931339066b5 100644 --- a/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala @@ -1,190 +1,156 @@ package dotty.tools.dotc package transform -import TreeTransforms.{ MiniPhaseTransform, TransformerInfo } import ast.Trees._, ast.tpd, core._ import Contexts.Context, Types._, Decorators._, Symbols._, DenotTransformers._ import SymDenotations._, Scopes._, StdNames._, NameOps._, Names._ +import MegaPhase.MiniPhase import scala.collection.mutable /** Specializes classes that inherit from `FunctionN` where there exists a * specialized form. */ -class SpecializeFunctions extends MiniPhaseTransform with InfoTransformer { +class SpecializeFunctions extends MiniPhase with InfoTransformer { import ast.tpd._ val phaseName = "specializeFunctions" + override def runsAfter = Set(classOf[ElimByName]) - private[this] var _blacklistedSymbols: List[Symbol] = _ + private val jFunction = "scala.compat.java8.JFunction".toTermName - private def blacklistedSymbols(implicit ctx: Context): List[Symbol] = { - if (_blacklistedSymbols eq null) _blacklistedSymbols = List( - ctx.getClassIfDefined("scala.math.Ordering").asClass.membersNamed("Ops".toTypeName).first.symbol - ) - - _blacklistedSymbols - } - - /** Transforms the type to include decls for specialized applys and replace - * the class parents with specialized versions. - */ - def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { - case tp: ClassInfo if !sym.is(Flags.Package) && (tp.decls ne EmptyScope) => { + /** Transforms the type to include decls for specialized applys */ + override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { + case tp: ClassInfo if !sym.is(Flags.Package) && (tp.decls ne EmptyScope) && derivesFromFn012(sym) => var newApplys = Map.empty[Name, Symbol] - val newParents = tp.parents.mapConserve { parent => - List(0, 1, 2, 3).flatMap { arity => - val func = defn.FunctionClass(arity) - if (!parent.derivesFrom(func)) Nil - else { - val typeParams = tp.typeRef.baseArgInfos(func) - val interface = specInterface(typeParams) - - if (interface.exists) { - if (tp.decls.lookup(nme.apply).exists) { - val specializedMethodName = nme.apply.specializedFunction(typeParams.last, typeParams.init) - newApplys = newApplys + (specializedMethodName -> interface) - } + var arity = 0 + while (arity < 3) { + val func = defn.FunctionClass(arity) + if (tp.derivesFrom(func)) { + val typeParams = tp.cls.typeRef.baseType(func).argInfos + val isSpecializable = + defn.isSpecializableFunction( + sym.asClass, + typeParams.init, + typeParams.last + ) - if (parent.isRef(func)) List(interface.typeRef) - else Nil - } - else Nil + if (isSpecializable && tp.decls.lookup(nme.apply).exists) { + val interface = specInterface(typeParams) + val specializedMethodName = nme.apply.specializedFunction(typeParams.last, typeParams.init) + newApplys += (specializedMethodName -> interface) } } - .headOption - .getOrElse(parent) + arity += 1 } def newDecls = - if (newApplys.isEmpty) tp.decls - else - newApplys.toList.map { case (name, interface) => - ctx.newSymbol( - sym, - name, - Flags.Override | Flags.Method, - interface.info.decls.lookup(name).info - ) - } - .foldLeft(tp.decls.cloneScope) { - (scope, sym) => scope.enter(sym); scope - } + newApplys.toList.map { case (name, interface) => + ctx.newSymbol( + sym, + name, + Flags.Override | Flags.Method | Flags.Synthetic, + interface.info.decls.lookup(name).info + ) + } + .foldLeft(tp.decls.cloneScope) { + (scope, sym) => scope.enter(sym); scope + } - tp.derivedClassInfo( - classParents = newParents, - decls = newDecls - ) - } + if (newApplys.isEmpty) tp + else tp.derivedClassInfo(decls = newDecls) case _ => tp } /** Transforms the `Template` of the classes to contain forwarders from the - * generic applys to the specialized ones. Also replaces parents of the - * class on the tree level and inserts the specialized applys in the - * template body. + * generic applys to the specialized ones. Also inserts the specialized applys + * in the template body. */ - override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo) = { - val applyBuf = new mutable.ListBuffer[Tree] - val newBody = tree.body.mapConserve { - case dt: DefDef if dt.name == nme.apply && dt.vparamss.length == 1 => { - val specName = nme.apply.specializedFunction( - dt.tpe.widen.finalResultType, - dt.vparamss.head.map(_.symbol.info) - ) - - val specializedApply = tree.symbol.enclosingClass.info.decls.lookup(specName)//member(specName).symbol - //val specializedApply = tree.symbol.enclosingClass.info.member(specName).symbol - - if (false) { - println(tree.symbol.enclosingClass.show) - println("'" + specName.show + "'") - println(specializedApply) - println(specializedApply.exists) - } - - - if (specializedApply.exists) { - val apply = specializedApply.asTerm - val specializedDecl = - polyDefDef(apply, trefs => vrefss => { - dt.rhs - .changeOwner(dt.symbol, apply) - .subst(dt.vparamss.flatten.map(_.symbol), vrefss.flatten.map(_.symbol)) + override def transformTemplate(tree: Template)(implicit ctx: Context) = { + val cls = tree.symbol.enclosingClass.asClass + if (derivesFromFn012(cls)) { + val applyBuf = new mutable.ListBuffer[Tree] + val newBody = tree.body.mapConserve { + case dt: DefDef if dt.name == nme.apply && dt.vparamss.length == 1 => + val typeParams = dt.vparamss.head.map(_.symbol.info) + val retType = dt.tpe.widen.finalResultType + + val specName = specializedName(nme.apply, typeParams :+ retType) + val specializedApply = cls.info.decls.lookup(specName) + if (specializedApply.exists) { + val apply = specializedApply.asTerm + val specializedDecl = + polyDefDef(apply, trefs => vrefss => { + dt.rhs + .changeOwner(dt.symbol, apply) + .subst(dt.vparamss.flatten.map(_.symbol), vrefss.flatten.map(_.symbol)) + }) + applyBuf += specializedDecl + + // create a forwarding to the specialized apply + cpy.DefDef(dt)(rhs = { + tpd + .ref(apply) + .appliedToArgs(dt.vparamss.head.map(vparam => ref(vparam.symbol))) }) - applyBuf += specializedDecl - - // create a forwarding to the specialized apply - cpy.DefDef(dt)(rhs = { - tpd - .ref(apply) - .appliedToArgs(dt.vparamss.head.map(vparam => ref(vparam.symbol))) - }) - } else dt - } - case x => x - } - - val missing: List[TypeTree] = List(0, 1, 2, 3).flatMap { arity => - val func = defn.FunctionClass(arity) - val tr = tree.symbol.enclosingClass.typeRef + } else dt - if (!tr.parents.exists(_.isRef(func))) Nil - else { - val typeParams = tr.baseArgInfos(func) - val interface = specInterface(typeParams) - - if (interface.exists) List(interface.info) - else Nil + case x => x } - }.map(TypeTree) - cpy.Template(tree)( - parents = tree.parents ++ missing, - body = applyBuf.toList ++ newBody - ) + cpy.Template(tree)( + body = applyBuf.toList ::: newBody + ) + } else tree } /** Dispatch to specialized `apply`s in user code when available */ - override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = + override def transformApply(tree: Apply)(implicit ctx: Context) = tree match { - case app @ Apply(fun, args) + case Apply(fun, args) if fun.symbol.name == nme.apply && fun.symbol.owner.derivesFrom(defn.FunctionClass(args.length)) - => { + => val params = (fun.tpe.widen.firstParamTypes :+ tree.tpe).map(_.widenSingleton.dealias) - val specializedApply = specializedName(nme.apply, params) - - if (!params.exists(_.isInstanceOf[ExprType]) && fun.symbol.owner.info.decls.lookup(specializedApply).exists) { + val isSpecializable = + defn.isSpecializableFunction( + fun.symbol.owner.asClass, + params.init, + params.last) + + if (isSpecializable && !params.exists(_.isInstanceOf[ExprType])) { + val specializedApply = specializedName(nme.apply, params) val newSel = fun match { case Select(qual, _) => qual.select(specializedApply) - case _ => { + case _ => (fun.tpe: @unchecked) match { case TermRef(prefix: ThisType, name) => tpd.This(prefix.cls).select(specializedApply) case TermRef(prefix: NamedType, name) => tpd.ref(prefix).select(specializedApply) } - } } newSel.appliedToArgs(args) } else tree - } + case _ => tree } - @inline private def specializedName(name: Name, args: List[Type])(implicit ctx: Context) = - name.specializedFor(args, args.map(_.typeSymbol.name), Nil, Nil) + private def specializedName(name: Name, args: List[Type])(implicit ctx: Context) = + name.specializedFunction(args.last, args.init) - @inline private def specInterface(typeParams: List[Type])(implicit ctx: Context) = { - val specName = - ("JFunction" + (typeParams.length - 1)).toTermName - .specializedFunction(typeParams.last, typeParams.init) + private def functionName(typeParams: List[Type])(implicit ctx: Context) = + jFunction ++ (typeParams.length - 1).toString - ctx.getClassIfDefined("scala.compat.java8.".toTermName ++ specName) - } + private def specInterface(typeParams: List[Type])(implicit ctx: Context) = + ctx.getClassIfDefined(functionName(typeParams).specializedFunction(typeParams.last, typeParams.init)) + + private def derivesFromFn012(sym: Symbol)(implicit ctx: Context): Boolean = + sym.derivesFrom(defn.FunctionClass(0)) || + sym.derivesFrom(defn.FunctionClass(1)) || + sym.derivesFrom(defn.FunctionClass(2)) } diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala b/compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala index 2afee5a397ec..19c7abacb5fb 100644 --- a/compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala @@ -1,10 +1,10 @@ package dotty.tools.dotc package transform -import TreeTransforms.{ MiniPhaseTransform, TransformerInfo } import ast.Trees._, ast.tpd, core._ import Contexts.Context, Types._, Decorators._, Symbols._, DenotTransformers._ import SymDenotations._, Scopes._, StdNames._, NameOps._, Names._ +import MegaPhase.MiniPhase /** This phase synthesizes specialized methods for FunctionN, this is done * since there are no scala signatures in the bytecode for the specialized @@ -14,14 +14,14 @@ import SymDenotations._, Scopes._, StdNames._, NameOps._, Names._ * can hardcode them. This should, however be removed once we're using a * different standard library. */ -class SpecializedApplyMethods extends MiniPhaseTransform with InfoTransformer { +class SpecializedApplyMethods extends MiniPhase with InfoTransformer { import ast.tpd._ val phaseName = "specializedApplyMethods" - private[this] var func0Applys: List[Symbol] = _ - private[this] var func1Applys: List[Symbol] = _ - private[this] var func2Applys: List[Symbol] = _ + private[this] var func0Applys: collection.Set[Symbol] = _ + private[this] var func1Applys: collection.Set[Symbol] = _ + private[this] var func2Applys: collection.Set[Symbol] = _ private[this] var func0: Symbol = _ private[this] var func1: Symbol = _ private[this] var func2: Symbol = _ @@ -30,30 +30,30 @@ class SpecializedApplyMethods extends MiniPhaseTransform with InfoTransformer { val definitions = ctx.definitions import definitions._ - def specApply(sym: Symbol, args: List[Type], ret: Type)(implicit ctx: Context) = { + def specApply(sym: Symbol, args: List[Type], ret: Type)(implicit ctx: Context): Symbol = { val name = nme.apply.specializedFunction(ret, args) ctx.newSymbol(sym, name, Flags.Method, MethodType(args, ret)) } func0 = FunctionClass(0) - func0Applys = for (r <- ScalaValueTypes.toList) yield specApply(func0, Nil, r) + func0Applys = for (r <- defn.Function0SpecializedReturns) yield specApply(func0, Nil, r) func1 = FunctionClass(1) func1Applys = for { - r <- List(UnitType, BooleanType, IntType, FloatType, LongType, DoubleType) - t1 <- List(IntType, LongType, FloatType, DoubleType) + r <- defn.Function1SpecializedReturns + t1 <- defn.Function1SpecializedParams } yield specApply(func1, List(t1), r) func2 = FunctionClass(2) func2Applys = for { - r <- List(UnitType, BooleanType, IntType, FloatType, LongType, DoubleType) - t1 <- List(IntType, LongType, DoubleType) - t2 <- List(IntType, LongType, DoubleType) + r <- Function2SpecializedReturns + t1 <- Function2SpecializedParams + t2 <- Function2SpecializedReturns } yield specApply(func2, List(t1, t2), r) } /** Add symbols for specialized methods to FunctionN */ - def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { + override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { case tp: ClassInfo if defn.isPlainFunctionClass(sym) => { init() val newDecls = sym.name.functionArity match { @@ -75,7 +75,7 @@ class SpecializedApplyMethods extends MiniPhaseTransform with InfoTransformer { } /** Create bridge methods for FunctionN with specialized applys */ - override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo) = { + override def transformTemplate(tree: Template)(implicit ctx: Context) = { val owner = tree.symbol.owner val additionalSymbols = if (owner eq func0) func0Applys diff --git a/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala b/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala index 9605c8fbfdd7..503d5198aae4 100644 --- a/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala +++ b/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala @@ -60,8 +60,8 @@ class SpecializeFunctionsTests extends DottyBytecodeTest { apps.length == 3, s"Wrong number of specialized applys, actual length: ${apps.length} - $apps" ) - assert(apps.contains("apply"), "Func3 did not contain `apply` forwarder method") - assert(apps.contains("apply$mcIII$sp"), "Func3 did not contain specialized apply") + assert(apps.contains("apply"), "Func2 did not contain `apply` forwarder method") + assert(apps.contains("apply$mcIII$sp"), "Func2 did not contain specialized apply") } } @@ -91,6 +91,7 @@ class SpecializeFunctionsTests extends DottyBytecodeTest { |}""".stripMargin checkBCode(source) { dir => + // No specialization for Function1[Char, Int] assertBoxing("", findClass("Test$", dir).methods) } } @@ -136,7 +137,93 @@ class SpecializeFunctionsTests extends DottyBytecodeTest { |}""".stripMargin checkBCode(source) { dir => + // No specialization for Function2[Char, Char, Char] assertBoxing("", findClass("Test$", dir).methods) } } + + @Test def multipleParentsNoBoxing = { + implicit val source: String = + """|object Test { + | class Func01 extends Function0[Int] with Function1[Int, Int] { + | def apply(): Int = 0 + | def apply(x: Int): Int = x + | } + | (new Func01: Function0[Int])() + | (new Func01: Function1[Int, Int])(1) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def multipleLevelInheritanceNoBoxing = { + implicit val source: String = + """|object Test { + | class Func1[T](fn: T => Int) extends Function1[T, Int] { + | def apply(x: T): Int = fn(x) + | } + | class Fn extends Func1(identity[Int]) + | (new Fn: Function1[Int, Int])(123) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def lambdaNoBoxing1 = { + implicit val source: String = + """|object Test { + | val fn = (x: Int) => x + 1 + | fn(2) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def lambdaNoBoxing2 = { + implicit val source: String = + """|object Test { + | def fn[T, U, V](op0: T => U, op1: U => V): T => V = (x: T) => op1(op0(x)) + | val f0: Int => Double = _.toDouble + | val f1: Double => Int = _.toInt + | val id = fn(f0, f1) + | id(2) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def classWithFieldBoxing = { + implicit val source: String = + """|object Test { + | class Func0[T](x: T) extends Function0[T] { + | def apply(): T = x + | } + | (new Func0(2): Function0[Int])() + |}""".stripMargin + + checkBCode(source) { dir => + // Boxing happens because of the field of `Func0`. + assertBoxing("", findClass("Test$", dir).methods) + } + } + + @Test def passByNameNoBoxing = { + implicit val source: String = + """|object Test { + | def fn(x: => Int): Int = x + | fn(2) + |}""".stripMargin + + checkBCode(source) { dir => + assertNoBoxing("fn", findClass("Test$", dir).methods) + } + } } From 7fbec1caec9a99b42ef9bee0be4ed4f7fc81aa07 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 9 Sep 2020 16:29:51 +0200 Subject: [PATCH 3/3] Third version of function specialization - Fix isPlainFunctionClass Previous implementation is incorrect, as scala.Function1$ would qualify. - Create the symbol at the next phase Create the symbol at the next phase, so that it is a valid member of the corresponding function for all valid periods of its SymDenotations. Otherwise, the valid period will offset by 1, which causes a stale symbol in compiling stdlib. - Handle abstract apply and multiple applys - Don't specialize abstract apply - Fast specialization We avoid going through InfoTransformer, which will cause all symbols to be checked. The reason why it works is that the specialized base classes, i.e. Function0-2 already have all the relevant definitions. - Use StringBuilder instead of StringBuffer (thanks @smarter) StringBuffer is synchronized thus is slower. --- community-build/community-projects/stdLib213 | 2 +- compiler/src/dotty/tools/dotc/Compiler.scala | 4 +- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 4 +- .../dotty/tools/dotc/core/Definitions.scala | 32 ++- .../src/dotty/tools/dotc/core/NameOps.scala | 20 +- .../DispatchToSpecializedApply.scala | 29 --- .../tools/dotc/transform/ExpandSAMs.scala | 2 +- .../transform/FunctionXXLForwarders.scala | 14 +- .../transform/SpecializeApplyMethods.scala | 118 +++++++++++ .../dotc/transform/SpecializeFunctions.scala | 200 +++++++----------- .../transform/SpecializedApplyMethods.scala | 96 --------- .../dotc/transform/TransformByNameApply.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 2 +- .../tools/backend/jvm/DottyBytecodeTest.scala | 15 +- .../transform/SpecializeFunctionsTests.scala | 70 ++++-- 15 files changed, 314 insertions(+), 296 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/transform/DispatchToSpecializedApply.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/SpecializeApplyMethods.scala delete mode 100644 compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala diff --git a/community-build/community-projects/stdLib213 b/community-build/community-projects/stdLib213 index 5b4406c7de9a..09b05334175c 160000 --- a/community-build/community-projects/stdLib213 +++ b/community-build/community-projects/stdLib213 @@ -1 +1 @@ -Subproject commit 5b4406c7de9a7e671d8f533f7d4436a7a0586358 +Subproject commit 09b05334175ce5019f5169f3a3e5d4bbb341ea99 diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 5e0e711febe0..721c91118e4d 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -69,6 +69,7 @@ class Compiler { new CacheAliasImplicits, // Cache RHS of parameterless alias implicits new ByNameClosures, // Expand arguments to by-name parameters to closures new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope + new SpecializeApplyMethods, // Adds specialized methods to FunctionN new RefChecks) :: // Various checks mostly related to abstract members and overriding List(new ElimOpaque, // Turn opaque into normal aliases new TryCatchPatterns, // Compile cases in try/catch @@ -76,6 +77,7 @@ class Compiler { new sjs.ExplicitJSClasses, // Make all JS classes explicit (Scala.js only) new ExplicitOuter, // Add accessors to outer classes from nested ones. new ExplicitSelf, // Make references to non-trivial self types explicit as casts + new ElimByName, // Expand by-name parameter references new StringInterpolatorOpt) :: // Optimizes raw and s string interpolators by rewriting them to string concatentations List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions new InlinePatterns, // Remove placeholders of inlined patterns @@ -83,7 +85,7 @@ class Compiler { new SeqLiterals, // Express vararg arguments as arrays new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods new Getters, // Replace non-private vals and vars with getter defs (fields are added later) - new ElimByName, // Expand by-name parameter references + new SpecializeFunctions, // Specialized Function{0,1,2} by replacing super with specialized super new LiftTry, // Put try expressions that might execute on non-empty stacks into their own methods new CollectNullableFields, // Collect fields that can be nulled out after use in lazy initialization new ElimOuterSelect, // Expand outer selections diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 016fc6b8487d..9ba7030b6292 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -784,8 +784,8 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => def tupleArgs(tree: Tree)(using Context): List[Tree] = tree match { case Block(Nil, expr) => tupleArgs(expr) case Inlined(_, Nil, expr) => tupleArgs(expr) - case Apply(fn, args) - if fn.symbol.name == nme.apply && + case Apply(fn: NameTree, args) + if fn.name == nme.apply && fn.symbol.owner.is(Module) && defn.isTupleClass(fn.symbol.owner.companionClass) => args case _ => Nil diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 637e136c8509..303d3dd32a65 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1209,7 +1209,11 @@ class Definitions { else funType(n) ).symbol.asClass - @tu lazy val Function0_apply: Symbol = FunctionClass(0).requiredMethod(nme.apply) + @tu lazy val Function0_apply: Symbol = Function0.requiredMethod(nme.apply) + + @tu lazy val Function0: Symbol = FunctionClass(0) + @tu lazy val Function1: Symbol = FunctionClass(1) + @tu lazy val Function2: Symbol = FunctionClass(2) def FunctionType(n: Int, isContextual: Boolean = false, isErased: Boolean = false)(using Context): TypeRef = FunctionClass(n, isContextual && !ctx.erasedTypes, isErased).typeRef @@ -1244,7 +1248,7 @@ class Definitions { def isBottomClassAfterErasure(cls: Symbol): Boolean = cls == NothingClass || cls == NullClass - /** Is a function class. + /** Is any function class where * - FunctionXXL * - FunctionN for N >= 0 * - ContextFunctionN for N >= 0 @@ -1253,6 +1257,11 @@ class Definitions { */ def isFunctionClass(cls: Symbol): Boolean = scalaClassName(cls).isFunction + /** Is a function class where + * - FunctionN for N >= 0 and N != XXL + */ + def isPlainFunctionClass(cls: Symbol) = isVarArityClass(cls, str.Function) + /** Is an context function class. * - ContextFunctionN for N >= 0 * - ErasedContextFunctionN for N > 0 @@ -1488,6 +1497,25 @@ class Definitions { false }) + @tu lazy val Function0SpecializedApplyNames: collection.Set[TermName] = + for r <- Function0SpecializedReturnTypes + yield nme.apply.specializedFunction(r, Nil).asTermName + + @tu lazy val Function1SpecializedApplyNames: collection.Set[TermName] = + for + r <- Function1SpecializedReturnTypes + t1 <- Function1SpecializedParamTypes + yield + nme.apply.specializedFunction(r, List(t1)).asTermName + + @tu lazy val Function2SpecializedApplyNames: collection.Set[TermName] = + for + r <- Function2SpecializedReturnTypes + t1 <- Function2SpecializedParamTypes + t2 <- Function2SpecializedParamTypes + yield + nme.apply.specializedFunction(r, List(t1, t2)).asTermName + def functionArity(tp: Type)(using Context): Int = tp.dropDependentRefinement.dealias.argInfos.length - 1 /** Return underlying context function type (i.e. instance of an ContextFunctionN class) diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 27e209aba04e..6cfdedbe9a27 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -231,6 +231,11 @@ object NameOps { def isFunction: Boolean = (name eq tpnme.FunctionXXL) || checkedFunArity(functionSuffixStart) >= 0 + /** Is a function name + * - FunctionN for N >= 0 + */ + def isPlainFunction: Boolean = functionArity >= 0 + /** Is an context function name, i.e one of ContextFunctionN or ErasedContextFunctionN for N >= 0 */ def isContextFunction: Boolean = @@ -279,7 +284,7 @@ object NameOps { /** This method is to be used on **type parameters** from a class, since * this method does sorting based on their names */ - def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): Name = { + def specializedFor(classTargs: List[Type], classTargsNames: List[Name], methodTargs: List[Type], methodTarsNames: List[Name])(using Context): N = { val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => defn.typeTag(x._1)) val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => defn.typeTag(x._1)) @@ -294,10 +299,15 @@ object NameOps { * * `<...>` */ - def specializedFunction(ret: Types.Type, args: List[Types.Type])(implicit ctx: Context): Name = - name ++ nme.specializedTypeNames.prefix ++ - nme.specializedTypeNames.separator ++ defn.typeTag(ret) ++ - args.map(defn.typeTag).fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.suffix + def specializedFunction(ret: Type, args: List[Type])(using Context): Name = + val sb = new StringBuilder + sb.append(name.toString) + sb.append(nme.specializedTypeNames.prefix.toString) + sb.append(nme.specializedTypeNames.separator) + sb.append(defn.typeTag(ret).toString) + args.foreach { arg => sb.append(defn.typeTag(arg)) } + sb.append(nme.specializedTypeNames.suffix) + termName(sb.toString) /** If name length exceeds allowable limit, replace part of it by hash */ def compactified(using Context): TermName = termName(compactify(name.toString)) diff --git a/compiler/src/dotty/tools/dotc/transform/DispatchToSpecializedApply.scala b/compiler/src/dotty/tools/dotc/transform/DispatchToSpecializedApply.scala deleted file mode 100644 index b3b612bd84fa..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/DispatchToSpecializedApply.scala +++ /dev/null @@ -1,29 +0,0 @@ -package dotty.tools -package dotc -package transform - -import TreeTransforms.{ MiniPhaseTransform, TransformerInfo } -import core._ -import Contexts.Context, Types._, Decorators._, Symbols._, DenotTransformers._ -import Denotations._, SymDenotations._, Scopes._, StdNames._, NameOps._, Names._ - -class DispatchToSpecializedApply extends MiniPhaseTransform { - import ast.Trees._ - import ast.tpd - - val phaseName = "dispatchToSpecializedApply" - - override def transformApply(tree: tpd.Apply)(implicit ctx: Context, info: TransformerInfo) = - tree match { - case Apply(select @ Select(id, nme.apply), arg :: Nil) => - val params = List(arg.tpe, tree.tpe) - val specializedApply = nme.apply.specializedFor(params, params.map(_.typeSymbol.name)) - val hasOverridenSpecializedApply = id.tpe.decls.iterator.exists { sym => - sym.is(Flags.Override) && (sym.name eq specializedApply) - } - - if (hasOverridenSpecializedApply) tpd.Apply(tpd.Select(id, specializedApply), arg :: Nil) - else tree - case _ => tree - } -} diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala index 374d938e5523..1b06394b1156 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -162,7 +162,7 @@ class ExpandSAMs extends MiniPhase { cpy.Block(tree)(pfDef :: Nil, New(pfSym.typeRef, Nil)) case _ => - val found = tpe.baseType(defn.FunctionClass(1)) + val found = tpe.baseType(defn.Function1) report.error(TypeMismatch(found, tpe), tree.srcPos) tree } diff --git a/compiler/src/dotty/tools/dotc/transform/FunctionXXLForwarders.scala b/compiler/src/dotty/tools/dotc/transform/FunctionXXLForwarders.scala index 25bb7a5ce28f..3ef0debb93e0 100644 --- a/compiler/src/dotty/tools/dotc/transform/FunctionXXLForwarders.scala +++ b/compiler/src/dotty/tools/dotc/transform/FunctionXXLForwarders.scala @@ -39,18 +39,20 @@ class FunctionXXLForwarders extends MiniPhase with IdentityDenotTransformer { ref(receiver.symbol).appliedToArgss(argss).cast(defn.ObjectType) } + if impl.symbol.owner.is(Trait) then return impl + val forwarders = for { - tree <- if (impl.symbol.owner.is(Trait)) Nil else impl.body - if tree.symbol.is(Method) && tree.symbol.name == nme.apply && - tree.symbol.signature.paramsSig.size > MaxImplementedFunctionArity && - tree.symbol.allOverriddenSymbols.exists(sym => defn.isXXLFunctionClass(sym.owner)) + (ddef: DefDef) <- impl.body + if ddef.name == nme.apply && ddef.symbol.is(Method) && + ddef.symbol.signature.paramsSig.size > MaxImplementedFunctionArity && + ddef.symbol.allOverriddenSymbols.exists(sym => defn.isXXLFunctionClass(sym.owner)) } yield { val xsType = defn.ArrayType.appliedTo(List(defn.ObjectType)) val methType = MethodType(List(nme.args))(_ => List(xsType), _ => defn.ObjectType) - val meth = newSymbol(tree.symbol.owner, nme.apply, Synthetic | Method, methType) - DefDef(meth, paramss => forwarderRhs(tree, paramss.head.head)) + val meth = newSymbol(ddef.symbol.owner, nme.apply, Synthetic | Method, methType) + DefDef(meth, paramss => forwarderRhs(ddef, paramss.head.head)) } cpy.Template(impl)(body = forwarders ::: impl.body) diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeApplyMethods.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeApplyMethods.scala new file mode 100644 index 000000000000..ef825832c1cd --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeApplyMethods.scala @@ -0,0 +1,118 @@ +package dotty.tools.dotc +package transform + +import ast.Trees._, ast.tpd, core._ +import Contexts._, Types._, Decorators._, Symbols._, DenotTransformers._ +import SymDenotations._, Scopes._, StdNames._, NameOps._, Names._ +import MegaPhase.MiniPhase + +import scala.collection.mutable + + +/** This phase synthesizes specialized methods for FunctionN, this is done + * since there are no scala signatures in the bytecode for the specialized + * methods. + * + * We know which specializations exist for the different arities, therefore we + * can hardcode them. This should, however be removed once we're using a + * different standard library. + */ +class SpecializeApplyMethods extends MiniPhase with InfoTransformer { + import ast.tpd._ + + val phaseName = "specializeApplyMethods" + + override def isEnabled(using Context): Boolean = + !ctx.settings.scalajs.value + + private def specApplySymbol(sym: Symbol, args: List[Type], ret: Type)(using Context): Symbol = { + val name = nme.apply.specializedFunction(ret, args) + // Create the symbol at the next phase, so that it is a valid member of the + // corresponding function for all valid periods of its SymDenotations. + // Otherwise, the valid period will offset by 1, which causes a stale symbol + // in compiling stdlib. + atNextPhase(newSymbol(sym, name, Flags.Method, MethodType(args, ret))) + } + + private inline def specFun0(inline op: Type => Unit)(using Context): Unit = { + for (r <- defn.Function0SpecializedReturnTypes) do + op(r) + } + + private inline def specFun1(inline op: (Type, Type) => Unit)(using Context): Unit = { + for + r <- defn.Function1SpecializedReturnTypes + t1 <- defn.Function1SpecializedParamTypes + do + op(t1, r) + } + + private inline def specFun2(inline op: (Type, Type, Type) => Unit)(using Context): Unit = { + for + r <- defn.Function2SpecializedReturnTypes + t1 <- defn.Function2SpecializedParamTypes + t2 <- defn.Function2SpecializedParamTypes + do + op(t1, t2, r) + } + + override def infoMayChange(sym: Symbol)(using Context) = + sym == defn.Function0 + || sym == defn.Function1 + || sym == defn.Function2 + + /** Add symbols for specialized methods to FunctionN */ + override def transformInfo(tp: Type, sym: Symbol)(using Context) = tp match { + case tp: ClassInfo => + if sym == defn.Function0 then + val scope = tp.decls.cloneScope + specFun0 { r => scope.enter(specApplySymbol(sym, Nil, r)) } + tp.derivedClassInfo(decls = scope) + + else if sym == defn.Function1 then + val scope = tp.decls.cloneScope + specFun1 { (t1, r) => scope.enter(specApplySymbol(sym, t1 :: Nil, r)) } + tp.derivedClassInfo(decls = scope) + + else if sym == defn.Function2 then + val scope = tp.decls.cloneScope + specFun2 { (t1, t2, r) => scope.enter(specApplySymbol(sym, t1 :: t2 :: Nil, r)) } + tp.derivedClassInfo(decls = scope) + + else tp + + case _ => tp + } + + /** Create bridge methods for FunctionN with specialized applys */ + override def transformTemplate(tree: Template)(using Context) = { + val cls = tree.symbol.owner.asClass + + def synthesizeApply(names: collection.Set[TermName]): Tree = { + val applyBuf = new mutable.ListBuffer[DefDef] + names.foreach { name => + val applySym = cls.info.decls.lookup(name) + val ddef = DefDef( + applySym.asTerm, + { vparamss => + This(cls) + .select(nme.apply) + .appliedToArgss(vparamss) + .ensureConforms(applySym.info.finalResultType) + } + ) + applyBuf += ddef + } + cpy.Template(tree)(body = tree.body ++ applyBuf) + } + + if cls == defn.Function0 then + synthesizeApply(defn.Function0SpecializedApplyNames) + else if cls == defn.Function1 then + synthesizeApply(defn.Function1SpecializedApplyNames) + else if cls == defn.Function2 then + synthesizeApply(defn.Function2SpecializedApplyNames) + else + tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala index f931339066b5..781f854383cd 100644 --- a/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala @@ -2,7 +2,7 @@ package dotty.tools.dotc package transform import ast.Trees._, ast.tpd, core._ -import Contexts.Context, Types._, Decorators._, Symbols._, DenotTransformers._ +import Contexts._, Types._, Decorators._, Symbols._, DenotTransformers._ import SymDenotations._, Scopes._, StdNames._, NameOps._, Names._ import MegaPhase.MiniPhase @@ -11,146 +11,94 @@ import scala.collection.mutable /** Specializes classes that inherit from `FunctionN` where there exists a * specialized form. */ -class SpecializeFunctions extends MiniPhase with InfoTransformer { +class SpecializeFunctions extends MiniPhase { import ast.tpd._ val phaseName = "specializeFunctions" - override def runsAfter = Set(classOf[ElimByName]) - - private val jFunction = "scala.compat.java8.JFunction".toTermName - - /** Transforms the type to include decls for specialized applys */ - override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { - case tp: ClassInfo if !sym.is(Flags.Package) && (tp.decls ne EmptyScope) && derivesFromFn012(sym) => - var newApplys = Map.empty[Name, Symbol] - - var arity = 0 - while (arity < 3) { - val func = defn.FunctionClass(arity) - if (tp.derivesFrom(func)) { - val typeParams = tp.cls.typeRef.baseType(func).argInfos - val isSpecializable = - defn.isSpecializableFunction( - sym.asClass, - typeParams.init, - typeParams.last - ) - - if (isSpecializable && tp.decls.lookup(nme.apply).exists) { - val interface = specInterface(typeParams) - val specializedMethodName = nme.apply.specializedFunction(typeParams.last, typeParams.init) - newApplys += (specializedMethodName -> interface) - } - } - arity += 1 - } - - def newDecls = - newApplys.toList.map { case (name, interface) => - ctx.newSymbol( - sym, - name, - Flags.Override | Flags.Method | Flags.Synthetic, - interface.info.decls.lookup(name).info - ) - } - .foldLeft(tp.decls.cloneScope) { - (scope, sym) => scope.enter(sym); scope - } + override def runsAfter = Set(ElimByName.name) - if (newApplys.isEmpty) tp - else tp.derivedClassInfo(decls = newDecls) + override def isEnabled(using Context): Boolean = + !ctx.settings.scalajs.value - case _ => tp - } - - /** Transforms the `Template` of the classes to contain forwarders from the - * generic applys to the specialized ones. Also inserts the specialized applys - * in the template body. + /** Create forwarders from the generic applys to the specialized ones. */ - override def transformTemplate(tree: Template)(implicit ctx: Context) = { - val cls = tree.symbol.enclosingClass.asClass - if (derivesFromFn012(cls)) { - val applyBuf = new mutable.ListBuffer[Tree] - val newBody = tree.body.mapConserve { - case dt: DefDef if dt.name == nme.apply && dt.vparamss.length == 1 => - val typeParams = dt.vparamss.head.map(_.symbol.info) - val retType = dt.tpe.widen.finalResultType - - val specName = specializedName(nme.apply, typeParams :+ retType) - val specializedApply = cls.info.decls.lookup(specName) - if (specializedApply.exists) { - val apply = specializedApply.asTerm - val specializedDecl = - polyDefDef(apply, trefs => vrefss => { - dt.rhs - .changeOwner(dt.symbol, apply) - .subst(dt.vparamss.flatten.map(_.symbol), vrefss.flatten.map(_.symbol)) - }) - applyBuf += specializedDecl - - // create a forwarding to the specialized apply - cpy.DefDef(dt)(rhs = { - tpd - .ref(apply) - .appliedToArgs(dt.vparamss.head.map(vparam => ref(vparam.symbol))) - }) - } else dt - - case x => x - } - - cpy.Template(tree)( - body = applyBuf.toList ::: newBody - ) - } else tree + override def transformDefDef(ddef: DefDef)(using Context) = { + if ddef.name != nme.apply + || ddef.vparamss.length != 1 + || ddef.vparamss.head.length > 2 + || !ctx.owner.isClass + then + return ddef + + val sym = ddef.symbol + val cls = ctx.owner.asClass + + var specName: Name = null + + def isSpecializable = { + val paramTypes = ddef.vparamss.head.map(_.symbol.info) + val retType = sym.info.finalResultType + specName = nme.apply.specializedFunction(retType, paramTypes) + defn.isSpecializableFunction(cls, paramTypes, retType) + } + + if (sym.is(Flags.Deferred) || !isSpecializable) return ddef + + val specializedApply = newSymbol( + cls, + specName, + sym.flags | Flags.Synthetic, + sym.info + ).entered + + val specializedDecl = + DefDef(specializedApply.asTerm, vparamss => { + ddef.rhs + .changeOwner(ddef.symbol, specializedApply) + .subst(ddef.vparamss.head.map(_.symbol), vparamss.head.map(_.symbol)) + }) + + // create a forwarding to the specialized apply + val args = ddef.vparamss.head.map(vparam => ref(vparam.symbol)) + val rhs = This(cls).select(specializedApply).appliedToArgs(args) + val ddef1 = cpy.DefDef(ddef)(rhs = rhs) + Thicket(ddef1, specializedDecl) } /** Dispatch to specialized `apply`s in user code when available */ - override def transformApply(tree: Apply)(implicit ctx: Context) = + override def transformApply(tree: Apply)(using Context) = tree match { - case Apply(fun, args) - if fun.symbol.name == nme.apply && - fun.symbol.owner.derivesFrom(defn.FunctionClass(args.length)) - => - val params = (fun.tpe.widen.firstParamTypes :+ tree.tpe).map(_.widenSingleton.dealias) + case Apply(fun: NameTree, args) if fun.name == nme.apply && args.size <= 3 => + val argTypes = fun.tpe.widen.firstParamTypes.map(_.widenSingleton.dealias) + val retType = tree.tpe.widenSingleton.dealias val isSpecializable = defn.isSpecializableFunction( fun.symbol.owner.asClass, - params.init, - params.last) - - if (isSpecializable && !params.exists(_.isInstanceOf[ExprType])) { - val specializedApply = specializedName(nme.apply, params) - val newSel = fun match { - case Select(qual, _) => - qual.select(specializedApply) - case _ => - (fun.tpe: @unchecked) match { - case TermRef(prefix: ThisType, name) => - tpd.This(prefix.cls).select(specializedApply) - case TermRef(prefix: NamedType, name) => - tpd.ref(prefix).select(specializedApply) - } - } - - newSel.appliedToArgs(args) + argTypes, + retType + ) + + if (!isSpecializable || argTypes.exists(_.isInstanceOf[ExprType])) return tree + + val specializedApply = nme.apply.specializedFunction(retType, argTypes) + val newSel = fun match { + case Select(qual, _) => + qual.select(specializedApply) + case _ => + (fun.tpe: @unchecked) match { + case TermRef(prefix: ThisType, name) => + tpd.This(prefix.cls).select(specializedApply) + case TermRef(prefix: NamedType, name) => + tpd.ref(prefix).select(specializedApply) + } } - else tree + + newSel.appliedToArgs(args) case _ => tree } - private def specializedName(name: Name, args: List[Type])(implicit ctx: Context) = - name.specializedFunction(args.last, args.init) - - private def functionName(typeParams: List[Type])(implicit ctx: Context) = - jFunction ++ (typeParams.length - 1).toString - - private def specInterface(typeParams: List[Type])(implicit ctx: Context) = - ctx.getClassIfDefined(functionName(typeParams).specializedFunction(typeParams.last, typeParams.init)) - - private def derivesFromFn012(sym: Symbol)(implicit ctx: Context): Boolean = - sym.derivesFrom(defn.FunctionClass(0)) || - sym.derivesFrom(defn.FunctionClass(1)) || - sym.derivesFrom(defn.FunctionClass(2)) + private def derivesFromFn012(cls: ClassSymbol)(using Context): Boolean = + cls.baseClasses.exists { p => + p == defn.Function0 || p == defn.Function1 || p == defn.Function2 + } } diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala b/compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala deleted file mode 100644 index 19c7abacb5fb..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/SpecializedApplyMethods.scala +++ /dev/null @@ -1,96 +0,0 @@ -package dotty.tools.dotc -package transform - -import ast.Trees._, ast.tpd, core._ -import Contexts.Context, Types._, Decorators._, Symbols._, DenotTransformers._ -import SymDenotations._, Scopes._, StdNames._, NameOps._, Names._ -import MegaPhase.MiniPhase - -/** This phase synthesizes specialized methods for FunctionN, this is done - * since there are no scala signatures in the bytecode for the specialized - * methods. - * - * We know which specializations exist for the different arities, therefore we - * can hardcode them. This should, however be removed once we're using a - * different standard library. - */ -class SpecializedApplyMethods extends MiniPhase with InfoTransformer { - import ast.tpd._ - - val phaseName = "specializedApplyMethods" - - private[this] var func0Applys: collection.Set[Symbol] = _ - private[this] var func1Applys: collection.Set[Symbol] = _ - private[this] var func2Applys: collection.Set[Symbol] = _ - private[this] var func0: Symbol = _ - private[this] var func1: Symbol = _ - private[this] var func2: Symbol = _ - - private def init()(implicit ctx: Context): Unit = if (func0Applys eq null) { - val definitions = ctx.definitions - import definitions._ - - def specApply(sym: Symbol, args: List[Type], ret: Type)(implicit ctx: Context): Symbol = { - val name = nme.apply.specializedFunction(ret, args) - ctx.newSymbol(sym, name, Flags.Method, MethodType(args, ret)) - } - - func0 = FunctionClass(0) - func0Applys = for (r <- defn.Function0SpecializedReturns) yield specApply(func0, Nil, r) - - func1 = FunctionClass(1) - func1Applys = for { - r <- defn.Function1SpecializedReturns - t1 <- defn.Function1SpecializedParams - } yield specApply(func1, List(t1), r) - - func2 = FunctionClass(2) - func2Applys = for { - r <- Function2SpecializedReturns - t1 <- Function2SpecializedParams - t2 <- Function2SpecializedReturns - } yield specApply(func2, List(t1, t2), r) - } - - /** Add symbols for specialized methods to FunctionN */ - override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { - case tp: ClassInfo if defn.isPlainFunctionClass(sym) => { - init() - val newDecls = sym.name.functionArity match { - case 0 => func0Applys.foldLeft(tp.decls.cloneScope) { - (decls, sym) => decls.enter(sym); decls - } - case 1 => func1Applys.foldLeft(tp.decls.cloneScope) { - (decls, sym) => decls.enter(sym); decls - } - case 2 => func2Applys.foldLeft(tp.decls.cloneScope) { - (decls, sym) => decls.enter(sym); decls - } - case _ => tp.decls - } - - tp.derivedClassInfo(decls = newDecls) - } - case _ => tp - } - - /** Create bridge methods for FunctionN with specialized applys */ - override def transformTemplate(tree: Template)(implicit ctx: Context) = { - val owner = tree.symbol.owner - val additionalSymbols = - if (owner eq func0) func0Applys - else if (owner eq func1) func1Applys - else if (owner eq func2) func2Applys - else Nil - - if (additionalSymbols eq Nil) tree - else cpy.Template(tree)(body = tree.body ++ additionalSymbols.map { apply => - DefDef(apply.asTerm, { vparamss => - This(owner.asClass) - .select(nme.apply) - .appliedToArgss(vparamss) - .ensureConforms(apply.info.finalResultType) - }) - }) - } -} diff --git a/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala b/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala index 867c3329d944..dab1d8f25ca2 100644 --- a/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala +++ b/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala @@ -47,7 +47,7 @@ abstract class TransformByNameApply extends MiniPhase { thisPhase: DenotTransfor ref(defn.cbnArg).appliedToType(argType).appliedTo(arg).withSpan(arg.span) arg match { case Apply(Select(qual, nme.apply), Nil) - if qual.tpe.derivesFrom(defn.FunctionClass(0)) && (isPureExpr(qual) || qual.symbol.isAllOf(Inline | Param)) => + if qual.tpe.derivesFrom(defn.Function0) && (isPureExpr(qual) || qual.symbol.isAllOf(Inline | Param)) => wrap(qual) case _ => if (isByNameRef(arg) || arg.symbol == defn.cbnArg) arg diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index ee9095e693f3..03a81f42745b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -174,7 +174,7 @@ object Implicits: // We keep the old behavior under -source 3.0-migration. val isFunctionInS2 = migrateTo3 - && tpw.derivesFrom(defn.FunctionClass(1)) + && tpw.derivesFrom(defn.Function1) && ref.symbol != defn.Predef_conforms val isImplicitConversion = tpw.derivesFrom(defn.ConversionClass) // An implementation of <:< counts as a view diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala index 771dd6adf132..2ceeec681295 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala @@ -21,6 +21,8 @@ import scala.tools.asm.{ClassWriter, ClassReader} import scala.tools.asm.tree._ import java.io.{File => JFile, InputStream} +import org.junit.Assert._ + trait DottyBytecodeTest { import AsmNode._ import ASMConverters._ @@ -75,7 +77,7 @@ trait DottyBytecodeTest { } /** Finds a class with `cls` as name in `dir`, throws if it can't find it */ - def findClass(cls: String, dir: Directory) = { + def findClass(cls: String, dir: AbstractFile) = { val clsIn = dir.lookupName(s"$cls.class", directory = false).input val clsNode = loadClassNode(clsIn) assert(clsNode.name == cls, s"inspecting wrong class: ${clsNode.name}") @@ -229,7 +231,7 @@ trait DottyBytecodeTest { } .getOrElse(fail("Could not find constructor for object `Test`")) - private def boxingError(ins: List[_], source: String) = + private def boxingError(ins: List[_], source: String) = s"""|---------------------------------- |${ins.mkString("\n")} |---------------------------------- @@ -257,6 +259,15 @@ trait DottyBytecodeTest { (ins, boxed) } + protected def hasInvokeStatic(method: MethodNode): Boolean = { + val ins = instructionsFromMethod(method) + ins.exists { + case Invoke(op, owner, name, desc, itf) => + op == 184 + case _ => false + } + } + } object DottyBytecodeTest { extension [T](l: List[T]) def stringLines = l.mkString("\n") diff --git a/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala b/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala index 503d5198aae4..5d159622f5ea 100644 --- a/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala +++ b/compiler/test/dotty/tools/dotc/transform/SpecializeFunctionsTests.scala @@ -50,7 +50,9 @@ class SpecializeFunctionsTests extends DottyBytecodeTest { checkBCode(source) { dir => val apps = findClass("Func2", dir).methods.asScala.collect { - case m if m.name == "apply$mcIII$sp" => m + case m if m.name == "apply$mcIII$sp" => + assert(!hasInvokeStatic(m)) // should not call super specialized method + m case m if m.name == "apply" => m } .map(_.name) @@ -65,9 +67,31 @@ class SpecializeFunctionsTests extends DottyBytecodeTest { } } + @Test def notSpecializeAbstractMethod = { + val source = + """|trait Vector extends (Int=>Int) { + | override def apply(i: Int): Int + |}""".stripMargin + + checkBCode(source) { dir => + val apps = + findClass("Vector", dir).methods.asScala.collect { + case m if m.name == "apply$mcII$sp" => m + case m if m.name == "apply" => m + } + .map(_.name) + .toList + + assert( + apps.length == 1, + s"Wrong number of specialized applys, actual length: ${apps.length} - $apps" + ) + } + } + @Test def noBoxingSpecFunction0 = { implicit val source: String = - """|object Test { + """|class Test { | class Func0 extends Function0[Int] { | def apply() = 1337 | } @@ -76,13 +100,13 @@ class SpecializeFunctionsTests extends DottyBytecodeTest { |}""".stripMargin checkBCode(source) { dir => - assertNoBoxing("", findClass("Test$", dir).methods) + assertNoBoxing("", findClass("Test", dir).methods) } } @Test def boxingFunction1 = { implicit val source: String = - """|object Test { + """|class Test { | class Func1 extends Function1[Char, Int] { | def apply(c: Char) = c.toInt | } @@ -92,13 +116,13 @@ class SpecializeFunctionsTests extends DottyBytecodeTest { checkBCode(source) { dir => // No specialization for Function1[Char, Int] - assertBoxing("", findClass("Test$", dir).methods) + assertBoxing("", findClass("Test", dir).methods) } } @Test def noBoxingSpecFunction1 = { implicit val source: String = - """|object Test { + """|class Test { | class Func1 extends Function1[Int, Int] { | def apply(i: Int) = i + 1 | } @@ -107,13 +131,13 @@ class SpecializeFunctionsTests extends DottyBytecodeTest { |}""".stripMargin checkBCode(source) { dir => - assertNoBoxing("", findClass("Test$", dir).methods) + assertNoBoxing("", findClass("Test", dir).methods) } } @Test def noBoxingSpecFunction2 = { implicit val source: String = - """|object Test { + """|class Test { | class Func2 extends Function2[Int, Int, Int] { | def apply(i: Int, j: Int) = i + j | } @@ -122,13 +146,13 @@ class SpecializeFunctionsTests extends DottyBytecodeTest { |}""".stripMargin checkBCode(source) { dir => - assertNoBoxing("", findClass("Test$", dir).methods) + assertNoBoxing("", findClass("Test", dir).methods) } } @Test def boxingFunction2 = { implicit val source: String = - """|object Test { + """|class Test { | class Func2 extends Function2[Char, Char, Char] { | def apply(c1: Char, c2: Char) = c1 | } @@ -138,13 +162,13 @@ class SpecializeFunctionsTests extends DottyBytecodeTest { checkBCode(source) { dir => // No specialization for Function2[Char, Char, Char] - assertBoxing("", findClass("Test$", dir).methods) + assertBoxing("", findClass("Test", dir).methods) } } @Test def multipleParentsNoBoxing = { implicit val source: String = - """|object Test { + """|class Test { | class Func01 extends Function0[Int] with Function1[Int, Int] { | def apply(): Int = 0 | def apply(x: Int): Int = x @@ -154,13 +178,13 @@ class SpecializeFunctionsTests extends DottyBytecodeTest { |}""".stripMargin checkBCode(source) { dir => - assertNoBoxing("", findClass("Test$", dir).methods) + assertNoBoxing("", findClass("Test", dir).methods) } } @Test def multipleLevelInheritanceNoBoxing = { implicit val source: String = - """|object Test { + """|class Test { | class Func1[T](fn: T => Int) extends Function1[T, Int] { | def apply(x: T): Int = fn(x) | } @@ -169,25 +193,25 @@ class SpecializeFunctionsTests extends DottyBytecodeTest { |}""".stripMargin checkBCode(source) { dir => - assertNoBoxing("", findClass("Test$", dir).methods) + assertNoBoxing("", findClass("Test", dir).methods) } } @Test def lambdaNoBoxing1 = { implicit val source: String = - """|object Test { + """|class Test { | val fn = (x: Int) => x + 1 | fn(2) |}""".stripMargin checkBCode(source) { dir => - assertNoBoxing("", findClass("Test$", dir).methods) + assertNoBoxing("", findClass("Test", dir).methods) } } @Test def lambdaNoBoxing2 = { implicit val source: String = - """|object Test { + """|class Test { | def fn[T, U, V](op0: T => U, op1: U => V): T => V = (x: T) => op1(op0(x)) | val f0: Int => Double = _.toDouble | val f1: Double => Int = _.toInt @@ -196,13 +220,13 @@ class SpecializeFunctionsTests extends DottyBytecodeTest { |}""".stripMargin checkBCode(source) { dir => - assertNoBoxing("", findClass("Test$", dir).methods) + assertNoBoxing("", findClass("Test", dir).methods) } } @Test def classWithFieldBoxing = { implicit val source: String = - """|object Test { + """|class Test { | class Func0[T](x: T) extends Function0[T] { | def apply(): T = x | } @@ -211,19 +235,19 @@ class SpecializeFunctionsTests extends DottyBytecodeTest { checkBCode(source) { dir => // Boxing happens because of the field of `Func0`. - assertBoxing("", findClass("Test$", dir).methods) + assertBoxing("", findClass("Test", dir).methods) } } @Test def passByNameNoBoxing = { implicit val source: String = - """|object Test { + """|class Test { | def fn(x: => Int): Int = x | fn(2) |}""".stripMargin checkBCode(source) { dir => - assertNoBoxing("fn", findClass("Test$", dir).methods) + assertNoBoxing("fn", findClass("Test", dir).methods) } } }