Skip to content

Move inline β-reduction out of typer #5216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Oct 22, 2018
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion compiler/src/dotty/tools/dotc/CompilationUnit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package dotc

import util.SourceFile
import ast.{tpd, untpd}
import tpd.{ Tree, TreeTraverser }
import dotty.tools.dotc.ast.Trees
import tpd.{Tree, TreeTraverser}
import typer.PrepareInlineable.InlineAccessors
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.SymDenotations.ClassDenotation
import dotty.tools.dotc.core.Symbols._
import dotty.tools.dotc.transform.SymUtils._
import dotty.tools.dotc.typer.Inliner

class CompilationUnit(val source: SourceFile) {

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

/** Will be reset to `true` if `tpdTree` contains a call to an inline method. The information
* is used in phase InlineCalls in order to avoid traversing an inline-less tree.
*/
var containsInlineCalls: Boolean = false

/** Will be reset to `true` if `untpdTree` contains `Quote` trees. The information
* is used in phase ReifyQuotes in order to avoid traversing a quote-less tree.
*/
Expand All @@ -46,17 +53,24 @@ object CompilationUnit {
if (forceTrees) {
val force = new Force
force.traverse(unit1.tpdTree)
unit1.containsInlineCalls = force.containsInline
unit1.containsQuotesOrSplices = force.containsQuotes
}
unit1
}

/** Force the tree to be loaded */
private class Force extends TreeTraverser {
var containsInline = false
var containsQuotes = false
def traverse(tree: Tree)(implicit ctx: Context): Unit = {
if (tree.symbol.isQuote)
containsQuotes = true
tree match {
case _: tpd.RefTree | _: Trees.GenericApply[_] if Inliner.isInlineable(tree) =>
containsInline = true
case _ =>
}
traverseChildren(tree)
}
}
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class Compiler {
/** Phases dealing with TASTY tree pickling and unpickling */
protected def picklerPhases: List[List[Phase]] =
List(new Pickler) :: // Generate TASTY info
List(new InlineCalls) :: // β-reduce inline calls
List(new ReifyQuotes) :: // Turn quoted trees into explicit run-time data structures
Nil

Expand Down
5 changes: 2 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -583,9 +583,8 @@ object Trees {

/** A tree representing inlined code.
*
* @param call Info about the original call that was inlined
* Until PostTyper, this is the full call, afterwards only
* a reference to the toplevel class from which the call was inlined.
* @param call Info about the original call that was inlined.
* Only a reference to the toplevel class from which the call was inlined.
* @param bindings Bindings for proxies to be used in the inlined code
* @param expansion The inlined tree, minus bindings.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dotty.tools.dotc.decompiler

import dotty.tools.dotc.fromtasty._
import dotty.tools.dotc.core.Phases.Phase
import dotty.tools.dotc.transform.InlineCalls

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

override protected def picklerPhases: List[List[Phase]] = Nil
override protected def picklerPhases: List[List[Phase]] =
List(new InlineCalls) :: // TODO should we really inline for the decompiler?
Nil

override protected def transformPhases: List[List[Phase]] = Nil

override protected def backendPhases: List[List[Phase]] =
Expand Down
45 changes: 45 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/InlineCalls.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package dotty.tools.dotc.transform

import dotty.tools.dotc.ast.Trees._
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core.Phases.Phase
import dotty.tools.dotc.core.Types.MethodicType
import dotty.tools.dotc.transform.SymUtils._
import dotty.tools.dotc.typer.{ConstFold, Inliner}

/** β-reduce all calls to inline methods and preform constant folding */
class InlineCalls extends MacroTransform { thisPhase =>
import tpd._

override def phaseName: String = InlineCalls.name

override def run(implicit ctx: Context): Unit =
if (ctx.compilationUnit.containsInlineCalls && !ctx.settings.YnoInline.value) super.run

override def transformPhase(implicit ctx: Context): Phase = thisPhase.next

protected def newTransformer(implicit ctx: Context): Transformer =
new InlineCallsTransformer

class InlineCallsTransformer extends Transformer {
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
case _: RefTree | _: GenericApply[_] if !tree.tpe.widenDealias.isInstanceOf[MethodicType] && Inliner.isInlineable(tree) && !ctx.reporter.hasErrors =>
val tree2 = super.transform(tree) // transform arguments before inlining (inline arguments and constant fold arguments)
transform(Inliner.inlineCall(tree2, tree.tpe.widen))
case _: MemberDef =>
val newTree = super.transform(tree)
newTree.symbol.defTree = newTree // update tree set in PostTyper or set for inlined members
newTree
case _ =>
if (tree.symbol.isQuote || tree.symbol.isSplice)
ctx.compilationUnit.containsQuotesOrSplices = true
ConstFold(super.transform(tree))
}
}

}

object InlineCalls {
final val name = "inlineCalls"
}
22 changes: 9 additions & 13 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
}
}

private def handleInlineCall(sym: Symbol)(implicit ctx: Context): Unit = {
if (sym.is(Inline))
ctx.compilationUnit.containsInlineCalls = true
}

private object dropInlines extends TreeMap {
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
case Inlined(call, _, _) =>
Expand All @@ -189,12 +194,14 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
override def transform(tree: Tree)(implicit ctx: Context): Tree =
try tree match {
case tree: Ident if !tree.isType =>
handleInlineCall(tree.symbol)
handleMeta(tree.symbol)
tree.tpe match {
case tpe: ThisType => This(tpe.cls).withPos(tree.pos)
case _ => tree
}
case tree @ Select(qual, name) =>
handleInlineCall(tree.symbol)
handleMeta(tree.symbol)
if (name.isTypeName) {
Checking.checkRealizable(qual.tpe, qual.pos.focus)
Expand All @@ -203,6 +210,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
else
transformSelect(tree, Nil)
case tree: Apply =>
handleInlineCall(tree.symbol)
val methType = tree.fun.tpe.widen
val app =
if (methType.isErasedMethod)
Expand All @@ -223,6 +231,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
super.transform(app)
}
case tree: TypeApply =>
handleInlineCall(tree.symbol)
val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree)
if (fn.symbol != defn.ChildAnnot.primaryConstructor) {
// Make an exception for ChildAnnot, which should really have AnyKind bounds
Expand All @@ -236,19 +245,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
case _ =>
super.transform(tree1)
}
case Inlined(call, bindings, expansion) if !call.isEmpty =>
// Leave only a call trace consisting of
// - a reference to the top-level class from which the call was inlined,
// - the call's position
// in the call field of an Inlined node.
// The trace has enough info to completely reconstruct positions.
// The minimization is done for two reasons:
// 1. To save space (calls might contain large inline arguments, which would otherwise
// be duplicated
// 2. To enable correct pickling (calls can share symbols with the inlined code, which
// would trigger an assertion when pickling).
val callTrace = Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos)
cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)(inlineContext(call)))
case tree: Template =>
withNoCheckNews(tree.parents.flatMap(newPart)) {
val templ1 = paramFwd.forwardParamAccessors(tree)
Expand Down
16 changes: 14 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,10 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
private def registerType(tpe: Type): Unit = tpe match {
case tpe: ThisType if !canElideThis(tpe) && !thisProxy.contains(tpe.cls) =>
val proxyName = s"${tpe.cls.name}_this".toTermName
val proxyType = tpe.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner)
val proxyType = inlineCallPrefix.tpe.tryNormalize match {
case typeMatchResult if typeMatchResult.exists => typeMatchResult
case _ => tpe.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner).widenIfUnstable
}
thisProxy(tpe.cls) = newSym(proxyName, InlineProxy, proxyType).termRef
if (!tpe.cls.isStaticOwner)
registerType(inlinedMethod.owner.thisType) // make sure we have a base from which to outer-select
Expand Down Expand Up @@ -452,9 +455,18 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {

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

// Leave only a call trace consisting of
// - a reference to the top-level class from which the call was inlined,
// - the call's position
// in the call field of an Inlined node.
// The trace has enough info to completely reconstruct positions.
// The minimization is done for the following reason:
// * To save space (calls might contain large inline arguments, which would otherwise be duplicated
val callTrace = Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos)

// Take care that only argument bindings go into `bindings`, since positions are
// different for bindings from arguments and bindings from body.
tpd.Inlined(call, finalBindings, finalExpansion)
tpd.Inlined(callTrace, finalBindings, finalExpansion)
}
}

Expand Down
7 changes: 0 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2441,13 +2441,6 @@ class Typer extends Namer
checkEqualityEvidence(tree, pt)
tree
}
else if (Inliner.isInlineable(tree) &&
!ctx.settings.YnoInline.value &&
!ctx.isAfterTyper &&
!ctx.reporter.hasErrors &&
tree.tpe <:< pt) {
readaptSimplified(Inliner.inlineCall(tree, pt))
}
else if (tree.tpe <:< pt) {
if (pt.hasAnnotation(defn.InlineParamAnnot))
checkInlineConformant(tree, isFinal = false, "argument to inline parameter")
Expand Down
6 changes: 2 additions & 4 deletions tests/pos/depfuntype.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ object Test {
val z = depfun3(d)
val z1: d.M = z

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

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

Expand Down
9 changes: 9 additions & 0 deletions tests/run/tuples1a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
object Test extends App {
val t7 = '5' *: 4 *: "C" *: ()

val t7a = t7.tail
val t7b = t7a.tail
val t7c: Unit = (t7.tail: (Int, String)).tail
val t7d: Unit = (t7.tail: Int *: String *: Unit).tail
val t7e: Unit = t7.tail.tail
}