aboutsummaryrefslogblamecommitdiff
path: root/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala
blob: 2fea1984730feba79ea45cbbdb2f4165eeb6f967 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13












                                                      
                                

























                                                                                                       
                                      










                                                                                          
                                                                                   






                                                                                                        









                                                                                  

                                                                                                      
                                                                                      







                                                                                             



                                                                                             

                                                                                            




                                                                    

                                                                               
                                                                          

                                      


                                                                         





                                                                           
                                                                           










                                                                                              
                                                       




                                                                               
                                                           







                                                                                                      
                                          































                                                                                                      
package dotty.tools.dotc
package transform

import TreeTransforms._
import core.DenotTransformers.IdentityDenotTransformer
import core.Symbols._
import core.Contexts._
import core.Types._
import core.Flags._
import core.Decorators._
import core.StdNames.nme
import core.Names._
import core.NameOps._
import core.NameKinds.DirectName
import ast.Trees._
import ast.tpd
import collection.mutable

/** This phase optimizes code using implicit function types, by applying two rewrite rules.
 *  Let IF be the implicit function type
 *
 *      implicit Us => R
 *
 *  (1) A method definition
 *
 *      def m(xs: Ts): IF = implicit (ys: Us) => E
 *
 *  is expanded to two methods:
 *
 *      def m(xs: Ts): IF = implicit (ys: Us) => m$direct(xs)(ys)
 *      def m$direct(xs: Ts)(ys: Us): R = E
 *
 *  (and equivalently for methods with type parameters or a different number of value parameter lists).
 *  An abstract method definition
 *
 *     def m(xs: Ts): IF
 *
 *  is expanded to:
 *
 *     def m(xs: Ts): IF
 *     def m$direct(xs: Ts)(ys: Us): R
 *
 *  (2) A reference `qual.apply` where `qual` has implicit function type and
 *  `qual` refers to a method `m` is rewritten to a reference to `m$direct`,
 *  keeping the same type and value arguments as they are found in `qual`.
 */
class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTransform =>
  import tpd._

  override def phaseName: String = "shortcutImplicits"
  val treeTransform = new Transform

  /** If this option is true, we don't specialize symbols that are known to be only
   *  targets of monomorphic calls.
   *  The reason for this option is that benchmarks show that on the JVM for monomorphic dispatch
   *  scenarios inlining and escape analysis can often remove all calling overhead, so we might as
   *  well not duplicate the code. We need more experience to decide on the best setting of this option.
   */
  final val specializeMonoTargets = true

  class Transform extends TreeTransform {
    def phase = thisTransform

    override def prepareForUnit(tree: Tree)(implicit ctx: Context) = new Transform

    /** A map to cache mapping local methods to their direct counterparts.
     *  A fresh map is created for each unit.
     */
    private val directMeth = new mutable.HashMap[Symbol, Symbol]

    /** Should `sym` get a ..$direct companion?
     *  This is the case if (1) `sym` is a method with an implicit function type as final result type.
     *  However if `specializeMonoTargets` is false, we exclude symbols that are known
     *  to be only targets of monomorphic calls because they are effectively
     *  final and don't override anything.
     */
    private def shouldBeSpecialized(sym: Symbol)(implicit ctx: Context) =
      sym.is(Method, butNot = Accessor) &&
      defn.isImplicitFunctionType(sym.info.finalResultType) &&
      (specializeMonoTargets || !sym.isEffectivelyFinal || sym.allOverriddenSymbols.nonEmpty)

    /** @pre    The type's final result type is an implicit function type `implicit Ts => R`.
     *  @return The type of the `apply` member of `implicit Ts => R`.
     */
    private def directInfo(info: Type)(implicit ctx: Context): Type = info match {
      case info: PolyType   => info.derivedLambdaType(resType = directInfo(info.resultType))
      case info: MethodType => info.derivedLambdaType(resType = directInfo(info.resultType))
      case info: ExprType   => directInfo(info.resultType)
      case info             => info.member(nme.apply).info
    }

    /** A new `m$direct` method to accompany the given method `m` */
    private def newDirectMethod(sym: Symbol)(implicit ctx: Context): Symbol = {
      val direct = sym.copy(
        name = DirectName(sym.name.asTermName).asInstanceOf[sym.ThisName],
        flags = sym.flags | Synthetic,
        info = directInfo(sym.info))
      if (direct.allOverriddenSymbols.isEmpty) direct.resetFlag(Override)
      direct
    }

    /** The direct method `m$direct` that accompanies the given method `m`.
     *  Create one if it does not exist already.
     */
    private def directMethod(sym: Symbol)(implicit ctx: Context): Symbol =
      if (sym.owner.isClass) {
        val direct = sym.owner.info.member(DirectName(sym.name.asTermName))
          .suchThat(_.info matches directInfo(sym.info)).symbol
        if (direct.maybeOwner == sym.owner) direct
        else newDirectMethod(sym).enteredAfter(thisTransform)
      }
      else directMeth.getOrElseUpdate(sym, newDirectMethod(sym))


    /** Transform `qual.apply` occurrences according to rewrite rule (2) above */
    override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) =
      if (tree.name == nme.apply &&
          defn.isImplicitFunctionType(tree.qualifier.tpe.widen) &&
          shouldBeSpecialized(tree.qualifier.symbol)) {
        def directQual(tree: Tree): Tree = tree match {
          case Apply(fn, args)     => cpy.Apply(tree)(directQual(fn), args)
          case TypeApply(fn, args) => cpy.TypeApply(tree)(directQual(fn), args)
          case Block(stats, expr)  => cpy.Block(tree)(stats, directQual(expr))
          case tree: RefTree =>
            cpy.Ref(tree)(DirectName(tree.name.asTermName))
              .withType(directMethod(tree.symbol).termRef)
        }
        directQual(tree.qualifier)
      } else tree

    /** Transform methods with implicit function type result according to rewrite rule (1) above */
    override def transformDefDef(mdef: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = {
      val original = mdef.symbol
      if (shouldBeSpecialized(original)) {
        val direct = directMethod(original)

        def splitClosure(tree: Tree): (List[Type] => List[List[Tree]] => Tree, Tree) = tree match {
          case Block(Nil, expr) => splitClosure(expr)
          case Block((meth @ DefDef(nme.ANON_FUN, Nil, clparams :: Nil, _, _)) :: Nil, cl: Closure) =>
            val tparamSyms = mdef.tparams.map(_.symbol)
            val vparamSymss = mdef.vparamss.map(_.map(_.symbol))
            val clparamSyms = clparams.map(_.symbol)
            val remappedCore = (ts: List[Type]) => (prefss: List[List[Tree]]) =>
              meth.rhs
                .subst(tparamSyms ::: (vparamSymss.flatten ++ clparamSyms),
                       ts.map(_.typeSymbol) ::: prefss.flatten.map(_.symbol))
                .changeOwnerAfter(original, direct, thisTransform)
                .changeOwnerAfter(meth.symbol, direct, thisTransform)
            val forwarder = ref(direct)
              .appliedToTypeTrees(tparamSyms.map(ref(_)))
              .appliedToArgss(vparamSymss.map(_.map(ref(_))) :+ clparamSyms.map(ref(_)))
            val fwdClosure = cpy.Block(tree)(cpy.DefDef(meth)(rhs = forwarder) :: Nil, cl)
            (remappedCore, fwdClosure)
          case EmptyTree =>
            (_ => _ => EmptyTree, EmptyTree)
        }

        val (remappedCore, fwdClosure) = splitClosure(mdef.rhs)
        val originalDef = cpy.DefDef(mdef)(rhs = fwdClosure)
        val directDef = polyDefDef(direct.asTerm, remappedCore)
        Thicket(originalDef, directDef)
      }
      else mdef
    }
  }
}