Skip to content

Commit fe7378f

Browse files
committed
Improvements to Inliner
- Allow to recognize Inlined nodes as closure defs - Inline also pure valdefs that are used only once (was restricted to methods only) - Beta reduce again after inlining closures that are used only once
1 parent c5b02e4 commit fe7378f

File tree

4 files changed

+95
-20
lines changed

4 files changed

+95
-20
lines changed

compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,8 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
418418
def isPureExpr(tree: Tree)(implicit ctx: Context) = exprPurity(tree) >= Pure
419419
def isIdempotentExpr(tree: Tree)(implicit ctx: Context) = exprPurity(tree) >= Idempotent
420420

421+
def isPureBinding(tree: Tree)(implicit ctx: Context) = statPurity(tree) >= Pure
422+
421423
/** The purity level of this reference.
422424
* @return
423425
* SimplyPure if reference is (nonlazy and stable) or to a parameterized function
@@ -579,11 +581,15 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
579581

580582
/** An extractor for def of a closure contained the block of the closure. */
581583
object closureDef {
582-
def unapply(tree: Tree): Option[DefDef] = tree match {
583-
case Block(Nil, expr) => unapply(expr)
584+
def unapply(tree: Tree)(implicit ctx: Context): Option[DefDef] = tree match {
584585
case Block((meth @ DefDef(nme.ANON_FUN, _, _, _, _)) :: Nil, closure: Closure) =>
585586
Some(meth)
586-
case _ => None
587+
case Block(Nil, expr) =>
588+
unapply(expr)
589+
case Inlined(_, bindings, expr) if bindings.forall(isPureBinding) =>
590+
unapply(expr)
591+
case _ =>
592+
None
587593
}
588594
}
589595

compiler/src/dotty/tools/dotc/typer/Inliner.scala

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -794,22 +794,24 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
794794
countRefs.traverse(tree)
795795
for (binding <- bindings) countRefs.traverse(binding)
796796
val inlineBindings = new TreeMap {
797-
override def transform(t: Tree)(implicit ctx: Context) =
798-
super.transform {
799-
t match {
800-
case t: RefTree =>
801-
val sym = t.symbol
802-
refCount.get(sym) match {
803-
case Some(1) if sym.is(Method) =>
804-
bindingOfSym(sym) match {
805-
case binding: ValOrDefDef => integrate(binding.rhs, sym)
806-
}
807-
case none => t
797+
override def transform(t: Tree)(implicit ctx: Context) = t match {
798+
case t: RefTree =>
799+
val sym = t.symbol
800+
val t1 = refCount.get(sym) match {
801+
case Some(1) =>
802+
bindingOfSym(sym) match {
803+
case binding: ValOrDefDef => integrate(binding.rhs, sym)
808804
}
809-
case _ => t
805+
case none => t
810806
}
811-
}
807+
super.transform(t1)
808+
case t: Apply =>
809+
val t1 = super.transform(t)
810+
if (t1 `eq` t) t else reducer.betaReduce(t1)
811+
case _ =>
812+
super.transform(t)
812813
}
814+
}
813815
def retain(binding: MemberDef) = refCount.get(binding.symbol) match {
814816
case Some(x) => x > 1 || x == 1 && !binding.symbol.is(Method)
815817
case none => true

tests/run-with-compiler/i3876-d.check

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
6
22
{
33
val x$1: scala.Int = 3
4-
5-
{ // inlined
6-
x$1.+(x$1)
7-
}
4+
x$1.+(x$1)
85
}

tests/run/transparent-foreach.scala

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// A demonstrator how `transparent override` can be used to sepcialize inherited methods
2+
trait Iterable[+A] {
3+
def foreach(f: A => Unit): Unit
4+
}
5+
// The unconventional definition of `Range` and `UntilRange` is needed so
6+
// that `UntiRange` is recognized as a Noinits class. Our purity predictor
7+
// for classes currently bails out for arguments passed to parent classes
8+
// and for concrete `val`s. That's because the predictor runs on untyped trees
9+
// so there's no way to predict whether an expression is pure or not.
10+
// It would be nice if we could improve the predictor to work with typed trees.
11+
// The tricky bit is doing this without causing cycles. That's the price we
12+
// pay for making inlining a typelevel computation.
13+
// One possible way to do it would be to make purity a property that's computed on demand,
14+
// just like info, but evaluated later. Then we might still cause cycles, but these
15+
// would be "justified" by inlining attempts. I.e. you could avoid a cycle by
16+
// inlining less.
17+
abstract class Range extends Iterable[Int] {
18+
val start: Int
19+
val end: Int
20+
def step: Int
21+
def inclusive: Boolean
22+
def foreach(f: Int => Unit): Unit = {
23+
var idx = start
24+
while (
25+
if (step > 0)
26+
if (inclusive) idx <= end else idx < end
27+
else
28+
if (inclusive) idx >= end else idx > end
29+
) {
30+
f(idx)
31+
idx = idx + step
32+
}
33+
}
34+
}
35+
class UntilRange(val start: Int, val end: Int) extends Range {
36+
def step = 1
37+
def inclusive = false
38+
transparent override def foreach(f: Int => Unit): Unit = {
39+
var idx = start
40+
while (idx < end) {
41+
f(idx)
42+
idx += 1
43+
}
44+
}
45+
}
46+
object Test extends App {
47+
var x = 0
48+
new UntilRange(1, 10).foreach(x += _)
49+
// Expands to:
50+
// var idx: Int = 1
51+
// while idx < 10 do
52+
// x = x * idx
53+
// idx = idx + 1
54+
// }
55+
56+
class IntDeco(val x: Int) extends AnyVal {
57+
transparent def until(y: Int) = new UntilRange(x, y)
58+
}
59+
implicit transparent def intDeco(x: Int): IntDeco = new IntDeco(x)
60+
// So far, the decorator has to be an explicit def, since
61+
// we can inline only methods defined in source. So an implicit class
62+
// will not work. We can make this work by making `Desugar` smarter
63+
// and generate a transparent with pre-defined "BodyToInline" annotation.
64+
// It's doable, just extra work.
65+
66+
(1 until 10).foreach(x += _)
67+
// expands to same as above
68+
69+
assert(x == 90)
70+
}

0 commit comments

Comments
 (0)