Skip to content

Commit 0ed8e0a

Browse files
DropNoEffects: comments and formating
1 parent 257954c commit 0ed8e0a

File tree

2 files changed

+115
-96
lines changed

2 files changed

+115
-96
lines changed

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

Lines changed: 114 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import core.Flags._
1010
import ast.Trees._
1111
import Simplify.desugarIdent
1212

13-
/** Removes side effect free statements in blocks.
13+
/** Removes side effect free statements in blocks and Defdef.
1414
* Note: BoxedUnit currently messes up this phase when run after erasure
1515
*/
1616
class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) extends Optimisation {
@@ -19,120 +19,137 @@ class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) exte
1919
val visitor = NoVisitor
2020

2121
def transformer(localCtx: Context): Tree => Tree = {
22+
// Remove empty blocks
2223
case Block(Nil, expr) => expr
24+
25+
// Keep only side effect free statements in blocks
2326
case a: Block =>
2427
val newStats0 = a.stats.mapConserve(keepOnlySideEffects)
28+
29+
// Flatten nested blocks
2530
val newStats1 = if (newStats0 eq a.stats) newStats0 else newStats0.flatMap {
26-
case x: Block => x.stats ::: List(x.expr)
31+
case x: Block => x.stats ::: List(x.expr)
2732
case EmptyTree => Nil
2833
case t => t :: Nil
2934
}
3035
val (newStats2, newExpr) = a.expr match {
3136
case Block(stats2, expr) => (newStats1 ++ stats2, expr)
3237
case _ => (newStats1, a.expr)
3338
}
39+
3440
if (newStats2.nonEmpty)
3541
cpy.Block(a)(stats = newStats2, newExpr)
3642
else newExpr
37-
case a: DefDef =>
38-
if (a.symbol.info.finalResultType.derivesFrom(defn.UnitClass) &&
39-
!a.rhs.tpe.derivesFrom(defn.UnitClass) &&
40-
!a.rhs.tpe.derivesFrom(defn.NothingClass)) {
41-
def insertUnit(t: Tree) = {
42-
if (!t.tpe.derivesFrom(defn.UnitClass)) Block(t :: Nil, unitLiteral)
43-
else t
44-
}
45-
cpy.DefDef(a)(rhs = insertUnit(keepOnlySideEffects(a.rhs)), tpt = TypeTree(defn.UnitType))
46-
} else a
43+
44+
// Keep only side effect free statements unit returning functions
45+
case a: DefDef if (a.symbol.info.finalResultType.derivesFrom(defn.UnitClass) &&
46+
!a.rhs.tpe.derivesFrom(defn.UnitClass) &&
47+
!a.rhs.tpe.derivesFrom(defn.NothingClass)) =>
48+
def insertUnit(t: Tree) = {
49+
if (!t.tpe.derivesFrom(defn.UnitClass)) Block(t :: Nil, unitLiteral)
50+
else t
51+
}
52+
cpy.DefDef(a)(rhs = insertUnit(keepOnlySideEffects(a.rhs)), tpt = TypeTree(defn.UnitType))
53+
4754
case t => t
4855
}
4956

50-
def keepOnlySideEffects(t: Tree): Tree = {
51-
t match {
52-
case l: Literal =>
53-
EmptyTree
54-
case t: This =>
55-
EmptyTree
56-
case Typed(exp, tpe) =>
57-
keepOnlySideEffects(exp)
58-
case t @ If(cond, thenp, elsep) =>
59-
val nthenp = keepOnlySideEffects(thenp)
60-
val nelsep = keepOnlySideEffects(elsep)
61-
if (thenp.isEmpty && elsep.isEmpty) keepOnlySideEffects(cond)
62-
else cpy.If(t)(
63-
thenp = nthenp.orElse(if (thenp.isInstanceOf[Literal]) thenp else unitLiteral),
64-
elsep = nelsep.orElse(if (elsep.isInstanceOf[Literal]) elsep else unitLiteral))
65-
case Select(rec, _) if
66-
(t.symbol.isGetter && !t.symbol.is(Mutable | Lazy)) ||
67-
(t.symbol.owner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) ||
68-
(t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) =>
69-
keepOnlySideEffects(rec) // Accessing a field of a product
70-
case s @ Select(qual, name) if
71-
// !name.eq(nme.TYPE_) && // Keep the .TYPE added by ClassOf, would be needed for AfterErasure
72-
!t.symbol.is(Mutable | Lazy) && !t.symbol.is(Method) =>
73-
keepOnlySideEffects(qual)
74-
case Block(List(t: DefDef), s: Closure) =>
75-
EmptyTree
76-
case bl @ Block(stats, expr) =>
77-
val stats1 = stats.mapConserve(keepOnlySideEffects)
78-
val stats2 = if (stats1 ne stats) stats1.filter(x=>x ne EmptyTree) else stats1
79-
val expr2: Tree = expr match {
80-
case t: Literal if t.tpe.derivesFrom(defn.UnitClass) => expr
81-
case _ => keepOnlySideEffects(expr).orElse(unitLiteral)
82-
}
83-
cpy.Block(bl)(stats2, expr2)
84-
case t: Ident if !t.symbol.is(Method | Lazy) && !t.symbol.info.isInstanceOf[ExprType] || effectsDontEscape(t) =>
85-
desugarIdent(t) match {
86-
case Some(t) if !(t.qualifier.symbol.is(JavaDefined) && t.qualifier.symbol.is(Package)) => t
87-
case _ => EmptyTree
88-
}
89-
case app: Apply if app.fun.symbol.is(Label) && !app.tpe.finalResultType.derivesFrom(defn.UnitClass) =>
90-
// This is "the scary hack". It changes the return type to Unit, then
91-
// invalidates the denotation cache. Because this optimisation only
92-
// operates locally, this should be fine.
93-
val denot = app.fun.symbol.denot
94-
if (!denot.info.finalResultType.derivesFrom(defn.UnitClass)) {
95-
val newLabelType = app.symbol.info match {
96-
case mt: MethodType =>
97-
mt.derivedLambdaType(mt.paramNames, mt.paramInfos, defn.UnitType)
98-
case et: ExprType =>
99-
et.derivedExprType(defn.UnitType)
100-
}
101-
val newD = app.symbol.asSymDenotation.copySymDenotation(info = newLabelType)
102-
newD.installAfter(simplifyPhase)
103-
}
57+
val keepOnlySideEffects: Tree => Tree = {
58+
case l: Literal =>
59+
EmptyTree
10460

105-
ref(app.symbol).appliedToArgs(app.args)
106-
case t @ Apply(fun, _) if effectsDontEscape(t) =>
107-
def getArgsss(a: Tree): List[Tree] = a match {
108-
case a: Apply => getArgsss(a.fun) ::: a.args
109-
case _ => Nil
110-
}
111-
def getSel(t: Tree): Tree = {t match {
112-
case t: Apply => getSel(t.fun)
113-
case t: Select => t.qualifier
114-
case t: TypeApply => getSel(t.fun)
115-
case _ => t
116-
}}
117-
val args = getArgsss(t)
118-
val rec = getSel(t)
119-
val prefix = rec match {
120-
case t: New =>
121-
args.map(keepOnlySideEffects)
122-
case _ =>
123-
rec :: args.map(keepOnlySideEffects)
61+
case t: This =>
62+
EmptyTree
63+
64+
case Typed(exp, tpe) =>
65+
keepOnlySideEffects(exp)
66+
67+
// If is pure, propagade the simplification
68+
case t @ If(cond, thenp, elsep) =>
69+
val nthenp = keepOnlySideEffects(thenp)
70+
val nelsep = keepOnlySideEffects(elsep)
71+
if (thenp.isEmpty && elsep.isEmpty) keepOnlySideEffects(cond)
72+
else cpy.If(t)(
73+
thenp = nthenp.orElse(if (thenp.isInstanceOf[Literal]) thenp else unitLiteral),
74+
elsep = nelsep.orElse(if (elsep.isInstanceOf[Literal]) elsep else unitLiteral))
75+
76+
// Accessing a field of a product
77+
case t @ Select(rec, _) if
78+
(t.symbol.isGetter && !t.symbol.is(Mutable | Lazy)) ||
79+
(t.symbol.owner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) ||
80+
(t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) =>
81+
keepOnlySideEffects(rec)
82+
83+
case s @ Select(qual, name) if
84+
// !name.eq(nme.TYPE_) && // Keep the .TYPE added by ClassOf, would be needed for AfterErasure
85+
!s.symbol.is(Mutable | Lazy) && !s.symbol.is(Method) =>
86+
keepOnlySideEffects(qual)
87+
88+
case Block(List(t: DefDef), s: Closure) =>
89+
EmptyTree
90+
91+
case bl @ Block(stats, expr) =>
92+
val stats1 = stats.mapConserve(keepOnlySideEffects)
93+
val stats2 = if (stats1 ne stats) stats1.filter(_ ne EmptyTree) else stats1
94+
val expr2: Tree = expr match {
95+
case t: Literal if t.tpe.derivesFrom(defn.UnitClass) => expr
96+
case _ => keepOnlySideEffects(expr).orElse(unitLiteral)
97+
}
98+
cpy.Block(bl)(stats2, expr2)
99+
100+
case t: Ident if !t.symbol.is(Method | Lazy) && !t.symbol.info.isInstanceOf[ExprType] || effectsDontEscape(t) =>
101+
desugarIdent(t) match {
102+
case Some(t) if !(t.qualifier.symbol.is(JavaDefined) && t.qualifier.symbol.is(Package)) => t
103+
case _ => EmptyTree
104+
}
105+
106+
case app: Apply if app.fun.symbol.is(Label) && !app.tpe.finalResultType.derivesFrom(defn.UnitClass) =>
107+
// This is "the scary hack". It changes the return type to Unit, then
108+
// invalidates the denotation cache. Because this optimisation only
109+
// operates locally, this should be fine.
110+
val denot = app.fun.symbol.denot
111+
if (!denot.info.finalResultType.derivesFrom(defn.UnitClass)) {
112+
val newLabelType = app.symbol.info match {
113+
case mt: MethodType =>
114+
mt.derivedLambdaType(mt.paramNames, mt.paramInfos, defn.UnitType)
115+
case et: ExprType =>
116+
et.derivedExprType(defn.UnitType)
124117
}
125-
Block(prefix, unitLiteral)
118+
val newD = app.symbol.asSymDenotation.copySymDenotation(info = newLabelType)
119+
newD.installAfter(simplifyPhase)
120+
}
126121

127-
case t @ TypeApply(Select(rec, _), List(testType)) if t.symbol.eq(defn.Any_asInstanceOf) && testType.tpe.widenDealias.typeSymbol.exists =>
128-
val receiverType = TypeErasure.erasure(rec.tpe)
129-
val erazedTestedType = TypeErasure.erasure(testType.tpe)
130-
if (receiverType.derivesFrom(erazedTestedType.typeSymbol))
131-
rec
132-
else t
122+
ref(app.symbol).appliedToArgs(app.args)
133123

134-
case _ => t
135-
}
124+
case t @ Apply(fun, _) if effectsDontEscape(t) =>
125+
def getArgsss(a: Tree): List[Tree] = a match {
126+
case a: Apply => getArgsss(a.fun) ::: a.args
127+
case _ => Nil
128+
}
129+
def getSel(t: Tree): Tree = {t match {
130+
case t: Apply => getSel(t.fun)
131+
case t: Select => t.qualifier
132+
case t: TypeApply => getSel(t.fun)
133+
case _ => t
134+
}}
135+
val args = getArgsss(t)
136+
val rec = getSel(t)
137+
val prefix = rec match {
138+
case t: New =>
139+
args.map(keepOnlySideEffects)
140+
case _ =>
141+
rec :: args.map(keepOnlySideEffects)
142+
}
143+
Block(prefix, unitLiteral)
144+
145+
case t @ TypeApply(Select(rec, _), List(testType)) if t.symbol.eq(defn.Any_asInstanceOf) && testType.tpe.widenDealias.typeSymbol.exists =>
146+
val receiverType = TypeErasure.erasure(rec.tpe)
147+
val erazedTestedType = TypeErasure.erasure(testType.tpe)
148+
if (receiverType.derivesFrom(erazedTestedType.typeSymbol))
149+
rec
150+
else t
151+
152+
case t => t
136153
}
137154

138155
val constructorWhiteList: Set[String] = Set(
@@ -178,10 +195,11 @@ class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) exte
178195
"scala.runtime.BoxesRunTime.unboxToFloat"
179196
)
180197

198+
/** Does this tree has side effects? This is an approximation awaiting real purity analysis... */
181199
def effectsDontEscape(t: Tree): Boolean = t match {
182-
case Apply(fun, args) if fun.symbol.isConstructor && constructorWhiteList.contains(fun.symbol.owner.fullName.toString) =>
200+
case Apply(fun, _) if fun.symbol.isConstructor && constructorWhiteList.contains(fun.symbol.owner.fullName.toString) =>
183201
true
184-
case Apply(fun, args) if methodsWhiteList.contains(fun.symbol.fullName.toString) =>
202+
case Apply(fun, _) if methodsWhiteList.contains(fun.symbol.fullName.toString) =>
185203
true
186204
case Ident(_) if t.symbol.is(Module) && (t.symbol.is(Synthetic) || moduleWhiteList.contains(t.symbol.fullName.toString)) =>
187205
true

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer {
113113

114114
object Simplify {
115115
import tpd._
116+
// TODO: This function is duplicated in jvm/DottyBackendInterface.scala, let's factor these out!
116117
def desugarIdent(i: Ident)(implicit ctx: Context): Option[Select] = {
117118
i.tpe match {
118119
case TermRef(prefix: TermRef, name) =>

0 commit comments

Comments
 (0)