Skip to content

Commit 3a06f0a

Browse files
InlineLocalObjects: Refactoring and formatting
1 parent 618f826 commit 3a06f0a

File tree

2 files changed

+97
-54
lines changed

2 files changed

+97
-54
lines changed

compiler/src/dotty/tools/dotc/transform/localopt/InlineLocalObjects.scala

Lines changed: 96 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package transform.localopt
44
import core.Constants.Constant
55
import core.Contexts.Context
66
import core.Decorators._
7+
import core.Names.Name
8+
import core.Types.Type
79
import core.NameOps._
810
import core.StdNames._
911
import core.Symbols._
@@ -13,46 +15,67 @@ import scala.collection.mutable
1315
import transform.SymUtils._
1416
import config.Printers.simplify
1517

16-
/** Inline case classes as vals, this essentially (local) implements multi
17-
* parameter value classes. The main motivation is to get ride of all the
18-
* intermediate tuples coming from pattern matching expressions.
18+
/** Inline case classes as vals.
19+
*
20+
* In other words, implements (local) multi parameter value classes. The main
21+
* motivation is to get ride of all the intermediate tuples created by the
22+
* pattern matcher.
1923
*/
2024
class InlineLocalObjects extends Optimisation {
2125
import ast.tpd._
2226

2327
// In the end only calls constructor. Reason for unconditional inlining
2428
val hasPerfectRHS = mutable.HashMap[Symbol, Boolean]()
29+
2530
// If all values have perfect RHS than key has perfect RHS
2631
val checkGood = mutable.HashMap[Symbol, Set[Symbol]]()
32+
2733
val forwarderWritesTo = mutable.HashMap[Symbol, Symbol]()
34+
2835
val gettersCalled = mutable.HashSet[Symbol]()
2936

37+
def symbolAccessors(s: Symbol)(implicit ctx: Context): List[Symbol] = {
38+
val accessors = s.info.classSymbol.caseAccessors.filter(_.isGetter)
39+
if (accessors.isEmpty)
40+
s.info.classSymbol.caseAccessors
41+
else accessors
42+
}
43+
3044
def followTailPerfect(t: Tree, symbol: Symbol)(implicit ctx: Context): Unit = {
3145
t match {
32-
case Block(_, expr) => followTailPerfect(expr, symbol)
33-
case If(_, thenp, elsep) => followTailPerfect(thenp, symbol); followTailPerfect(elsep, symbol);
46+
case Block(_, expr) =>
47+
followTailPerfect(expr, symbol)
48+
49+
case If(_, thenp, elsep) =>
50+
followTailPerfect(thenp, symbol)
51+
followTailPerfect(elsep, symbol)
52+
3453
case Apply(fun, _) if fun.symbol.isConstructor && t.tpe.widenDealias == symbol.info.widenDealias.finalResultType.widenDealias =>
3554
hasPerfectRHS(symbol) = true
55+
3656
case Apply(fun, _) if fun.symbol.is(Label) && (fun.symbol ne symbol) =>
3757
checkGood.put(symbol, checkGood.getOrElse(symbol, Set.empty) + fun.symbol)
3858
// assert(forwarderWritesTo.getOrElse(t.symbol, symbol) == symbol)
3959
forwarderWritesTo(t.symbol) = symbol
60+
4061
case t: Ident if !t.symbol.owner.isClass && (t.symbol ne symbol) =>
4162
checkGood.put(symbol, checkGood.getOrElse(symbol, Set.empty) + t.symbol)
63+
4264
case _ =>
4365
}
4466
}
4567

4668
def visitor(implicit ctx: Context): Tree => Unit = {
47-
case vdef: ValDef if (vdef.symbol.info.classSymbol is CaseClass) &&
48-
!vdef.symbol.is(Lazy) &&
49-
!vdef.symbol.info.classSymbol.caseAccessors.exists(x => x.is(Mutable)) =>
50-
followTailPerfect(vdef.rhs, vdef.symbol)
69+
case t: ValDef if (t.symbol.info.classSymbol is CaseClass) &&
70+
!t.symbol.is(Lazy) &&
71+
!t.symbol.info.classSymbol.caseAccessors.exists(_.is(Mutable)) =>
72+
followTailPerfect(t.rhs, t.symbol)
5173

5274
case Assign(lhs, rhs) if !lhs.symbol.owner.isClass =>
5375
checkGood.put(lhs.symbol, checkGood.getOrElse(lhs.symbol, Set.empty) + rhs.symbol)
5476

55-
case t @ Select(qual, _) if (t.symbol.isGetter && !t.symbol.is(Mutable)) ||
77+
case t @ Select(qual, _) if
78+
(t.symbol.isGetter && !t.symbol.is(Mutable | Lazy)) ||
5679
(t.symbol.maybeOwner.derivesFrom(defn.ProductClass) && t.symbol.maybeOwner.is(CaseClass) && t.symbol.name.isSelectorName) ||
5780
(t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) =>
5881
gettersCalled(qual.symbol) = true
@@ -65,9 +88,9 @@ class InlineLocalObjects extends Optimisation {
6588

6689
def transformer(implicit ctx: Context): Tree => Tree = {
6790
var hasChanged = true
68-
while(hasChanged) {
91+
while (hasChanged) {
6992
hasChanged = false
70-
checkGood.foreach{case (key, values) =>
93+
checkGood.foreach { case (key, values) =>
7194
values.foreach { value =>
7295
if (hasPerfectRHS.getOrElse(key, false)) {
7396
hasChanged = !hasPerfectRHS.put(value, true).getOrElse(false)
@@ -77,54 +100,70 @@ class InlineLocalObjects extends Optimisation {
77100
}
78101

79102
val newMappings: Map[Symbol, Map[Symbol, Symbol]] =
80-
hasPerfectRHS.iterator.map(x => x._1).filter(x => !x.is(Method) && !x.is(Label) && gettersCalled.contains(x.symbol) && (x.symbol.info.classSymbol is CaseClass))
81-
.map { refVal =>
82-
simplify.println(s"replacing ${refVal.symbol.fullName} with stack-allocated fields")
83-
var accessors = refVal.info.classSymbol.caseAccessors.filter(_.isGetter) // TODO: drop mutable ones
84-
if (accessors.isEmpty) accessors = refVal.info.classSymbol.caseAccessors
85-
val productAccessors = (1 to accessors.length).map(i => refVal.info.member(nme.productAccessorName(i)).symbol) // TODO: disambiguate
86-
val newLocals = accessors.map(x =>
87-
// TODO: it would be nice to have an additional optimisation that
88-
// TODO: is capable of turning those mutable ones into immutable in common cases
89-
ctx.newSymbol(ctx.owner.enclosingMethod, (refVal.name + "$" + x.name).toTermName, Synthetic | Mutable, x.asSeenFrom(refVal.info).info.finalResultType.widenDealias)
90-
)
91-
val fieldMapping = accessors zip newLocals
92-
val productMappings = productAccessors zip newLocals
93-
(refVal, (fieldMapping ++ productMappings).toMap)
94-
}.toMap
103+
hasPerfectRHS.iterator
104+
.map(_._1)
105+
.filter(x => !x.is(Method | Label) && gettersCalled.contains(x.symbol) && x.symbol.info.classSymbol.is(CaseClass))
106+
.map { refVal =>
107+
simplify.println(s"replacing ${refVal.symbol.fullName} with stack-allocated fields")
108+
var accessors = refVal.info.classSymbol.caseAccessors.filter(_.isGetter) // TODO: drop mutable ones
109+
if (accessors.isEmpty) accessors = refVal.info.classSymbol.caseAccessors
110+
111+
val productAccessors = (1 to accessors.length).map { i =>
112+
refVal.info.member(nme.productAccessorName(i)).symbol
113+
} // TODO: disambiguate
114+
115+
val newLocals = accessors.map { x =>
116+
// TODO: it would be nice to have an additional optimisation that
117+
// TODO: is capable of turning those mutable ones into immutable in common cases
118+
val owner: Symbol = ctx.owner.enclosingMethod
119+
val name: Name = (refVal.name + "$" + x.name).toTermName
120+
val flags: FlagSet = Synthetic | Mutable
121+
val info: Type = x.asSeenFrom(refVal.info).info.finalResultType.widenDealias
122+
ctx.newSymbol(owner, name, flags, info)
123+
}
124+
val fieldMapping = accessors zip newLocals
125+
val productMappings = productAccessors zip newLocals
126+
(refVal, (fieldMapping ++ productMappings).toMap)
127+
}.toMap
128+
95129
val toSplit: mutable.Set[Symbol] = mutable.Set.empty ++ newMappings.keySet
96130

97131
def splitWrites(t: Tree, target: Symbol): Tree = {
98132
t match {
99-
case tree@ Block(stats, expr) => cpy.Block(tree)(stats, splitWrites(expr, target))
100-
case tree@ If(_, thenp, elsep) => cpy.If(tree)(thenp = splitWrites(thenp, target), elsep = splitWrites(elsep, target))
101-
case Apply(sel , args) if sel.symbol.isConstructor && t.tpe.widenDealias == target.info.widenDealias.finalResultType.widenDealias =>
133+
case tree @ Block(stats, expr) =>
134+
cpy.Block(tree)(stats, splitWrites(expr, target))
135+
136+
case tree @ If(_, thenp, elsep) =>
137+
cpy.If(tree)(thenp = splitWrites(thenp, target), elsep = splitWrites(elsep, target))
138+
139+
case Apply(sel, args) if sel.symbol.isConstructor && t.tpe.widenDealias == target.info.widenDealias.finalResultType.widenDealias =>
102140
val fieldsByAccessors = newMappings(target)
103-
var accessors = target.info.classSymbol.caseAccessors.filter(_.isGetter) // TODO: when is this filter needed?
104-
if (accessors.isEmpty) accessors = target.info.classSymbol.caseAccessors
105-
val assigns = (accessors zip args) map (x => ref(fieldsByAccessors(x._1)).becomes(x._2))
141+
var accessors = symbolAccessors(target)
142+
val assigns = (accessors zip args).map(x => ref(fieldsByAccessors(x._1)).becomes(x._2))
106143
val recreate = sel.appliedToArgs(accessors.map(x => ref(fieldsByAccessors(x))))
107144
Block(assigns, recreate)
145+
108146
case Apply(fun, _) if fun.symbol.is(Label) =>
109147
t // Do nothing. It will do on its own.
148+
110149
case t: Ident if !t.symbol.owner.isClass && newMappings.contains(t.symbol) && t.symbol.info.classSymbol == target.info.classSymbol =>
111150
val fieldsByAccessorslhs = newMappings(target)
112151
val fieldsByAccessorsrhs = newMappings(t.symbol)
113-
val accessors = target.info.classSymbol.caseAccessors.filter(_.isGetter)
114-
val assigns = accessors map (x => ref(fieldsByAccessorslhs(x)).becomes(ref(fieldsByAccessorsrhs(x))))
152+
val accessors = symbolAccessors(target)
153+
val assigns = accessors.map(x => ref(fieldsByAccessorslhs(x)).becomes(ref(fieldsByAccessorsrhs(x))))
115154
Block(assigns, t)
116-
// If `t` is itself split, push writes.
155+
// If `t` is itself split, push writes.
156+
117157
case _ =>
118158
evalOnce(t){ev =>
119159
if (ev.tpe.derivesFrom(defn.NothingClass)) ev
120160
else {
121161
val fieldsByAccessors = newMappings(target)
122-
val accessors = target.info.classSymbol.caseAccessors.filter(_.isGetter)
123-
val assigns = accessors map (x => ref(fieldsByAccessors(x)).becomes(ev.select(x)))
162+
val accessors = symbolAccessors(target)
163+
val assigns = accessors.map(x => ref(fieldsByAccessors(x)).becomes(ev.select(x)))
124164
Block(assigns, ev)
125165
}
126166
} // Need to eval-once and update fields.
127-
128167
}
129168
}
130169

@@ -142,33 +181,37 @@ class InlineLocalObjects extends Optimisation {
142181
gettersCalled.clear()
143182

144183
val res: Tree => Tree = {
145-
case ddef: DefDef if ddef.symbol.is(Label) =>
146-
newMappings.get(followCases(ddef.symbol)) match {
184+
case t: DefDef if t.symbol.is(Label) =>
185+
newMappings.get(followCases(t.symbol)) match {
147186
case Some(mappings) =>
148-
cpy.DefDef(ddef)(rhs = splitWrites(ddef.rhs, followCases(ddef.symbol)))
149-
case _ => ddef
187+
cpy.DefDef(t)(rhs = splitWrites(t.rhs, followCases(t.symbol)))
188+
case _ => t
150189
}
151-
case a: ValDef if toSplit.contains(a.symbol) =>
152-
toSplit -= a.symbol
190+
191+
case t: ValDef if toSplit.contains(t.symbol) =>
192+
toSplit -= t.symbol
153193
// Break ValDef apart into fields + boxed value
154-
val newFields = newMappings(a.symbol).values.toSet
194+
val newFields = newMappings(t.symbol).values.toSet
155195
Thicket(
156196
newFields.map(x => ValDef(x.asTerm, defaultValue(x.symbol.info.widenDealias))).toList :::
157-
List(cpy.ValDef(a)(rhs = splitWrites(a.rhs, a.symbol))))
158-
case ass: Assign =>
159-
newMappings.get(ass.lhs.symbol) match {
160-
case None => ass
197+
List(cpy.ValDef(t)(rhs = splitWrites(t.rhs, t.symbol))))
198+
199+
case t: Assign =>
200+
newMappings.get(t.lhs.symbol) match {
201+
case None => t
161202
case Some(mapping) =>
162-
val updates = mapping.filter(x => x._1.is(CaseAccessor)).map(x => ref(x._2).becomes(ref(ass.lhs.symbol).select(x._1))).toList
163-
Thicket(ass :: updates)
203+
val updates = mapping.filter(x => x._1.is(CaseAccessor)).map(x => ref(x._2).becomes(ref(t.lhs.symbol).select(x._1))).toList
204+
Thicket(t :: updates)
164205
}
165-
case t @ Select(rec, _) if (t.symbol.isGetter && !t.symbol.is(Mutable)) ||
206+
207+
case t @ Select(rec, _) if (t.symbol.isGetter && !t.symbol.is(Mutable | Lazy)) ||
166208
(t.symbol.maybeOwner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) ||
167209
(t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) =>
168210
newMappings.getOrElse(rec.symbol, Map.empty).get(t.symbol) match {
169211
case None => t
170212
case Some(newSym) => ref(newSym)
171213
}
214+
172215
case t => t
173216
}
174217
res

compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer {
4747
new Jumpjump ::
4848
new DropGoodCasts ::
4949
new DropNoEffects(this) ::
50-
// new InlineLocalObjects :: // followCases needs to be fixed, see ./tests/pos/rbtree.scala
50+
new InlineLocalObjects :: // followCases needs to be fixed, see ./tests/pos/rbtree.scala
5151
// new Varify :: // varify could stop other transformations from being applied. postponed.
5252
// new BubbleUpNothing ::
5353
new ConstantFold(this) ::

0 commit comments

Comments
 (0)