-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
nicolasstucki
merged 11 commits into
scala:master
from
dotty-staging:tuple-codegen-in-compiler
Jun 6, 2019
Merged
Changes from 10 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
1ee6602
Improve performance of generic tuples code generation
nicolasstucki ce50d3b
Avoid recomputing tuple size from type
nicolasstucki 7666e03
Implement Tuple operation directly in the compiler
nicolasstucki 2d9e6b9
Implement Tuple.head as Tuple.apply(0)
nicolasstucki a098e32
Add more documentation
nicolasstucki 3ff85db
Bound tuple sizes when possible
nicolasstucki d2bc81f
Optimize some operations on TupleXXL
nicolasstucki b3b3fb0
Add documentation
nicolasstucki c4fb0ac
Add missing bounds
nicolasstucki 950ead1
Improve tuple specialization code
nicolasstucki e5b880f
Return tree directly
nicolasstucki File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
239 changes: 239 additions & 0 deletions
239
compiler/src/dotty/tools/dotc/transform/TupleOptimizations.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) super.transformApply(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 super.transformApply(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]] = { | ||
odersky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@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 | ||
|
||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.