All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dotty.tools.dotc.transform.localopt.Simplify.scala Maven / Gradle / Ivy

package dotty.tools.dotc
package transform.localopt

import core.Contexts.Context
import core.DenotTransformers.IdentityDenotTransformer
import core.Symbols._
import core.Types._
import core.Flags._
import core.Decorators._
import core.NameOps._
import transform.MegaPhase.MiniPhase
import config.Printers.simplify
import ast.tpd

import scala.annotation.tailrec

/** This phase consists of a series of small, simple, local optimisations
 *  applied as a fix point transformation over Dotty Trees.
 *
 *  The termination condition uses referential equality on Trees. Furthermore,
 *  termination relies of every optimisation to be shrinking transformations.
 *
 *  This phase is intended to be run multiple times in the compilation pipeline.
 *  This is due to several reasons:
 *   - running this phase early allows to reduce size of compilation unit, speeding up subsequent transformations.
 *   - running this phase late allows to eliminate inefficiencies created by previous phase
 *   - different patters are easier to optimize at different moments of pipeline
 */
class Simplify extends MiniPhase with IdentityDenotTransformer {
  import tpd._
  override def phaseName: String = "simplify"
  override val cpy = tpd.cpy

  private[localopt] var SeqFactoryClass: Symbol = null
  private[localopt] var CommutativePrimitiveOperations: Set[Symbol] = null

  /** The original intention is to run most optimizations both before and after erasure.
   *  Erasure creates new inefficiencies as well as new optimization opportunities.
   *
   *  The order of optimizations is tuned to converge faster.
   *  Reordering them may require quadratically more rounds to finish.
   */
  private def beforeErasure: List[Optimisation] =
    new InlineCaseIntrinsics(this)  ::
    new RemoveUnnecessaryNullChecks ::
    new InlineOptions               ::
    //new InlineLabelsCalledOnce    :: // not needed: new pattern matcher does this already
    new Valify(this)                ::
    new Devalify                    ::
    new Jumpjump                    ::
    new DropGoodCasts               ::
    new DropNoEffects(this)         ::
    new InlineLocalObjects(this)    ::
    // new Varify                      :: // varify could stop other transformations from being applied. postponed.
    // new BubbleUpNothing             ::
    new ConstantFold(this)          ::
    Nil

  /** See comment on beforeErasure */
  private def afterErasure: List[Optimisation] =
    new Valify(this)                ::
    new Devalify                    ::
    new Jumpjump                    ::
    new DropGoodCasts               ::
    new DropNoEffects(this)         ::
    new ConstantFold(this)          ::
    Nil

  var optimisations: List[Optimisation] = Nil

  /** Optimisation fuel, for debugging. Decremented every time Simplify
   *  applies an optimisation until fuel == 0. Original idea from Automatic
   *  Isolation of Compiler Errors by David Whalley. Unable with -Yopt-fuel.
   *
   *  The fuel can be used to do a bisection on large test cases that fail
   *  -optimise. See compiler/test/bisect.sh for a shell script to automates
   *  the bisection search.
   */
  var fuel: Int = -1

  override def prepareForUnit(tree: Tree)(implicit ctx: Context) = {
    if (ctx.settings.optimise.value) {
      SeqFactoryClass = ctx.requiredClass("scala.collection.generic.SeqFactory")
      CommutativePrimitiveOperations = Set(defn.Boolean_&&, defn.Boolean_||, defn.Int_+, defn.Int_*, defn.Long_+, defn.Long_*)

      val maxFuel = ctx.settings.YoptFuel.value
      if (fuel < 0 && maxFuel > 0) // Both defaults are at -1
        fuel = maxFuel

      optimisations = {
        val o = if (ctx.erasedTypes) afterErasure else beforeErasure
        val p = ctx.settings.YoptPhases.value
        if (p.isEmpty) o else o.filter(x => p.contains(x.name))
      }
    }

    ctx
  }

  // The entry point of local optimisation: DefDefs
  override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = {
    val ctx0 = ctx
    if (ctx.settings.optimise.value && !tree.symbol.is(Label)) {
      implicit val ctx: Context = ctx0.withOwner(tree.symbol(ctx0))
      var rhs0 = tree.rhs
      var rhs1: Tree = null
      while (rhs1 ne rhs0) {
        rhs1 = rhs0
        optimisations.foreach { optimisation =>
          // Visit
          rhs0.foreachSubTree(optimisation.visitor)

          // Transform
          rhs0 = new TreeMap() {
            override def transform(tree: Tree)(implicit ctx: Context): Tree = {
              val innerCtx = if (tree.isDef && tree.symbol.exists) ctx.withOwner(tree.symbol) else ctx
              val childOptimizedTree = super.transform(tree)(innerCtx)
              printIfDifferent(childOptimizedTree, optimisation.transformer(ctx)(childOptimizedTree), optimisation)
            }
          }.transform(rhs0)

          // Clean
          optimisation.clear()
        }
      }
      if (rhs0 ne tree.rhs) tpd.cpy.DefDef(tree)(rhs = rhs0)
      else tree
    } else tree
  }

  private def printIfDifferent(tree1: Tree, tree2: => Tree, opt: Optimisation)(implicit ctx: Context): Tree = {
    if (fuel == -1)
      tree2 // Does nothing when fuel is disabled.
    else if (fuel == 0)
      tree1 // No more fuel? No more transformations for you!
    else {  // Print the trees if different and consume fuel accordingly.
      val t2 = tree2
      if (tree1 ne t2) {
        if (fuel > 0)
          fuel -= 1
        if (fuel != -1 && fuel < 5) {
          println(s"${tree1.symbol} was simplified by ${opt.name} (fuel=$fuel): ${tree1.show}")
          println(s"became after ${opt.name}: (fuel=$fuel) ${t2.show}")
        }
      }
      t2
    }
  }
}

object Simplify {
  import tpd._

  /** Is this tree mutable, or java.lang.System.{in, out, err}? These three
   *  System members are the only static final fields that are mutable.
   *  See https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5.4
   */
  def isEffectivelyMutable(t: Tree)(implicit ctx: Context): Boolean = t match {
    case _ if t.symbol.is(Mutable) => true
    case _: Select | _: Ident =>
      t.symbol.owner == defn.SystemModule
    case _ => false
  }

  def isImmutableAccessor(t: Tree)(implicit ctx: Context): Boolean = {
    val sym = t.symbol
    val isImmutableGetter = sym.isGetter && !sym.is(Mutable | Lazy)
    val isCaseAccessor    = sym.is(CaseAccessor) && !sym.is(Mutable | Lazy)
    val isProductAccessor = sym.exists                               &&
                            sym.owner.derivesFrom(defn.ProductClass) &&
                            sym.owner.is(CaseClass)                  &&
                            sym.name.isSelectorName                  &&
                            !sym.info.decls.exists(_.is(Mutable | Lazy)) // Conservatively covers case class A(var x: Int)
    isImmutableGetter || isCaseAccessor || isProductAccessor
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy