Skip to content

Commit ab85854

Browse files
committed
Implement Tuple operation directly in the compiler
To increase the performance of compiling code with Tuple/NonEmptyTuple operations
1 parent 77bce31 commit ab85854

File tree

6 files changed

+152
-22
lines changed

6 files changed

+152
-22
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class Compiler {
8686
new AugmentScala2Traits, // Augments Scala2 traits with additional members needed for mixin composition.
8787
new ResolveSuper, // Implement super accessors
8888
new FunctionXXLForwarders, // Add forwarders for FunctionXXL apply method
89+
new GenericTuples, // TODO
8990
new ArrayConstructors) :: // Intercept creation of (non-generic) arrays and intrinsify.
9091
List(new Erasure) :: // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements.
9192
List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,10 @@ class Definitions {
683683
lazy val Product_productPrefixR: TermRef = ProductClass.requiredMethodRef(nme.productPrefix)
684684
def Product_productPrefix(implicit ctx: Context): Symbol = Product_productPrefixR.symbol
685685

686+
lazy val IteratorType: TypeRef = ctx.requiredClassRef("scala.collection.Iterator")
687+
def IteratorClass(implicit ctx: Context): ClassSymbol = IteratorType.symbol.asClass
688+
def IteratorModule(implicit ctx: Context): Symbol = IteratorClass.companionModule
689+
686690
lazy val ModuleSerializationProxyType: TypeRef = ctx.requiredClassRef("scala.runtime.ModuleSerializationProxy")
687691
def ModuleSerializationProxyClass(implicit ctx: Context): ClassSymbol = ModuleSerializationProxyType.symbol.asClass
688692
lazy val ModuleSerializationProxyConstructor: TermSymbol =
@@ -783,8 +787,12 @@ class Definitions {
783787

784788
lazy val TupleTypeRef: TypeRef = ctx.requiredClassRef("scala.Tuple")
785789
def TupleClass(implicit ctx: Context): ClassSymbol = TupleTypeRef.symbol.asClass
790+
lazy val Tuple_consR: TermRef = TupleClass.requiredMethod("*:").termRef
791+
def Tuple_cons: Symbol = Tuple_consR.symbol
786792
lazy val NonEmptyTupleTypeRef: TypeRef = ctx.requiredClassRef("scala.NonEmptyTuple")
787793
def NonEmptyTupleClass(implicit ctx: Context): ClassSymbol = NonEmptyTupleTypeRef.symbol.asClass
794+
lazy val NonEmptyTuple_tailR: TermRef = NonEmptyTupleClass.requiredMethod("tail").termRef
795+
def NonEmptyTuple_tail: Symbol = NonEmptyTuple_tailR.symbol
788796

789797
lazy val PairType: TypeRef = ctx.requiredClassRef("scala.*:")
790798
def PairClass(implicit ctx: Context): ClassSymbol = PairType.symbol.asClass
@@ -794,6 +802,11 @@ class Definitions {
794802

795803
def TupleXXL_apply(implicit ctx: Context): Symbol =
796804
TupleXXLModule.info.member(nme.apply).requiredSymbol("method", nme.apply, TupleXXLModule)(_.info.isVarArgsMethod)
805+
def TupleXXL_fromIterator(implicit ctx: Context): Symbol = TupleXXLModule.requiredMethod("fromIterator")
806+
807+
lazy val DynamicTupleModule: Symbol = ctx.requiredModule("scala.runtime.DynamicTuple")
808+
lazy val DynamicTuple_consIterator: Symbol = DynamicTupleModule.requiredMethod("consIterator")
809+
lazy val DynamicTuple_dynamicTail: Symbol = DynamicTupleModule.requiredMethod("dynamicTail")
797810

798811
// Annotation base classes
799812
lazy val AnnotationType: TypeRef = ctx.requiredClassRef("scala.annotation.Annotation")
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core._
5+
import Constants.Constant
6+
import Contexts.Context
7+
import Decorators._
8+
import Flags._
9+
import ast.Trees._
10+
import Definitions._
11+
import DenotTransformers._
12+
import StdNames._
13+
import Symbols._
14+
import MegaPhase._
15+
import Types._
16+
import dotty.tools.dotc.ast.tpd
17+
18+
19+
import scala.annotation.tailrec
20+
21+
/** TODO
22+
*/
23+
class GenericTuples extends MiniPhase with IdentityDenotTransformer {
24+
import tpd._
25+
26+
def phaseName: String = "genericTuples"
27+
28+
override def transformApply(tree: tpd.Apply)(implicit ctx: Context): tpd.Tree = {
29+
if (tree.symbol == defn.Tuple_cons) transformTupleCons(tree)
30+
else super.transformApply(tree)
31+
}
32+
33+
// override def transformTypeApply(tree: tpd.TypeApply)(implicit ctx: Context): tpd.Tree = {
34+
// if (tree.symbol == defn.NonEmptyTuple_tail) transformTupleTail(tree)
35+
// else super.transformTypeApply(tree)
36+
// }
37+
38+
private def transformTupleCons(tree: tpd.Apply)(implicit ctx: Context): Tree = {
39+
val TypeApply(Select(qual, _), headType :: tailType :: Nil) = tree.fun
40+
tupleTypes(tree.tpe) match {
41+
case Some(tpes) =>
42+
val size = tpes.size
43+
if (size <= 5) {
44+
// val t = tail.asInstanceOf[TupleN[...]]
45+
// TupleN+1(head, t._1, ..., t._n)
46+
val tailType =
47+
if (size == 1) defn.UnitType
48+
else defn.TupleType(size - 1).appliedTo(tpes.tail)
49+
evalOnce(Typed(qual, TypeTree(tailType))) { tup =>
50+
val elements = tree.args.head :: (0 until size - 1).map(i => tup.select(nme.selectorName(i))).toList
51+
knownTupleFromElements(tpes, elements)
52+
}
53+
} else {
54+
// val it = Iterator.single(head) ++ tail.asInstanceOf[Product].productIterator
55+
// TupleN(it.next(), ..., it.next())
56+
val fullIterator = ref(defn.DynamicTuple_consIterator).appliedToArgs(tree.args.head :: qual :: Nil)
57+
evalOnce(fullIterator) { it =>
58+
knownTupleFromIterator(tpes.length, it).asInstance(tree.tpe)
59+
}
60+
}
61+
case _ =>
62+
// DynamicTuple.dynamic_*:(tail, head)
63+
ref(defn.DynamicTupleModule).select("dynamic_*:".toTermName).appliedToTypeTrees(tailType :: headType :: Nil).appliedToArgs(qual :: tree.args).asInstance(tree.tpe)
64+
}
65+
}
66+
67+
// private def transformTupleTail(tree: tpd.TypeApply)(implicit ctx: Context): Tree = {
68+
// val Select(tup, _) = tree.fun
69+
// tupleTypes(tree.args.head.tpe) match { // TODO tupleBoundedTypes
70+
// case Some(tpes) =>
71+
// val size = tpes.size
72+
// if (size <= 5) {
73+
// // val t = tup.asInstanceOf[TupleN[...]]
74+
// // TupleN-1(t._2, ..., t._n)
75+
// evalOnce(tup.asInstance(defn.TupleType(size).appliedTo(tpes))) { tup =>
76+
// val elements = (1 until size).map(i => tup.select(nme.selectorName(i))).toList
77+
// knownTupleFromElements(tpes.tail, elements)
78+
// }
79+
// } else {
80+
// // val it = this.asInstanceOf[Product].productIterator
81+
// // it.next()
82+
// // TupleN(it.next(), ..., it.next())
83+
// evalOnce(tup.asInstance(defn.ProductType).select(nme.productIterator)) { it =>
84+
// Block(
85+
// it.select(nme.next).ensureApplied :: Nil,
86+
// knownTupleFromIterator(size - 1, it).asInstance(tree.tpe)
87+
// )
88+
// }
89+
// }
90+
// case None =>
91+
// // DynamicTuple.dynamicTail(tup)
92+
// ref(defn.DynamicTuple_dynamicTail).appliedToType(tree.tpe).appliedToArgs(tup :: Nil)
93+
// }
94+
// }
95+
96+
/** Create a TupleN (1 <= N < 23) from the elements */
97+
private def knownTupleFromElements(tpes: List[Type], elements: List[Tree])(implicit ctx: Context) = {
98+
val size = elements.size
99+
assert(0 < size && size <= Definitions.MaxTupleArity)
100+
val tupleModule = defn.TupleType(size).classSymbol.companionModule
101+
ref(tupleModule).select(nme.apply).appliedToTypes(tpes).appliedToArgs(elements)
102+
}
103+
104+
private def knownTupleFromIterator(size: Int, it: Tree)(implicit ctx: Context): Tree = {
105+
if (size == 0) {
106+
// Unit for empty tuple
107+
Literal(Constant(())) // TODO should this code be here? Or assert(size > specializedSize)
108+
}
109+
else if (size <= Definitions.MaxTupleArity) {
110+
// TupleN(it.next(), ..., it.next())
111+
112+
// TODO outline this code for the 22 alternatives (or less, may not need the smallest ones)?
113+
// This would yield smaller bytecode at the cost of an extra (easily JIT inlinable) call.
114+
// def dynamicTupleN(it: Iterator[Any]): TupleN[Any, ..., Any] = Tuple(it.next(), ..., it.next())
115+
val tpes = List.fill(size)(defn.AnyType)
116+
val elements = (0 until size).map(_ => it.select(nme.next)).toList
117+
knownTupleFromElements(tpes, elements)
118+
} else {
119+
// TupleXXL.fromIterator(it)
120+
ref(defn.TupleXXL_fromIterator).appliedTo(it)
121+
}
122+
}
123+
124+
private def tupleTypes(tp: Type)(implicit ctx: Context): Option[List[Type]] = {
125+
@tailrec def rec(tp: Type, acc: List[Type]): Option[List[Type]] = tp match {
126+
case tp: AppliedType if defn.PairClass == tp.classSymbol => rec(tp.args(1), tp.args(0) :: acc)
127+
case tp: AppliedType if defn.isTupleClass(tp.tycon.classSymbol) => Some(acc.reverse ::: tp.args)
128+
case tp if tp.classSymbol == defn.UnitClass => Some(acc.reverse)
129+
case _ => None
130+
}
131+
rec(tp.stripTypeVar, Nil)
132+
}
133+
}

library/src-3.x/scala/Tuple.scala

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,28 +32,7 @@ sealed trait Tuple extends Any {
3232
DynamicTuple.dynamicToArray(this)
3333
}
3434

35-
inline def *: [H, This >: this.type <: Tuple] (x: H): H *: This = {
36-
type Result = H *: This
37-
inline constValueOpt[BoundedSize[this.type]] match {
38-
case Some(0) =>
39-
Tuple1(x).asInstanceOf[Result]
40-
case Some(1) =>
41-
Tuple2(x, asInstanceOf[Tuple1[_]]._1).asInstanceOf[Result]
42-
case Some(2) =>
43-
val t = asInstanceOf[Tuple2[_, _]]
44-
Tuple3(x, t._1, t._2).asInstanceOf[Result]
45-
case Some(3) =>
46-
val t = asInstanceOf[Tuple3[_, _, _]]
47-
Tuple4(x, t._1, t._2, t._3).asInstanceOf[Result]
48-
case Some(4) =>
49-
val t = asInstanceOf[Tuple4[_, _, _, _]]
50-
Tuple5(x, t._1, t._2, t._3, t._4).asInstanceOf[Result]
51-
case Some(n) =>
52-
knownTupleFromItrator[H *: this.type](n + 1, Iterator.single(x) ++ this.asInstanceOf[Product].productIterator)
53-
case _ =>
54-
DynamicTuple.dynamic_*:[This, H](this, x)
55-
}
56-
}
35+
def *: [H, This >: this.type <: Tuple] (x: H): H *: This = ???
5736

5837
inline def ++ [This >: this.type <: Tuple](that: Tuple): Concat[This, that.type] = {
5938
type Result = Concat[This, that.type]

library/src-3.x/scala/runtime/DynamicTuple.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,4 +269,7 @@ object DynamicTuple {
269269
}
270270
res.asInstanceOf[Result]
271271
}
272+
273+
def consIterator(head: Any, tail: Tuple): Iterator[Any] =
274+
Iterator.single(head) ++ tail.asInstanceOf[Product].productIterator
272275
}

library/src/scala/TupleXXL.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ final class TupleXXL private (es: Array[Object]) extends Product {
2121
def elems: Array[Object] = es
2222
}
2323
object TupleXXL {
24+
def fromIterator(elems: Iterator[Any]) = new TupleXXL(elems.map(_.asInstanceOf[Object]).toArray)
2425
def apply(elems: Array[Object]) = new TupleXXL(elems.clone)
2526
def apply(elems: Any*) = new TupleXXL(elems.asInstanceOf[Seq[Object]].toArray)
2627
def unapplySeq(x: TupleXXL): Option[Seq[Any]] = Some(x.elems.toSeq)

0 commit comments

Comments
 (0)