Skip to content

Commit 7067122

Browse files
authored
Merge pull request #5216 from dotty-staging/move-inline-out-of-typer
Move inline β-reduction out of typer
2 parents da32ca1 + c12e875 commit 7067122

File tree

12 files changed

+116
-45
lines changed

12 files changed

+116
-45
lines changed

compiler/src/dotty/tools/dotc/CompilationUnit.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package dotc
33

44
import util.SourceFile
55
import ast.{tpd, untpd}
6-
import tpd.{ Tree, TreeTraverser }
6+
import dotty.tools.dotc.ast.Trees
7+
import tpd.{Tree, TreeTraverser}
78
import typer.PrepareInlineable.InlineAccessors
89
import dotty.tools.dotc.core.Contexts.Context
910
import dotty.tools.dotc.core.SymDenotations.ClassDenotation
1011
import dotty.tools.dotc.core.Symbols._
1112
import dotty.tools.dotc.transform.SymUtils._
13+
import dotty.tools.dotc.typer.Inliner
1214

1315
class CompilationUnit(val source: SourceFile) {
1416

@@ -23,6 +25,11 @@ class CompilationUnit(val source: SourceFile) {
2325
/** Pickled TASTY binaries, indexed by class. */
2426
var pickled: Map[ClassSymbol, Array[Byte]] = Map()
2527

28+
/** Will be reset to `true` if `tpdTree` contains a call to an inline method. The information
29+
* is used in phase InlineCalls in order to avoid traversing an inline-less tree.
30+
*/
31+
var containsInlineCalls: Boolean = false
32+
2633
/** Will be reset to `true` if `untpdTree` contains `Quote` trees. The information
2734
* is used in phase ReifyQuotes in order to avoid traversing a quote-less tree.
2835
*/
@@ -46,17 +53,21 @@ object CompilationUnit {
4653
if (forceTrees) {
4754
val force = new Force
4855
force.traverse(unit1.tpdTree)
56+
unit1.containsInlineCalls = force.containsInline
4957
unit1.containsQuotesOrSplices = force.containsQuotes
5058
}
5159
unit1
5260
}
5361

5462
/** Force the tree to be loaded */
5563
private class Force extends TreeTraverser {
64+
var containsInline = false
5665
var containsQuotes = false
5766
def traverse(tree: Tree)(implicit ctx: Context): Unit = {
5867
if (tree.symbol.isQuote)
5968
containsQuotes = true
69+
if (tpd.isInlineCall(tree))
70+
containsInline = true
6071
traverseChildren(tree)
6172
}
6273
}

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class Compiler {
4444
/** Phases dealing with TASTY tree pickling and unpickling */
4545
protected def picklerPhases: List[List[Phase]] =
4646
List(new Pickler) :: // Generate TASTY info
47+
List(new InlineCalls) :: // β-reduce inline calls
4748
List(new ReifyQuotes) :: // Turn quoted trees into explicit run-time data structures
4849
Nil
4950

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ package ast
55
import core._
66
import Flags._, Trees._, Types._, Contexts._
77
import Names._, StdNames._, NameOps._, Symbols._
8-
import typer.ConstFold
8+
import typer.{ConstFold, Inliner}
99
import reporting.trace
1010

1111
import scala.annotation.tailrec
@@ -759,6 +759,14 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
759759
false
760760
}
761761

762+
/** Is this call a call to a method that is marked as Inline */
763+
def isInlineCall(arg: Tree)(implicit ctx: Context): Boolean = arg match {
764+
case _: RefTree | _: GenericApply[_] =>
765+
!arg.tpe.widenDealias.isInstanceOf[MethodicType] && Inliner.isInlineable(arg)
766+
case _ =>
767+
false
768+
}
769+
762770
/** Structural tree comparison (since == on trees is reference equality).
763771
* For the moment, only Ident, Select, Literal, Apply and TypeApply are supported
764772
*/

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -585,9 +585,8 @@ object Trees {
585585

586586
/** A tree representing inlined code.
587587
*
588-
* @param call Info about the original call that was inlined
589-
* Until PostTyper, this is the full call, afterwards only
590-
* a reference to the toplevel class from which the call was inlined.
588+
* @param call Info about the original call that was inlined.
589+
* Only a reference to the toplevel class from which the call was inlined.
591590
* @param bindings Bindings for proxies to be used in the inlined code
592591
* @param expansion The inlined tree, minus bindings.
593592
*

compiler/src/dotty/tools/dotc/decompiler/TASTYDecompiler.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dotty.tools.dotc.decompiler
22

33
import dotty.tools.dotc.fromtasty._
44
import dotty.tools.dotc.core.Phases.Phase
5+
import dotty.tools.dotc.transform.InlineCalls
56

67
/** Compiler from tasty to user readable high text representation
78
* of the compiled scala code.
@@ -14,7 +15,10 @@ class TASTYDecompiler extends TASTYCompiler {
1415
List(new ReadTastyTreesFromClasses) :: // Load classes from tasty
1516
Nil
1617

17-
override protected def picklerPhases: List[List[Phase]] = Nil
18+
override protected def picklerPhases: List[List[Phase]] =
19+
List(new InlineCalls) :: // TODO should we really inline for the decompiler?
20+
Nil
21+
1822
override protected def transformPhases: List[List[Phase]] = Nil
1923

2024
override protected def backendPhases: List[List[Phase]] =
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package dotty.tools.dotc.transform
2+
3+
import dotty.tools.dotc.ast.Trees._
4+
import dotty.tools.dotc.ast.tpd
5+
import dotty.tools.dotc.core.Contexts._
6+
import dotty.tools.dotc.core.Phases.Phase
7+
import dotty.tools.dotc.core.Types.MethodicType
8+
import dotty.tools.dotc.transform.SymUtils._
9+
import dotty.tools.dotc.typer.{ConstFold, Inliner}
10+
11+
/** β-reduce all calls to inline methods and perform constant folding */
12+
class InlineCalls extends MacroTransform { thisPhase =>
13+
import tpd._
14+
15+
override def phaseName: String = InlineCalls.name
16+
17+
override def run(implicit ctx: Context): Unit =
18+
if (ctx.compilationUnit.containsInlineCalls && !ctx.settings.YnoInline.value) super.run
19+
20+
override def transformPhase(implicit ctx: Context): Phase = thisPhase.next
21+
22+
protected def newTransformer(implicit ctx: Context): Transformer =
23+
new InlineCallsTransformer
24+
25+
class InlineCallsTransformer extends Transformer {
26+
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
27+
case tree if isInlineCall(tree) && !ctx.reporter.hasErrors =>
28+
val tree2 = super.transform(tree) // transform arguments before inlining (inline arguments and constant fold arguments)
29+
transform(Inliner.inlineCall(tree2, tree.tpe.widen))
30+
case _: MemberDef =>
31+
val newTree = super.transform(tree)
32+
newTree.symbol.defTree = newTree // update tree set in PostTyper or set for inlined members
33+
newTree
34+
case _ =>
35+
if (tree.symbol.isQuote || tree.symbol.isSplice)
36+
ctx.compilationUnit.containsQuotesOrSplices = true
37+
ConstFold(super.transform(tree))
38+
}
39+
}
40+
41+
}
42+
43+
object InlineCalls {
44+
final val name = "inlineCalls"
45+
}

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

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ object PostTyper {
4040
*
4141
* (10) Adds Child annotations to all sealed classes
4242
*
43-
* (11) Minimizes `call` fields of `Inlined` nodes to just point to the toplevel
44-
* class from which code was inlined.
43+
* (11) Replace RHS of `erased` (but not `inline`) members by `(???: rhs.type)`
4544
*
4645
* The reason for making this a macro transform is that some functions (in particular
4746
* super and protected accessors and instantiation checks) are naturally top-down and
@@ -178,23 +177,22 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
178177
}
179178
}
180179

181-
private object dropInlines extends TreeMap {
182-
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
183-
case Inlined(call, _, _) =>
184-
cpy.Inlined(tree)(call, Nil, Typed(ref(defn.Predef_undefined), TypeTree(tree.tpe)))
185-
case _ => super.transform(tree)
186-
}
180+
private def handleInlineCall(sym: Symbol)(implicit ctx: Context): Unit = {
181+
if (sym.is(Inline))
182+
ctx.compilationUnit.containsInlineCalls = true
187183
}
188184

189185
override def transform(tree: Tree)(implicit ctx: Context): Tree =
190186
try tree match {
191187
case tree: Ident if !tree.isType =>
188+
handleInlineCall(tree.symbol)
192189
handleMeta(tree.symbol)
193190
tree.tpe match {
194191
case tpe: ThisType => This(tpe.cls).withPos(tree.pos)
195192
case _ => tree
196193
}
197194
case tree @ Select(qual, name) =>
195+
handleInlineCall(tree.symbol)
198196
handleMeta(tree.symbol)
199197
if (name.isTypeName) {
200198
Checking.checkRealizable(qual.tpe, qual.pos.focus)
@@ -203,14 +201,15 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
203201
else
204202
transformSelect(tree, Nil)
205203
case tree: Apply =>
204+
handleInlineCall(tree.symbol)
206205
val methType = tree.fun.tpe.widen
207206
val app =
208207
if (methType.isErasedMethod)
209208
tpd.cpy.Apply(tree)(
210209
tree.fun,
211210
tree.args.map(arg =>
212211
if (methType.isImplicitMethod && arg.pos.isSynthetic) ref(defn.Predef_undefined)
213-
else dropInlines.transform(arg)))
212+
else arg))
214213
else
215214
tree
216215
methPart(app) match {
@@ -223,6 +222,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
223222
super.transform(app)
224223
}
225224
case tree: TypeApply =>
225+
handleInlineCall(tree.symbol)
226226
val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree)
227227
if (fn.symbol != defn.ChildAnnot.primaryConstructor) {
228228
// Make an exception for ChildAnnot, which should really have AnyKind bounds
@@ -236,19 +236,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
236236
case _ =>
237237
super.transform(tree1)
238238
}
239-
case Inlined(call, bindings, expansion) if !call.isEmpty =>
240-
// Leave only a call trace consisting of
241-
// - a reference to the top-level class from which the call was inlined,
242-
// - the call's position
243-
// in the call field of an Inlined node.
244-
// The trace has enough info to completely reconstruct positions.
245-
// The minimization is done for two reasons:
246-
// 1. To save space (calls might contain large inline arguments, which would otherwise
247-
// be duplicated
248-
// 2. To enable correct pickling (calls can share symbols with the inlined code, which
249-
// would trigger an assertion when pickling).
250-
val callTrace = Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos)
251-
cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)(inlineContext(call)))
252239
case tree: Template =>
253240
withNoCheckNews(tree.parents.flatMap(newPart)) {
254241
val templ1 = paramFwd.forwardParamAccessors(tree)
@@ -335,9 +322,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
335322
}
336323

337324
/** Transforms the rhs tree into a its default tree if it is in an `erased` val/def.
338-
* Performed to shrink the tree that is known to be erased later.
339-
*/
325+
* Performed to shrink the tree that is known to be erased later.
326+
*/
340327
private def normalizeErasedRhs(rhs: Tree, sym: Symbol)(implicit ctx: Context) =
341-
if (sym.isEffectivelyErased) dropInlines.transform(rhs) else rhs
328+
if (!sym.isEffectivelyErased || sym.isInlineMethod || !rhs.tpe.exists) rhs
329+
else Typed(ref(defn.Predef_undefined), TypeTree(rhs.tpe))
342330
}
343331
}

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,10 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
301301
private def registerType(tpe: Type): Unit = tpe match {
302302
case tpe: ThisType if !canElideThis(tpe) && !thisProxy.contains(tpe.cls) =>
303303
val proxyName = s"${tpe.cls.name}_this".toTermName
304-
val proxyType = tpe.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner)
304+
val proxyType = inlineCallPrefix.tpe.tryNormalize match {
305+
case typeMatchResult if typeMatchResult.exists => typeMatchResult
306+
case _ => tpe.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner).widenIfUnstable
307+
}
305308
thisProxy(tpe.cls) = newSym(proxyName, InlineProxy, proxyType).termRef
306309
if (!tpe.cls.isStaticOwner)
307310
registerType(inlinedMethod.owner.thisType) // make sure we have a base from which to outer-select
@@ -452,9 +455,18 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
452455

453456
if (inlinedMethod == defn.Typelevel_error) issueError()
454457

458+
// Leave only a call trace consisting of
459+
// - a reference to the top-level class from which the call was inlined,
460+
// - the call's position
461+
// in the call field of an Inlined node.
462+
// The trace has enough info to completely reconstruct positions.
463+
// The minimization is done for the following reason:
464+
// * To save space (calls might contain large inline arguments, which would otherwise be duplicated
465+
val callTrace = Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos)
466+
455467
// Take care that only argument bindings go into `bindings`, since positions are
456468
// different for bindings from arguments and bindings from body.
457-
tpd.Inlined(call, finalBindings, finalExpansion)
469+
tpd.Inlined(callTrace, finalBindings, finalExpansion)
458470
}
459471
}
460472

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2446,13 +2446,6 @@ class Typer extends Namer
24462446
checkEqualityEvidence(tree, pt)
24472447
tree
24482448
}
2449-
else if (Inliner.isInlineable(tree) &&
2450-
!ctx.settings.YnoInline.value &&
2451-
!ctx.isAfterTyper &&
2452-
!ctx.reporter.hasErrors &&
2453-
tree.tpe <:< pt) {
2454-
readaptSimplified(Inliner.inlineCall(tree, pt))
2455-
}
24562449
else if (tree.tpe <:< pt) {
24572450
if (pt.hasAnnotation(defn.InlineParamAnnot))
24582451
checkInlineConformant(tree, isFinal = false, "argument to inline parameter")

compiler/test/dotc/pos-from-tasty.blacklist

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@ repeatedArgs213.scala
1717
default-super.scala
1818

1919
# Need to implement printing of match types
20-
matchtype.scala
20+
matchtype.scala
21+
22+
# Fails on CI (not locally)
23+
inline-named-typeargs.scala

tests/pos/depfuntype.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,8 @@ object Test {
1616
val z = depfun3(d)
1717
val z1: d.M = z
1818

19-
// Reproduced here because the one from DottyPredef is lacking a Tasty tree and
20-
// therefore can't be inlined when testing non-bootstrapped.
21-
// But inlining `implicitly` is vital to make the definition of `ifun` below work.
22-
inline final def implicitly[T](implicit ev: T): T = ev
19+
// Reproduced here because the one from DottyPredef is lacking a parameter dependency of the return type `ev.type`
20+
inline final def implicitly[T](implicit ev: T): ev.type = ev
2321

2422
type IDF = implicit (x: C) => x.M
2523

tests/run/tuples1a.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
object Test extends App {
2+
val t7 = '5' *: 4 *: "C" *: ()
3+
4+
val t7a = t7.tail
5+
val t7b = t7a.tail
6+
val t7c: Unit = (t7.tail: (Int, String)).tail
7+
val t7d: Unit = (t7.tail: Int *: String *: Unit).tail
8+
val t7e: Unit = t7.tail.tail
9+
}

0 commit comments

Comments
 (0)