Skip to content

Implement Tuple operation directly in the compiler #6539

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 6, 2019
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class Compiler {
new AugmentScala2Traits, // Augments Scala2 traits with additional members needed for mixin composition.
new ResolveSuper, // Implement super accessors
new FunctionXXLForwarders, // Add forwarders for FunctionXXL apply method
new TupleOptimizations, // Optimize generic operations on tuples
new ArrayConstructors) :: // Intercept creation of (non-generic) arrays and intrinsify.
List(new Erasure) :: // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements.
List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types
Expand Down
21 changes: 21 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,10 @@ class Definitions {
lazy val Product_productPrefixR: TermRef = ProductClass.requiredMethodRef(nme.productPrefix)
def Product_productPrefix(implicit ctx: Context): Symbol = Product_productPrefixR.symbol

lazy val IteratorType: TypeRef = ctx.requiredClassRef("scala.collection.Iterator")
def IteratorClass(implicit ctx: Context): ClassSymbol = IteratorType.symbol.asClass
def IteratorModule(implicit ctx: Context): Symbol = IteratorClass.companionModule

lazy val ModuleSerializationProxyType: TypeRef = ctx.requiredClassRef("scala.runtime.ModuleSerializationProxy")
def ModuleSerializationProxyClass(implicit ctx: Context): ClassSymbol = ModuleSerializationProxyType.symbol.asClass
lazy val ModuleSerializationProxyConstructor: TermSymbol =
Expand Down Expand Up @@ -792,8 +796,12 @@ class Definitions {

lazy val TupleTypeRef: TypeRef = ctx.requiredClassRef("scala.Tuple")
def TupleClass(implicit ctx: Context): ClassSymbol = TupleTypeRef.symbol.asClass
lazy val Tuple_consR: TermRef = TupleClass.requiredMethod("*:").termRef
def Tuple_cons: Symbol = Tuple_consR.symbol
lazy val NonEmptyTupleTypeRef: TypeRef = ctx.requiredClassRef("scala.NonEmptyTuple")
def NonEmptyTupleClass(implicit ctx: Context): ClassSymbol = NonEmptyTupleTypeRef.symbol.asClass
lazy val NonEmptyTuple_tailR: TermRef = NonEmptyTupleClass.requiredMethod("tail").termRef
def NonEmptyTuple_tail: Symbol = NonEmptyTuple_tailR.symbol

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

def TupleXXL_apply(implicit ctx: Context): Symbol =
TupleXXLModule.info.member(nme.apply).requiredSymbol("method", nme.apply, TupleXXLModule)(_.info.isVarArgsMethod)
def TupleXXL_fromIterator(implicit ctx: Context): Symbol = TupleXXLModule.requiredMethod("fromIterator")

lazy val DynamicTupleModule: Symbol = ctx.requiredModule("scala.runtime.DynamicTuple")
lazy val DynamicTupleModuleClass: Symbol = DynamicTupleModule.moduleClass
lazy val DynamicTuple_consIterator: Symbol = DynamicTupleModule.requiredMethod("consIterator")
lazy val DynamicTuple_concatIterator: Symbol = DynamicTupleModule.requiredMethod("concatIterator")
lazy val DynamicTuple_dynamicApply: Symbol = DynamicTupleModule.requiredMethod("dynamicApply")
lazy val DynamicTuple_dynamicCons: Symbol = DynamicTupleModule.requiredMethod("dynamicCons")
lazy val DynamicTuple_dynamicSize: Symbol = DynamicTupleModule.requiredMethod("dynamicSize")
lazy val DynamicTuple_dynamicTail: Symbol = DynamicTupleModule.requiredMethod("dynamicTail")
lazy val DynamicTuple_dynamicConcat: Symbol = DynamicTupleModule.requiredMethod("dynamicConcat")
lazy val DynamicTuple_dynamicToArray: Symbol = DynamicTupleModule.requiredMethod("dynamicToArray")
lazy val DynamicTuple_productToArray: Symbol = DynamicTupleModule.requiredMethod("productToArray")

lazy val TupledFunctionTypeRef: TypeRef = ctx.requiredClassRef("scala.TupledFunction")
def TupledFunctionClass(implicit ctx: Context): ClassSymbol = TupledFunctionTypeRef.symbol.asClass
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ object StdNames {
val drop: N = "drop"
val dynamics: N = "dynamics"
val elem: N = "elem"
val elems: N = "elems"
val emptyValDef: N = "emptyValDef"
val ensureAccessible : N = "ensureAccessible"
val enumTag: N = "enumTag"
Expand Down
10 changes: 7 additions & 3 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -632,14 +632,18 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {

if (ctx.settings.XprintTypes.value && tree.hasType) {
// add type to term nodes; replace type nodes with their types unless -Yprint-pos is also set.
def tp = tree.typeOpt match {
val tp1 = tree.typeOpt match {
case tp: TermRef if tree.isInstanceOf[RefTree] && !tp.denot.isOverloaded => tp.underlying
case tp => tp
}
val tp2 = {
val tp = tp1.tryNormalize
if (tp != NoType) tp else tp1
}
if (!suppressTypes)
txt = ("<" ~ txt ~ ":" ~ toText(tp) ~ ">").close
txt = ("<" ~ txt ~ ":" ~ toText(tp2) ~ ">").close
else if (tree.isType && !homogenizedView)
txt = toText(tp)
txt = toText(tp2)
}
if (!suppressPositions) {
if (printPos) {
Expand Down
239 changes: 239 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/TupleOptimizations.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package dotty.tools.dotc
package transform

import core._
import Constants.Constant
import Contexts.Context
import Decorators._
import Flags._
import ast.Trees._
import Definitions._
import DenotTransformers._
import StdNames._
import Symbols._
import MegaPhase._
import Types._
import dotty.tools.dotc.ast.tpd

import scala.annotation.tailrec

/** Optimize generic operations on tuples */
class TupleOptimizations extends MiniPhase with IdentityDenotTransformer {
import tpd._

def phaseName: String = "genericTuples"

override def transformApply(tree: tpd.Apply)(implicit ctx: Context): tpd.Tree = {
if (!tree.symbol.exists || tree.symbol.owner != defn.DynamicTupleModuleClass) tree
else if (tree.symbol == defn.DynamicTuple_dynamicCons) transformTupleCons(tree)
else if (tree.symbol == defn.DynamicTuple_dynamicTail) transformTupleTail(tree)
else if (tree.symbol == defn.DynamicTuple_dynamicSize) transformTupleSize(tree)
else if (tree.symbol == defn.DynamicTuple_dynamicConcat) transformTupleConcat(tree)
else if (tree.symbol == defn.DynamicTuple_dynamicApply) transformTupleApply(tree)
else if (tree.symbol == defn.DynamicTuple_dynamicToArray) transformTupleToArray(tree)
else tree
}

private def transformTupleCons(tree: tpd.Apply)(implicit ctx: Context): Tree = {
val head :: tail :: Nil = tree.args
tupleTypes(tree.tpe) match {
case Some(tpes) =>
// Generate a the tuple directly with TupleN+1.apply
val size = tpes.size
if (size <= 5) {
// val t = tail
// TupleN+1(head, t._1, ..., t._n)
evalOnce(Typed(tail, TypeTree(defn.tupleType(tpes.tail)))) { tup =>
val elements = head :: tupleSelectors(tup, size - 1)
knownTupleFromElements(tpes, elements)
}
} else {
// val it = Iterator.single(head) ++ tail.asInstanceOf[Product].productIterator
// TupleN+1(it.next(), ..., it.next())
val fullIterator = ref(defn.DynamicTuple_consIterator).appliedToArgs(head :: tail :: Nil)
evalOnce(fullIterator) { it =>
knownTupleFromIterator(tpes.length, it).asInstance(tree.tpe)
}
}
case _ =>
// No optimization, keep:
// DynamicTuple.dynamicCons:(tail, head)
tree
}
}

private def transformTupleTail(tree: tpd.Apply)(implicit ctx: Context): Tree = {
val Apply(TypeApply(_, tpt :: Nil), tup :: Nil) = tree
tupleTypes(tpt.tpe, MaxTupleArity + 1) match {
case Some(tpes) =>
// Generate a the tuple directly with TupleN-1.apply
val size = tpes.size
assert(size > 0)
if (size == 1) {
// ()
Literal(Constant(()))
}
else if (size <= 5) {
// val t = tup.asInstanceOf[TupleN[...]]
// TupleN-1(t._2, ..., t._n)
evalOnce(Typed(tup, TypeTree(defn.tupleType(tpes)))) { tup =>
val elements = tupleSelectors(tup, size).tail
knownTupleFromElements(tpes.tail, elements)
}
} else if (size <= MaxTupleArity + 1) {
// val it = this.asInstanceOf[Product].productIterator
// it.next()
// TupleN-1(it.next(), ..., it.next())
evalOnce(tup.asInstance(defn.ProductType).select(nme.productIterator)) { it =>
Block(
it.select(nme.next).ensureApplied :: Nil,
knownTupleFromIterator(size - 1, it).asInstance(tree.tpe)
)
}
} else {
// tup.asInstanceOf[TupleXXL].tailXXL
tup.asInstance(defn.TupleXXLType).select("tailXXL".toTermName)
}
case None =>
// No optimization, keep:
// DynamicTuple.dynamicTail(tup)
tree
}
}

private def transformTupleSize(tree: tpd.Apply)(implicit ctx: Context): Tree = {
tree.tpe.tryNormalize match {
case tp: ConstantType => Literal(tp.value)
case _ => tree
}
}

private def transformTupleConcat(tree: tpd.Apply)(implicit ctx: Context): Tree = {
val Apply(TypeApply(_, selfTp :: thatTp :: Nil), self :: that :: Nil) = tree

(tupleTypes(selfTp.tpe), tupleTypes(that.tpe.widenTermRefExpr)) match {
case (Some(tpes1), Some(tpes2)) =>
// Generate a the tuple directly with TupleN+M.apply
val n = tpes1.size
val m = tpes2.size
if (n == 0) that
else if (m == 0) self
else if (n + m < 5) {
// val t = self
// val u = that
// TupleN+M(t._1,..., t._N, u._1, ..., u._M)
evalOnce(Typed(self, TypeTree(defn.tupleType(tpes1)))) { self =>
evalOnce(Typed(that, TypeTree(defn.tupleType(tpes2)))) { that =>
val types = tpes1 ::: tpes2
val elements = tupleSelectors(self, n) ::: tupleSelectors(that, m)
knownTupleFromElements(types, elements)
}
}
} else {
// val it = self.asInstanceOf[Product].productIterator ++ that.asInstanceOf[Product].productIterator
// TupleN+M(it.next(), ..., it.next())
val fullIterator = ref(defn.DynamicTuple_concatIterator).appliedToArgs(tree.args)
evalOnce(fullIterator) { it =>
knownTupleFromIterator(n + m, it).asInstance(tree.tpe)
}
}
case _ =>
// No optimization, keep:
// DynamicTuple.dynamicCons[This, that.type](self, that)
tree
}
}

private def transformTupleApply(tree: tpd.Apply)(implicit ctx: Context): Tree = {
val Apply(TypeApply(_, tpt :: nTpt :: Nil), tup :: nTree :: Nil) = tree
(tupleTypes(tpt.tpe), nTpt.tpe) match {
case (Some(tpes), nTpe: ConstantType) =>
// Get the element directly with TupleM._n+1 or TupleXXL.productElement(n)
val size = tpes.size
val n = nTpe.value.intValue
if (n < 0 || n >= size) {
ctx.error("index out of bounds: " + n, nTree.underlyingArgument.sourcePos)
tree
} else if (size <= MaxTupleArity) {
// tup._n
Typed(tup, TypeTree(defn.tupleType(tpes))).select(nme.selectorName(n))
} else {
// tup.asInstanceOf[TupleXXL].productElement(n)
tup.asInstance(defn.TupleXXLType).select(nme.productElement).appliedTo(Literal(nTpe.value))
}
case (None, nTpe: ConstantType) if nTpe.value.intValue < 0 =>
ctx.error("index out of bounds: " + nTpe.value.intValue, nTree.sourcePos)
tree
case _ =>
// No optimization, keep:
// DynamicTuple.dynamicApply(tup, n)
tree
}
}

private def transformTupleToArray(tree: tpd.Apply)(implicit ctx: Context): Tree = {
val Apply(_, tup :: Nil) = tree
tupleTypes(tup.tpe.widen, MaxTupleArity) match {
case Some(tpes) =>
val size = tpes.size
if (size == 0) {
// Array.emptyObjectArray
ref(defn.ArrayModule.companionModule).select("emptyObjectArray".toTermName).ensureApplied
} else if (size <= MaxTupleArity) {
// DynamicTuple.productToArray(tup.asInstanceOf[Product])
ref(defn.DynamicTuple_productToArray).appliedTo(tup.asInstance(defn.ProductType))
} else {
// tup.asInstanceOf[TupleXXL].elems.clone()
tup.asInstance(defn.TupleXXLType).select(nme.toArray)
}
case None =>
// No optimization, keep:
// DynamicTuple.dynamicToArray(tup)
tree
}
}

/** Create a TupleN (1 <= N < 23) from the elements */
private def knownTupleFromElements(tpes: List[Type], elements: List[Tree])(implicit ctx: Context) = {
val size = elements.size
assert(0 < size && size <= MaxTupleArity)
val tupleModule = defn.TupleType(size).classSymbol.companionModule
ref(tupleModule).select(nme.apply).appliedToTypes(tpes).appliedToArgs(elements)
}

private def knownTupleFromIterator(size: Int, it: Tree)(implicit ctx: Context): Tree = {
if (size == 0) {
// Unit for empty tuple
Literal(Constant(())) // TODO should this code be here? Or assert(size > specializedSize)
}
else if (size <= MaxTupleArity) {
// TupleN(it.next(), ..., it.next())

// TODO outline this code for the 22 alternatives (or less, may not need the smallest ones)?
// This would yield smaller bytecode at the cost of an extra (easily JIT inlinable) call.
// def dynamicTupleN(it: Iterator[Any]): TupleN[Any, ..., Any] = Tuple(it.next(), ..., it.next())
val tpes = List.fill(size)(defn.AnyType)
val elements = (0 until size).map(_ => it.select(nme.next)).toList
knownTupleFromElements(tpes, elements)
} else {
// No optimization, keep:
// TupleXXL.fromIterator(it)
ref(defn.TupleXXL_fromIterator).appliedTo(it)
}
}

private def tupleTypes(tp: Type, bound: Int = Int.MaxValue)(implicit ctx: Context): Option[List[Type]] = {
@tailrec def rec(tp: Type, acc: List[Type], bound: Int): Option[List[Type]] = tp match {
case _ if bound < 0 => Some(acc.reverse)
case tp: AppliedType if defn.PairClass == tp.classSymbol => rec(tp.args(1), tp.args.head :: acc, bound - 1)
case tp: AppliedType if defn.isTupleClass(tp.tycon.classSymbol) => Some(acc.reverse ::: tp.args)
case tp if tp.classSymbol == defn.UnitClass => Some(acc.reverse)
case _ => None
}
rec(tp.stripTypeVar, Nil, bound)
}

private def tupleSelectors(tup: Tree, size: Int)(implicit ctx: Context): List[Tree] =
(0 until size).map(i => tup.select(nme.selectorName(i))).toList

}
3 changes: 3 additions & 0 deletions compiler/test/dotc/run-test-pickling.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ t3452e
t3452g
t7374
tuples1.scala
tuples1a.scala
tuples1b.scala
typeclass-derivation1.scala
typeclass-derivation2.scala
typeclass-derivation2a.scala
Expand All @@ -14,3 +16,4 @@ derive-generic.scala
mixin-forwarder-overload
t8905
t10889
i5257.scala
Loading