Skip to content

Commit 09b778f

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 09b778f

File tree

10 files changed

+312
-273
lines changed

10 files changed

+312
-273
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: 21 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,19 @@ 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_concatIterator: Symbol = DynamicTupleModule.requiredMethod("concatIterator")
810+
lazy val DynamicTuple_dynamicApply: Symbol = DynamicTupleModule.requiredMethod("dynamicApply")
811+
lazy val DynamicTuple_dynamicCons: Symbol = DynamicTupleModule.requiredMethod("dynamicCons")
812+
lazy val DynamicTuple_dynamicHead: Symbol = DynamicTupleModule.requiredMethod("dynamicHead")
813+
lazy val DynamicTuple_dynamicSize: Symbol = DynamicTupleModule.requiredMethod("dynamicSize")
814+
lazy val DynamicTuple_dynamicTail: Symbol = DynamicTupleModule.requiredMethod("dynamicTail")
815+
lazy val DynamicTuple_dynamicConcat: Symbol = DynamicTupleModule.requiredMethod("dynamicConcat")
816+
lazy val DynamicTuple_dynamicToArray: Symbol = DynamicTupleModule.requiredMethod("dynamicToArray")
817+
lazy val DynamicTuple_productToArray: Symbol = DynamicTupleModule.requiredMethod("productToArray")
797818

798819
// Annotation base classes
799820
lazy val AnnotationType: TypeRef = ctx.requiredClassRef("scala.annotation.Annotation")

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ object StdNames {
410410
val drop: N = "drop"
411411
val dynamics: N = "dynamics"
412412
val elem: N = "elem"
413+
val elems: N = "elems"
413414
val emptyValDef: N = "emptyValDef"
414415
val ensureAccessible : N = "ensureAccessible"
415416
val enumTag: N = "enumTag"

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -626,14 +626,18 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
626626

627627
if (ctx.settings.XprintTypes.value && tree.hasType) {
628628
// add type to term nodes; replace type nodes with their types unless -Yprint-pos is also set.
629-
def tp = tree.typeOpt match {
629+
val tp1 = tree.typeOpt match {
630630
case tp: TermRef if tree.isInstanceOf[RefTree] && !tp.denot.isOverloaded => tp.underlying
631631
case tp => tp
632632
}
633+
val tp2 = {
634+
val tp = tp1.tryNormalize
635+
if (tp != NoType) tp else tp1
636+
}
633637
if (!suppressTypes)
634-
txt = ("<" ~ txt ~ ":" ~ toText(tp) ~ ">").close
638+
txt = ("<" ~ txt ~ ":" ~ toText(tp2) ~ ">").close
635639
else if (tree.isType && !homogenizedView)
636-
txt = toText(tp)
640+
txt = toText(tp2)
637641
}
638642
if (!suppressPositions) {
639643
if (printPos) {
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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+
import scala.annotation.tailrec
19+
20+
/** TODO
21+
*/
22+
class GenericTuples extends MiniPhase with IdentityDenotTransformer {
23+
import tpd._
24+
25+
def phaseName: String = "genericTuples"
26+
27+
override def transformApply(tree: tpd.Apply)(implicit ctx: Context): tpd.Tree = {
28+
if (tree.symbol == defn.DynamicTuple_dynamicCons) transformTupleCons(tree)
29+
else if (tree.symbol == defn.DynamicTuple_dynamicHead) transformTupleHead(tree)
30+
else if (tree.symbol == defn.DynamicTuple_dynamicTail) transformTupleTail(tree)
31+
else if (tree.symbol == defn.DynamicTuple_dynamicSize) transformTupleSize(tree)
32+
else if (tree.symbol == defn.DynamicTuple_dynamicConcat) transformTupleConcat(tree)
33+
else if (tree.symbol == defn.DynamicTuple_dynamicApply) transformTupleApply(tree)
34+
else if (tree.symbol == defn.DynamicTuple_dynamicToArray) transformTupleToArray(tree)
35+
else super.transformApply(tree)
36+
}
37+
38+
private def transformTupleCons(tree: tpd.Apply)(implicit ctx: Context): Tree = {
39+
val TypeApply(_, headType :: tailType :: Nil) = tree.fun
40+
val tail :: head :: Nil = tree.args
41+
tupleTypes(tree.tpe) match {
42+
case Some(tpes) =>
43+
val size = tpes.size
44+
if (size <= 5) {
45+
// val t = tail
46+
// TupleN+1(head, t._1, ..., t._n)
47+
evalOnce(Typed(tail, TypeTree(defn.tupleType(tpes.tail)))) { tup =>
48+
val elements = head :: (0 until size - 1).map(i => tup.select(nme.selectorName(i))).toList
49+
knownTupleFromElements(tpes, elements)
50+
}
51+
} else {
52+
// val it = Iterator.single(head) ++ tail.asInstanceOf[Product].productIterator
53+
// TupleN(it.next(), ..., it.next())
54+
val fullIterator = ref(defn.DynamicTuple_consIterator).appliedToArgs(head :: tail :: Nil)
55+
evalOnce(fullIterator) { it =>
56+
knownTupleFromIterator(tpes.length, it).asInstance(tree.tpe)
57+
}
58+
}
59+
case _ =>
60+
// DynamicTuple.dynamicCons:(tail, head)
61+
tree
62+
}
63+
}
64+
65+
private def transformTupleTail(tree: tpd.Apply)(implicit ctx: Context): Tree = {
66+
val Apply(TypeApply(_, tpt :: Nil), tup :: Nil) = tree
67+
tupleTypes(tpt.tpe) match { // TODO tupleBoundedTypes
68+
case Some(tpes) =>
69+
val size = tpes.size
70+
assert(size > 0)
71+
if (size == 1) {
72+
// ()
73+
Literal(Constant(()))
74+
}
75+
else if (size <= 5) {
76+
// val t = tup.asInstanceOf[TupleN[...]]
77+
// TupleN-1(t._2, ..., t._n)
78+
evalOnce(Typed(tup, TypeTree(defn.tupleType(tpes)))) { tup =>
79+
val elements = (1 until size).map(i => tup.select(nme.selectorName(i))).toList
80+
knownTupleFromElements(tpes.tail, elements)
81+
}
82+
} else {
83+
// val it = this.asInstanceOf[Product].productIterator
84+
// it.next()
85+
// TupleN(it.next(), ..., it.next())
86+
evalOnce(tup.asInstance(defn.ProductType).select(nme.productIterator)) { it =>
87+
Block(
88+
it.select(nme.next).ensureApplied :: Nil,
89+
knownTupleFromIterator(size - 1, it).asInstance(tree.tpe)
90+
)
91+
}
92+
}
93+
case None =>
94+
// DynamicTuple.dynamicTail(tup)
95+
tree
96+
}
97+
}
98+
99+
private def transformTupleHead(tree: tpd.Apply)(implicit ctx: Context): Tree = {
100+
val Apply(TypeApply(_, tpt :: Nil), tup :: Nil) = tree
101+
tupleTypes(tpt.tpe) match { // TODO tupleBoundedTypes
102+
case Some(tpes) =>
103+
if (tpes.size <= Definitions.MaxTupleArity) {
104+
// tup._1
105+
Typed(tup, TypeTree(defn.tupleType(tpes))).select(nme.selectorName(0))
106+
} else {
107+
// tup.asInstanceOf[TupleXXL].productElement(0)
108+
tup.asInstance(defn.TupleXXLType).select(nme.productElement).appliedTo(Literal(Constant(0)))
109+
}
110+
case None =>
111+
// DynamicTuple.dynamicHead(tup)
112+
tree
113+
}
114+
}
115+
116+
private def transformTupleSize(tree: tpd.Apply)(implicit ctx: Context): Tree = {
117+
tree.tpe.tryNormalize match {
118+
case tp: ConstantType => Literal(tp.value)
119+
case _ => tree
120+
}
121+
}
122+
123+
private def transformTupleConcat(tree: tpd.Apply)(implicit ctx: Context): Tree = {
124+
val Apply(TypeApply(_, selfTp :: thatTp :: Nil), self :: that :: Nil) = tree
125+
126+
(tupleTypes(selfTp.tpe), tupleTypes(that.tpe.widenTermRefExpr)) match {
127+
case (Some(tpes1), Some(tpes2)) =>
128+
val n = tpes1.size
129+
val m = tpes2.size
130+
if (n == 0) that
131+
else if (m == 0) self
132+
else if (n + m < 5) {
133+
// val t = self
134+
// val u = that
135+
// TupleN+M(t._1,..., t._N, u._1, ..., u._M)
136+
evalOnce(Typed(self, TypeTree(defn.tupleType(tpes1)))) { self =>
137+
evalOnce(Typed(that, TypeTree(defn.tupleType(tpes2)))) { that =>
138+
val types = tpes1 ::: tpes2
139+
val elements = {
140+
(0 until n).map(i => self.select(nme.selectorName(i))) ++
141+
(0 until m).map(i => that.select(nme.selectorName(i)))
142+
}.toList
143+
knownTupleFromElements(types, elements)
144+
}
145+
}
146+
} else {
147+
// val it = self.asInstanceOf[Product].productIterator ++ that.asInstanceOf[Product].productIterator
148+
// TupleN(it.next(), ..., it.next())
149+
val fullIterator = ref(defn.DynamicTuple_concatIterator).appliedToArgs(tree.args)
150+
evalOnce(fullIterator) { it =>
151+
knownTupleFromIterator(n + m, it).asInstance(tree.tpe)
152+
}
153+
}
154+
case _ =>
155+
// DynamicTuple.dynamicCons[This, that.type](self, that)
156+
tree
157+
}
158+
}
159+
160+
private def transformTupleApply(tree: tpd.Apply)(implicit ctx: Context): Tree = {
161+
val Apply(TypeApply(_, tpt :: nTpt :: Nil), tup :: nTree :: Nil) = tree
162+
(tupleTypes(tpt.tpe), nTpt.tpe) match {
163+
case (Some(tpes), nTpe: ConstantType) =>
164+
val size = tpes.size
165+
val n = nTpe.value.intValue
166+
if (n < 0 || n >= size) {
167+
ctx.error("index out of bounds: " + n, nTree.underlyingArgument.sourcePos)
168+
tree
169+
} else if (size <= Definitions.MaxTupleArity) {
170+
// tup._n
171+
Typed(tup, TypeTree(defn.tupleType(tpes))).select(nme.selectorName(n))
172+
} else {
173+
// tup.asInstanceOf[TupleXXL].productElement(n)
174+
tup.asInstance(defn.TupleXXLType).select(nme.productElement).appliedTo(Literal(nTpe.value))
175+
}
176+
case (None, nTpe: ConstantType) if nTpe.value.intValue < 0 =>
177+
ctx.error("index out of bounds: " + nTpe.value.intValue, nTree.sourcePos)
178+
tree
179+
case _ =>
180+
// DynamicTuple.dynamicApply(tup, n)
181+
tree
182+
}
183+
}
184+
185+
private def transformTupleToArray(tree: tpd.Apply)(implicit ctx: Context): Tree = {
186+
val Apply(_, tup :: Nil) = tree
187+
tupleTypes(tup.tpe.widen) match { // TODO tupleBoundedTypes
188+
case Some(tpes) =>
189+
val size = tpes.size
190+
if (size == 0) {
191+
// Array.emptyObjectArray
192+
ref(defn.ArrayModule.companionModule).select("emptyObjectArray".toTermName).ensureApplied
193+
} else if (size <= Definitions.MaxTupleArity) {
194+
// DynamicTuple.productToArray(tup.asInstanceOf[Product])
195+
ref(defn.DynamicTuple_productToArray).appliedTo(tup.asInstance(defn.ProductType))
196+
} else {
197+
// tup.asInstanceOf[TupleXXL].elems
198+
tup.asInstance(defn.TupleXXLType).select(nme.elems)
199+
}
200+
case None =>
201+
// DynamicTuple.dynamicToArray(tup)
202+
tree
203+
}
204+
}
205+
206+
/** Create a TupleN (1 <= N < 23) from the elements */
207+
private def knownTupleFromElements(tpes: List[Type], elements: List[Tree])(implicit ctx: Context) = {
208+
val size = elements.size
209+
assert(0 < size && size <= Definitions.MaxTupleArity)
210+
val tupleModule = defn.TupleType(size).classSymbol.companionModule
211+
ref(tupleModule).select(nme.apply).appliedToTypes(tpes).appliedToArgs(elements)
212+
}
213+
214+
private def knownTupleFromIterator(size: Int, it: Tree)(implicit ctx: Context): Tree = {
215+
if (size == 0) {
216+
// Unit for empty tuple
217+
Literal(Constant(())) // TODO should this code be here? Or assert(size > specializedSize)
218+
}
219+
else if (size <= Definitions.MaxTupleArity) {
220+
// TupleN(it.next(), ..., it.next())
221+
222+
// TODO outline this code for the 22 alternatives (or less, may not need the smallest ones)?
223+
// This would yield smaller bytecode at the cost of an extra (easily JIT inlinable) call.
224+
// def dynamicTupleN(it: Iterator[Any]): TupleN[Any, ..., Any] = Tuple(it.next(), ..., it.next())
225+
val tpes = List.fill(size)(defn.AnyType)
226+
val elements = (0 until size).map(_ => it.select(nme.next)).toList
227+
knownTupleFromElements(tpes, elements)
228+
} else {
229+
// TupleXXL.fromIterator(it)
230+
ref(defn.TupleXXL_fromIterator).appliedTo(it)
231+
}
232+
}
233+
234+
private def tupleTypes(tp: Type)(implicit ctx: Context): Option[List[Type]] = {
235+
@tailrec def rec(tp: Type, acc: List[Type]): Option[List[Type]] = tp match {
236+
case tp: AppliedType if defn.PairClass == tp.classSymbol => rec(tp.args(1), tp.args(0) :: acc)
237+
case tp: AppliedType if defn.isTupleClass(tp.tycon.classSymbol) => Some(acc.reverse ::: tp.args)
238+
case tp if tp.classSymbol == defn.UnitClass => Some(acc.reverse)
239+
case _ => None
240+
}
241+
rec(tp.stripTypeVar, Nil)
242+
}
243+
}

compiler/test/dotc/run-test-pickling.blacklist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ t3452e
44
t3452g
55
t7374
66
tuples1.scala
7+
tuples1a.scala
8+
tuples1b.scala
79
typeclass-derivation1.scala
810
typeclass-derivation2.scala
911
typeclass-derivation2a.scala
@@ -13,3 +15,4 @@ derive-generic.scala
1315
mixin-forwarder-overload
1416
t8905
1517
t10889
18+
i5257.scala

0 commit comments

Comments
 (0)