Skip to content

Commit ea1731a

Browse files
committed
Implement list optimisation limit
1 parent 2ac7c1c commit ea1731a

File tree

2 files changed

+82
-12
lines changed

2 files changed

+82
-12
lines changed

compiler/src/dotty/tools/dotc/transform/ArrayApply.scala

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package transform
55
import ast.tpd
66
import core.*, Contexts.*, Decorators.*, Symbols.*, Flags.*, StdNames.*
77
import reporting.trace
8+
import util.Property
89
import MegaPhase.*
910

1011
/** This phase rewrites calls to `Array.apply` to a direct instantiation of the array in the bytecode.
@@ -18,7 +19,16 @@ class ArrayApply extends MiniPhase {
1819

1920
override def description: String = ArrayApply.description
2021

21-
private val transformListApplyLimit = 8
22+
private val TransformListApplyBudgetKey = new Property.Key[Int]
23+
private def transformListApplyBudget(using Context) = ctx.property(TransformListApplyBudgetKey).getOrElse(8)
24+
25+
override def prepareForApply(tree: Apply)(using Context): Context =
26+
if isSeqApply(tree) then
27+
val args = seqApplyArgsOrNull(tree)
28+
if args != null then
29+
ctx.fresh.setProperty(TransformListApplyBudgetKey, transformListApplyBudget - args.elems.length)
30+
else ctx
31+
else ctx
2232

2333
override def transformApply(tree: Apply)(using Context): Tree =
2434
if isArrayModuleApply(tree.symbol) then
@@ -35,17 +45,12 @@ class ArrayApply extends MiniPhase {
3545
tree
3646

3747
else if isSeqApply(tree) then
38-
tree.args match
39-
// <List or Seq>(a, b, c) ~> new ::(a, new ::(b, new ::(c, Nil))) but only for reference types
40-
case StripAscription(Apply(wrapArrayMeth, List(StripAscription(rest: JavaSeqLiteral)))) :: Nil
41-
if defn.WrapArrayMethods().contains(wrapArrayMeth.symbol) &&
42-
rest.elems.lengthIs < transformListApplyLimit =>
43-
val consed = rest.elems.foldRight(ref(defn.NilModule)): (elem, acc) =>
44-
New(defn.ConsType, List(elem.ensureConforms(defn.ObjectType), acc))
45-
consed.cast(tree.tpe)
46-
47-
case _ =>
48-
tree
48+
val args = seqApplyArgsOrNull(tree)
49+
if args != null && (transformListApplyBudget > 0 || args.elems.isEmpty) then
50+
val consed = args.elems.foldRight(ref(defn.NilModule)): (elem, acc) =>
51+
New(defn.ConsType, List(elem.ensureConforms(defn.ObjectType), acc))
52+
consed.cast(tree.tpe)
53+
else tree
4954

5055
else tree
5156

@@ -70,6 +75,15 @@ class ArrayApply extends MiniPhase {
7075
|| sym == defn.CollectionSeqType.symbol.companionModule
7176
case _ => false
7277

78+
private def seqApplyArgsOrNull(tree: Apply)(using Context): JavaSeqLiteral | Null =
79+
// assumes isSeqApply(tree)
80+
tree.args match
81+
// <List or Seq>(a, b, c) ~> new ::(a, new ::(b, new ::(c, Nil))) but only for reference types
82+
case StripAscription(Apply(wrapArrayMeth, List(StripAscription(rest: JavaSeqLiteral)))) :: Nil
83+
if defn.WrapArrayMethods().contains(wrapArrayMeth.symbol) =>
84+
rest
85+
case _ => null
86+
7387
/** Only optimize when classtag if it is one of
7488
* - `ClassTag.apply(classOf[XYZ])`
7589
* - `ClassTag.apply(java.lang.XYZ.Type)` for boxed primitives `XYZ``

tests/run/list-apply-eval.scala

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,59 @@ object Test:
3131
// again, the lub of :: and Nil is Product, which breaks ++ (which requires IterableOnce)
3232
def lub2(b: Boolean): Unit =
3333
Seq(1) ++ (if (b) Seq(2) else Nil)
34+
35+
// Examples of arity and nesting arity
36+
// to find the thresholds and reproduce the behaviour of nsc
37+
// tested manually, comparing -Xprint across compilers (ran out of time)
38+
def examples(): Unit =
39+
val max1 = List[Object]("1", "2", "3", "4", "5", "6", "7") // 7 cons w/ 7 string heads + nil
40+
val max2 = List[Object]("1", "2", "3", "4", "5", "6", List[Object]()) // 7 cons w/ 6 string heads + 1 nil head + nil
41+
val max3 = List[Object]("1", "2", "3", "4", "5", List[Object]("6"))
42+
val max4 = List[Object]("1", "2", "3", "4", List[Object]("5", "6"))
43+
44+
val over1 = List[Object]("1", "2", "3", "4", "5", "6", "7", "8") // wrap 8-sized array
45+
val over2 = List[Object]("1", "2", "3", "4", "5", "6", "7", List[Object]()) // wrap 8-sized array
46+
val over3 = List[Object]("1", "2", "3", "4", "5", "6", List[Object]("7")) // wrap 1-sized array with 7
47+
val over4 = List[Object]("1", "2", "3", "4", "5", List[Object]("6", "7")) // wrap 2
48+
49+
val max5 =
50+
List[Object](
51+
List[Object](
52+
List[Object](
53+
List[Object](
54+
List[Object](
55+
List[Object](
56+
List[Object](
57+
List[Object](
58+
)))))))) // 7 cons + 1 nil
59+
60+
val over5 =
61+
List[Object](
62+
List[Object](
63+
List[Object](
64+
List[Object](
65+
List[Object](
66+
List[Object](
67+
List[Object](
68+
List[Object]( List[Object]()
69+
)))))))) // 7 cons + 1-sized array wrapping nil
70+
71+
val max6 =
72+
List[Object]( // ::(
73+
"1", "2", List[Object]( // 1, ::(2, ::(::(
74+
"3", "4", List[Object]( // 3, ::(4, ::(::(
75+
List[Object]() // Nil, Nil
76+
) // ), Nil))
77+
) // ), Nil))
78+
) // )
79+
// 7 cons + 4 string heads + 4 nils for nested lists
80+
81+
val max7 =
82+
List[Object]( // ::(
83+
"1", "2", List[Object]( // 1, ::(2, ::(::(
84+
"3", "4", List[Object]( // 3, ::(4, ::(::(
85+
"5" // 5, Nil
86+
) // ), Nil))
87+
) // ), Nil))
88+
) // )
89+
// 7 cons + 5 string heads + 3 nils for nested lists

0 commit comments

Comments
 (0)