Skip to content

Commit 33ad06b

Browse files
Merge pull request #6539 from dotty-staging/tuple-codegen-in-compiler
Implement Tuple operation directly in the compiler
2 parents bec8fa0 + e5b880f commit 33ad06b

File tree

16 files changed

+435
-348
lines changed

16 files changed

+435
-348
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class Compiler {
8787
new AugmentScala2Traits, // Augments Scala2 traits with additional members needed for mixin composition.
8888
new ResolveSuper, // Implement super accessors
8989
new FunctionXXLForwarders, // Add forwarders for FunctionXXL apply method
90+
new TupleOptimizations, // Optimize generic operations on tuples
9091
new ArrayConstructors) :: // Intercept creation of (non-generic) arrays and intrinsify.
9192
List(new Erasure) :: // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements.
9293
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
@@ -688,6 +688,10 @@ class Definitions {
688688
lazy val Product_productPrefixR: TermRef = ProductClass.requiredMethodRef(nme.productPrefix)
689689
def Product_productPrefix(implicit ctx: Context): Symbol = Product_productPrefixR.symbol
690690

691+
lazy val IteratorType: TypeRef = ctx.requiredClassRef("scala.collection.Iterator")
692+
def IteratorClass(implicit ctx: Context): ClassSymbol = IteratorType.symbol.asClass
693+
def IteratorModule(implicit ctx: Context): Symbol = IteratorClass.companionModule
694+
691695
lazy val ModuleSerializationProxyType: TypeRef = ctx.requiredClassRef("scala.runtime.ModuleSerializationProxy")
692696
def ModuleSerializationProxyClass(implicit ctx: Context): ClassSymbol = ModuleSerializationProxyType.symbol.asClass
693697
lazy val ModuleSerializationProxyConstructor: TermSymbol =
@@ -792,8 +796,12 @@ class Definitions {
792796

793797
lazy val TupleTypeRef: TypeRef = ctx.requiredClassRef("scala.Tuple")
794798
def TupleClass(implicit ctx: Context): ClassSymbol = TupleTypeRef.symbol.asClass
799+
lazy val Tuple_consR: TermRef = TupleClass.requiredMethod("*:").termRef
800+
def Tuple_cons: Symbol = Tuple_consR.symbol
795801
lazy val NonEmptyTupleTypeRef: TypeRef = ctx.requiredClassRef("scala.NonEmptyTuple")
796802
def NonEmptyTupleClass(implicit ctx: Context): ClassSymbol = NonEmptyTupleTypeRef.symbol.asClass
803+
lazy val NonEmptyTuple_tailR: TermRef = NonEmptyTupleClass.requiredMethod("tail").termRef
804+
def NonEmptyTuple_tail: Symbol = NonEmptyTuple_tailR.symbol
797805

798806
lazy val PairType: TypeRef = ctx.requiredClassRef("scala.*:")
799807
def PairClass(implicit ctx: Context): ClassSymbol = PairType.symbol.asClass
@@ -803,6 +811,19 @@ class Definitions {
803811

804812
def TupleXXL_apply(implicit ctx: Context): Symbol =
805813
TupleXXLModule.info.member(nme.apply).requiredSymbol("method", nme.apply, TupleXXLModule)(_.info.isVarArgsMethod)
814+
def TupleXXL_fromIterator(implicit ctx: Context): Symbol = TupleXXLModule.requiredMethod("fromIterator")
815+
816+
lazy val DynamicTupleModule: Symbol = ctx.requiredModule("scala.runtime.DynamicTuple")
817+
lazy val DynamicTupleModuleClass: Symbol = DynamicTupleModule.moduleClass
818+
lazy val DynamicTuple_consIterator: Symbol = DynamicTupleModule.requiredMethod("consIterator")
819+
lazy val DynamicTuple_concatIterator: Symbol = DynamicTupleModule.requiredMethod("concatIterator")
820+
lazy val DynamicTuple_dynamicApply: Symbol = DynamicTupleModule.requiredMethod("dynamicApply")
821+
lazy val DynamicTuple_dynamicCons: Symbol = DynamicTupleModule.requiredMethod("dynamicCons")
822+
lazy val DynamicTuple_dynamicSize: Symbol = DynamicTupleModule.requiredMethod("dynamicSize")
823+
lazy val DynamicTuple_dynamicTail: Symbol = DynamicTupleModule.requiredMethod("dynamicTail")
824+
lazy val DynamicTuple_dynamicConcat: Symbol = DynamicTupleModule.requiredMethod("dynamicConcat")
825+
lazy val DynamicTuple_dynamicToArray: Symbol = DynamicTupleModule.requiredMethod("dynamicToArray")
826+
lazy val DynamicTuple_productToArray: Symbol = DynamicTupleModule.requiredMethod("productToArray")
806827

807828
lazy val TupledFunctionTypeRef: TypeRef = ctx.requiredClassRef("scala.TupledFunction")
808829
def TupledFunctionClass(implicit ctx: Context): ClassSymbol = TupledFunctionTypeRef.symbol.asClass

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ object StdNames {
417417
val drop: N = "drop"
418418
val dynamics: N = "dynamics"
419419
val elem: N = "elem"
420+
val elems: N = "elems"
420421
val emptyValDef: N = "emptyValDef"
421422
val ensureAccessible : N = "ensureAccessible"
422423
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
@@ -632,14 +632,18 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
632632

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

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
@@ -14,3 +16,4 @@ derive-generic.scala
1416
mixin-forwarder-overload
1517
t8905
1618
t10889
19+
i5257.scala

0 commit comments

Comments
 (0)