From 328357ced669df31b564b367e61cc534049d0a86 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Wed, 31 May 2017 13:27:17 +0200 Subject: [PATCH 01/16] Split Simplify.scala in 15 files --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 +- .../dotty/tools/dotc/core/Definitions.scala | 5 +- .../dotc/transform/linker/Analysis.scala | 69 - .../dotc/transform/linker/Simplify.scala | 1415 ----------------- .../transform/localopt/BubbleUpNothing.scala | 57 + .../transform/localopt/ConstantFold.scala | 212 +++ .../dotc/transform/localopt/Devalify.scala | 212 +++ .../transform/localopt/DropGoodCasts.scala | 94 ++ .../transform/localopt/DropNoEffects.scala | 189 +++ .../localopt/InlineCaseIntrinsics.scala | 134 ++ .../localopt/InlineLabelsCalledOnce.scala | 50 + .../localopt/InlineLocalObjects.scala | 176 ++ .../transform/localopt/InlineOptions.scala | 51 + .../dotc/transform/localopt/Jumpjump.scala | 49 + .../transform/localopt/Optimisation.scala | 20 + .../RemoveUnnecessaryNullChecks.scala | 89 ++ .../dotc/transform/localopt/Simplify.scala | 102 ++ .../dotc/transform/localopt/Valify.scala | 83 + .../dotc/transform/localopt/Varify.scala | 77 + 19 files changed, 1600 insertions(+), 1486 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/transform/linker/Analysis.scala delete mode 100644 compiler/src/dotty/tools/dotc/transform/linker/Simplify.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/InlineLocalObjects.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/InlineOptions.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 8eb3dddb1975..db838a539d6e 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -17,7 +17,7 @@ import core.DenotTransformers.DenotTransformer import core.Denotations.SingleDenotation import dotty.tools.backend.jvm.{LabelDefs, GenBCode, CollectSuperCalls} -import dotty.tools.dotc.transform.linker.Simplify +import dotty.tools.dotc.transform.localopt.Simplify /** The central class of the dotc compiler. The job of a compiler is to create * runs, which process given `phases` in a given `rootContext`. diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index d1c69408281c..9786ccd2991f 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -368,11 +368,12 @@ class Definitions { lazy val SeqType: TypeRef = ctx.requiredClassRef("scala.collection.Seq") def SeqClass(implicit ctx: Context) = SeqType.symbol.asClass - lazy val Seq_applyR = SeqClass.requiredMethodRef(nme.apply) def Seq_apply(implicit ctx: Context) = Seq_applyR.symbol lazy val Seq_headR = SeqClass.requiredMethodRef(nme.head) def Seq_head(implicit ctx: Context) = Seq_headR.symbol + lazy val SeqFactoryType: TypeRef = ctx.requiredClassRef("scala.collection.generic.SeqFactory") + def SeqFactoryClass(implicit ctx: Context) = SeqFactoryType.symbol.asClass lazy val ArrayType: TypeRef = ctx.requiredClassRef("scala.Array") def ArrayClass(implicit ctx: Context) = ArrayType.symbol.asClass @@ -447,6 +448,8 @@ class Definitions { def Long_* = Long_mulR.symbol lazy val Long_divR = LongClass.requiredMethodRef(nme.DIV, List(LongType)) def Long_/ = Long_divR.symbol + val CommutativePrimitiveOperations = new PerRun[collection.Set[Symbol]](implicit ctx => + Set(defn.Boolean_&&, defn.Boolean_||, defn.Int_+, defn.Int_*, defn.Long_+, defn.Long_*)) lazy val FloatType: TypeRef = valueTypeRef("scala.Float", BoxedFloatType, java.lang.Float.TYPE, FloatEnc) def FloatClass(implicit ctx: Context) = FloatType.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/transform/linker/Analysis.scala b/compiler/src/dotty/tools/dotc/transform/linker/Analysis.scala deleted file mode 100644 index 2f3ec71038dd..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/linker/Analysis.scala +++ /dev/null @@ -1,69 +0,0 @@ -package dotty.tools.dotc -package transform.linker - -import ast.Trees._ -import ast.tpd -import core.Contexts._ -import core.NameOps._ -import core.StdNames._ -import core.Symbols._ -import dotty.tools.dotc.core.Flags - -object Analysis { - import tpd._ - private val constructorWhiteList = Set( - "scala.Tuple2", - "scala.Tuple3", - "scala.Tuple4", - "scala.Tuple5", - "scala.Tuple6", - "scala.Tuple7", - "scala.Tuple8", - "scala.Tuple9", - "scala.Tuple10", - "scala.Tuple11", - "scala.Tuple12", - "scala.Tuple13", - "scala.Tuple14", - "scala.Tuple15", - "scala.Tuple16", - "scala.Tuple17", - "scala.Tuple18", - "scala.Tuple19", - "scala.Tuple20", - "scala.Tuple21", - "scala.Tuple22", - "scala.Some" - ) - - private val moduleWhiteList = constructorWhiteList.map(x => x + "$") - - private val methodsWhiteList = List( - "java.lang.Math.min", - "java.lang.Math.max", - "java.lang.Object.eq", - "java.lang.Object.ne", - "scala.Boolean.$amp$amp", - "scala.runtime.BoxesRunTime.unboxToBoolean", - "scala.runtime.BoxesRunTime.unboxToLong", - "scala.runtime.BoxesRunTime.unboxToInt", - "scala.runtime.BoxesRunTime.unboxToShort", - "scala.runtime.BoxesRunTime.unboxToDouble", - "scala.runtime.BoxesRunTime.unboxToChar", - "scala.runtime.BoxesRunTime.unboxToFloat" - ) - - def effectsDontEscape(t: Tree)(implicit ctx: Context) = { - t match { - case Apply(fun, args) if fun.symbol.isConstructor && constructorWhiteList.contains(fun.symbol.owner.fullName.toString) => - true - case Apply(fun, args) if methodsWhiteList.contains(fun.symbol.fullName.toString) => - true - case Ident(_) if t.symbol.is(Flags.Module) && (t.symbol.is(Flags.Synthetic) || moduleWhiteList.contains(t.symbol.fullName.toString)) => - true - case _ => - false - // analisys - } - } -} diff --git a/compiler/src/dotty/tools/dotc/transform/linker/Simplify.scala b/compiler/src/dotty/tools/dotc/transform/linker/Simplify.scala deleted file mode 100644 index d1cd81fc3afd..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/linker/Simplify.scala +++ /dev/null @@ -1,1415 +0,0 @@ -package dotty.tools.dotc -package transform.linker - -import core._ -import core.Constants.Constant -import core.Contexts._ -import core.Decorators._ -import core.DenotTransformers.IdentityDenotTransformer -import core.NameOps._ -import core.StdNames._ -import core.Symbols._ -import core.Types._ -import ast.Trees._ -import ast.tpd -import scala.collection.mutable -import transform.SymUtils._ -import transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo, TreeTransform} -import typer.ConstFold -import dotty.tools.dotc.config.Printers.simplify -import Flags._ - -/** This phase consists of a series of small, simple, local optimizations - * applied as a fix point transformation over Dotty Trees. - * - * The termination condition uses referential equality on Trees. Furthermore, - * termination relies of every optimization to be shrinking transformations. - */ -class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { - import tpd._ - - override def phaseName: String = "simplify" - - private var SeqFactoryClass: Symbol = null - private var symmetricOperations: Set[Symbol] = null - var optimize = false - - - override val cpy = tpd.cpy - - override def prepareForUnit(tree: Tree)(implicit ctx: Context): TreeTransform = { - SeqFactoryClass = ctx.requiredClass("scala.collection.generic.SeqFactory") - symmetricOperations = Set(defn.Boolean_&&, defn.Boolean_||, defn.Int_+, defn.Int_*, defn.Long_+, defn.Long_*) - optimize = ctx.settings.optimise.value - this - } - - type Visitor = Tree => Unit - type ErasureCompatibility = Int - val BeforeErasure: ErasureCompatibility = 1 - val AfterErasure: ErasureCompatibility = 2 - val BeforeAndAfterErasure: ErasureCompatibility = BeforeErasure | AfterErasure - - val NoVisitor: Visitor = (_) => () - type Transformer = () => (Context => Tree => Tree) - - /** Every optimization is a function of the following type. - * - * - String is the optimization name (for debugging) - * - * - ErasureCompatibility is flag indicating whether this optimization can - * be run before or after Erasure (or both). - * - * - Visitor is run first to gather information on Trees (using mutation) - * - * - Transformer does the actual Tree => Tree transformation, possibly - * - using a different context from the one using in Optimization. - */ - type Optimization = (Context) => (String, ErasureCompatibility, Visitor, Transformer) - - private lazy val _optimizations: Seq[Optimization] = - inlineCaseIntrinsics :: - removeUnnecessaryNullChecks :: - inlineOptions :: - inlineLabelsCalledOnce :: - valify :: - devalify :: - jumpjump :: - dropGoodCasts :: - dropNoEffects :: - // inlineLocalObjects :: // followCases needs to be fixed, see ./tests/pos/rbtree.scala - // varify :: // varify could stop other transformations from being applied. postponed. - // bubbleUpNothing :: - constantFold :: - Nil - - // The entry point of local optimisation: DefDefs - override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = { - val ctx0 = ctx - if (optimize && !tree.symbol.is(Label)) { - implicit val ctx: Context = ctx0.withOwner(tree.symbol(ctx0)) - // TODO: optimize class bodies before erasure? - var rhs0 = tree.rhs - var rhs1: Tree = null - val erasureCompatibility = if (ctx.erasedTypes) AfterErasure else BeforeErasure - while (rhs1 ne rhs0) { - rhs1 = rhs0 - val initialized = _optimizations.map(x => x(ctx.withOwner(tree.symbol))) - var (names, erasureSupport , visitors, transformers) = unzip4(initialized) - // TODO: fuse for performance - while (names.nonEmpty) { - val nextVisitor = visitors.head - val supportsErasure = erasureSupport.head - if ((supportsErasure & erasureCompatibility) > 0) { - rhs0.foreachSubTree(nextVisitor) - val nextTransformer = transformers.head() - val name = names.head - val rhst = 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 - nextTransformer(ctx)(super.transform(tree)(innerCtx)) - } - }.transform(rhs0) - if (rhst ne rhs0) { - simplify.println(s"${tree.symbol} was simplified by ${name}: ${rhs0.show}") - simplify.println(s"became: ${rhst.show}") - } - rhs0 = rhst - } - names = names.tail - visitors = visitors.tail - erasureSupport = erasureSupport.tail - transformers = transformers.tail - } - } - if (rhs0 ne tree.rhs) tpd.cpy.DefDef(tree)(rhs = rhs0) - else tree - } else tree - } - - private def desugarIdent(i: Ident)(implicit ctx: Context): Option[Select] = { - i.tpe match { - case TermRef(prefix: TermRef, name) => - Some(ref(prefix).select(i.symbol)) - case TermRef(prefix: ThisType, name) => - Some(This(prefix.cls).select(i.symbol)) - case _ => None - } - } - - private def dropCasts(t: Tree)(implicit ctx: Context): Tree = t match { - // case TypeApply(aio@Select(rec, nm), _) if aio.symbol == defn.Any_asInstanceOf => dropCasts(rec) - case Typed(t, tpe) => t - case _ => t - } - - private def readingOnlyVals(t: Tree)(implicit ctx: Context): Boolean = dropCasts(t) match { - case Typed(exp, _) => readingOnlyVals(exp) - case TypeApply(fun @ Select(rec, _), List(tp)) => - if ((fun.symbol eq defn.Any_asInstanceOf) && rec.tpe.derivesFrom(tp.tpe.classSymbol)) - readingOnlyVals(rec) - else false - case Apply(Select(rec, _), Nil) => - def isGetterOfAImmutableField = t.symbol.isGetter && !t.symbol.is(Mutable) - def isCaseClassWithVar = t.symbol.info.decls.exists(_.is(Mutable)) - def isAccessingProductField = t.symbol.exists && - t.symbol.owner.derivesFrom(defn.ProductClass) && - t.symbol.owner.is(CaseClass) && - t.symbol.name.isSelectorName && - !isCaseClassWithVar // Conservative Covers case class A(var x: Int) - def isImmutableCaseAccessor = t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable) - if (isGetterOfAImmutableField || isAccessingProductField || isImmutableCaseAccessor) - readingOnlyVals(rec) - else false - case Select(rec, _) if t.symbol.is(Method) => - if (t.symbol.isGetter && !t.symbol.is(Mutable)) readingOnlyVals(rec) // getter of a immutable field - else if (t.symbol.owner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) { - def isImmutableField = { - val fieldId = t.symbol.name.toString.drop(1).toInt - 1 - !t.symbol.owner.caseAccessors(ctx)(fieldId).is(Mutable) - } - if (isImmutableField) readingOnlyVals(rec) // accessing a field of a product - else false - } else if (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) - readingOnlyVals(rec) - else false - case Select(qual, _) if !t.symbol.is(Mutable) => - readingOnlyVals(qual) - case t: Ident if !t.symbol.is(Mutable) && !t.symbol.is(Method) && !t.symbol.info.dealias.isInstanceOf[ExprType] => - desugarIdent(t) match { - case Some(t) => readingOnlyVals(t) - case None => true - } - case t: This => true - // null => false, or the following fails devalify: - // trait I { - // def foo: Any = null - // } - // object Main { - // def main = { - // val s: I = null - // s.foo - // } - // } - case Literal(Constant(null)) => false - case t: Literal => true - case _ => false - } - - /** Inline case class specific methods using desugarings assumptions. - * - * - - * - - * - * Dotty only: - * - - * - * Scala2 only: - * - - */ - val inlineCaseIntrinsics: Optimization = { implicit ctx: Context => - // Apply fun may be a side-effectful function. E.g. a block, see tests/run/t4859.scala - // we need to maintain expressions that were in this block - def evalReciever(a: Apply, res: Tree) = { - def receiver(t: Tree): Tree = t match { - case TypeApply(fun, targs) if fun.symbol eq t.symbol => receiver(fun) - case Apply(fn, args) if fn.symbol == t.symbol => receiver(fn) - case Select(qual, _) => qual - case x => x - } - val recv = receiver(a) - if (recv.isEmpty || tpd.isPureRef(recv)) - res - else - Block(recv :: Nil, res) - } - - val transformer: Transformer = () => localCtx => { - // For synthetic applies on case classes (both dotty/scalac) - // - CC.apply(args) → new CC(args) - case a: Apply if !a.tpe.isInstanceOf[MethodicType] && - a.symbol.is(Synthetic) && - a.symbol.owner.is(Module) && - (a.symbol.name == nme.apply) && - a.symbol.owner.companionClass.is(CaseClass) && - !a.tpe.derivesFrom(defn.EnumClass) && - (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => - - def unrollArgs(t: Tree, l: List[List[Tree]]): List[List[Tree]] = t match { - case Apply(t, args) => unrollArgs(t, args :: l) - case _ => l - } - val argss = unrollArgs(a.fun, a.args :: Nil) - def rollInArgs(l: List[List[Tree]], fun: Tree): Tree = l match { - case head :: tail => rollInArgs(tail, fun.appliedToArgs(head)) - case _ => fun - } - val constructor = a.symbol.owner.companionClass.primaryConstructor.asTerm - evalReciever(a, rollInArgs(argss.tail, New(a.tpe.widenDealias, constructor, argss.head))) - - // For synthetic dotty unapplies on case classes: - // - CC.unapply(arg): CC → arg - // - CC.unapply(arg): Boolean → true, dotty only - // - CC.unapply(arg): Option[CC] → new Some(new scala.TupleN(arg._1, ..., arg._N)) - case a: Apply if a.symbol.is(Synthetic) && - a.symbol.owner.is(Module) && - (a.symbol.name == nme.unapply) && - a.symbol.owner.companionClass.is(CaseClass) && - !a.tpe.derivesFrom(defn.EnumClass) && - (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => - - val args = a.args.head - val isDottyUnapply = !a.symbol.owner.is(Scala2x) - val isScalaOptionUnapply = - a.tpe.derivesFrom(defn.OptionClass) && - a.args.head.tpe.derivesFrom(a.symbol.owner.companionClass) - - if (isDottyUnapply) { // dotty only - if (a.tpe.derivesFrom(defn.BooleanClass)) - // CC.unapply(arg): Boolean → true - evalReciever(a, Literal(Constant(true))) - else - // CC.unapply(arg): CC → arg - evalReciever(a, a.args.head) - } - else if (isScalaOptionUnapply) { - // CC.unapply(arg): Option[CC] → new Some(new scala.TupleN(arg._1, ..., arg._N)) - // The output is defined as a Tree => Tree to go thought tpd.evalOnce. - def some(e: Tree) = { - val accessors = e.tpe.widenDealias.classSymbol.caseAccessors.filter(_.is(Method)) - val fields = accessors.map(x => e.select(x).ensureApplied) - val tplType = a.tpe.baseArgTypes(defn.OptionClass).head - val someTpe = a.tpe.translateParameterized(defn.OptionClass, defn.SomeClass) - - if (fields.tail.nonEmpty) - New(someTpe, New(tplType, fields) :: Nil) - else // scalac does not have Tuple1 - New(someTpe, fields.head :: Nil) - } - val none = ref(defn.NoneModuleRef) - def isNull(e: Tree) = e.select(defn.Object_eq).appliedTo(Literal(Constant(null))) - def fi(e: Tree) = If(isNull(e), none, some(e)) - evalReciever(a, evalOnce(a.args.head)(fi)) - } - else a - - // Seq.unapplySeq(arg) → new Some(arg) - // Where Seq is any companion of type <: SeqFactoryClass - case a: Apply if (a.symbol.name == nme.unapplySeq) && - a.symbol.owner.derivesFrom(SeqFactoryClass) && - a.symbol.extendedOverriddenSymbols.isEmpty && - (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => - - def reciever(t: Tree): Type = t match { - case t: Apply => reciever(t.fun) - case t: TypeApply => reciever(t.fun) - case t: Ident => desugarIdent(t) match { - case Some(t) => reciever(t) - case _ => NoType - } - case t: Select => t.qualifier.tpe.widenDealias - } - - val recv = reciever(a) - if (recv.typeSymbol.is(Module)) { - val someTpe = a.tpe.translateParameterized(defn.OptionClass, defn.SomeClass) - evalReciever(a, New(someTpe, a.args.head :: Nil)) - } - else a - case t => t - } - // To run this optimisation after erasure one would need to specialize it - // for constructor with outer pointer and values classes. There is probably - // no need to run this more than once. - ("inlineCaseIntrinsics", BeforeErasure, NoVisitor, transformer) - } - - /** Various constant folding. - * - * - Starts/ends with the constant folding implemented in typer (ConstFold). - * - * - (if) specific optimization that propagate booleans, negation, and factor - * out (nested) if with equivalent branches wrt to isSimilar (using &&,||). - * - * - Constant propagation over pattern matching. - */ - val constantFold: Optimization = { implicit ctx: Context => - def preEval(t: Tree) = { - if (t.isInstanceOf[Literal] || t.isInstanceOf[CaseDef] || !isPureExpr(t)) t - else { - val s = ConstFold.apply(t) - if ((s ne null) && s.tpe.isInstanceOf[ConstantType]) { - val constant = s.tpe.asInstanceOf[ConstantType].value - Literal(constant) - } else t - } - } - - def isSimilar(t1: Tree, t2: Tree): Boolean = t1 match { - case t1: Apply => - t2 match { - case t2: Apply => - (t1.symbol == t2.symbol) && - (t1.args zip t2.args).forall(x => isSimilar(x._1, x._2)) && - isSimilar(t1.fun, t2.fun) - case _ => false - } - case t1: Ident => - desugarIdent(t1) match { - case Some(t) => - val t2i = t2 match { - case t2: Ident => desugarIdent(t2).getOrElse(t2) - case _ => t2 - } - isSimilar(t, t2i) - case None => t1.symbol eq t2.symbol - } - case t1: Select => t2 match { - case t2: Select => - (t1.symbol eq t2.symbol) && - isSimilar(t1.qualifier, t2.qualifier) - case t2: Ident => desugarIdent(t2) match { - case Some(t2) => isSimilar(t1, t2) - case None => false - } - case _ => false - } - case t1: Literal => t2 match { - case t2: Literal => - t1.const.tag == t2.const.tag && - t1.const.value == t2.const.value - case _ => false - } - case _ => false - } - - def isBool(tpe: Type): Boolean = tpe.derivesFrom(defn.BooleanClass) - def isConst(tpe: Type): Boolean = tpe.isInstanceOf[ConstantType] - def asConst(tpe: Type): ConstantType = tpe.asInstanceOf[ConstantType] - - val transformer: Transformer = () => localCtx => { x => preEval(x) match { - // TODO: include handling of isInstanceOf similar to one in IsInstanceOfEvaluator - // TODO: include methods such as Int.int2double(see ./tests/pos/harmonize.scala) - case If(cond1, thenp, elsep) if isSimilar(thenp, elsep) => - Block(cond1 :: Nil, thenp) - - case If(cond1, If(cond2, thenp2, elsep2), elsep1) if isSimilar(elsep1, elsep2) => - If(cond1.select(defn.Boolean_&&).appliedTo(cond2), thenp2, elsep1) - - case If(cond1, If(cond2, thenp2, elsep2), elsep1) if isSimilar(elsep1, thenp2) => - If(cond1.select(defn.Boolean_!).ensureApplied.select(defn.Boolean_||).appliedTo(cond2), elsep1, elsep2) - - case If(cond1, thenp1, If(cond2, thenp2, elsep2)) if isSimilar(thenp1, thenp2) => - If(cond1.select(defn.Boolean_||).appliedTo(cond2), thenp1, elsep2) - - case If(cond1, thenp1, If(cond2, thenp2, elsep2)) if isSimilar(thenp1, elsep2) => - If(cond1.select(defn.Boolean_||).appliedTo(cond2.select(defn.Boolean_!).ensureApplied), thenp1, thenp2) - - case If(t: Literal, thenp, elsep) => - if (t.const.booleanValue) thenp - else elsep - - case ift @ If(cond, thenp: Literal, elsep: Literal) - if isBool(ift.tpe) && thenp.const.booleanValue && !elsep.const.booleanValue => - cond - - // the lower two are disabled, as it may make the isSimilar rule not apply for a nested structure of iffs. - // see the example below: - // (b1, b2) match { - // case (true, true) => true - // case (false, false) => true - // case _ => false - // } - // case ift @ If(cond, thenp: Literal, elsep) - // if isBool(ift.tpe) && thenp.const.booleanValue => - // if (thenp.const.booleanValue) - // cond.select(defn.Boolean_||).appliedTo(elsep) - // else // thenp is false, this tree is bigger then the original - // cond.select(defn.Boolean_!).ensureApplied.select(defn.Boolean_&&).appliedTo(elsep) - // case ift @ If(cond, thenp, elsep :Literal) if - // isBool(ift.tpe) && !elsep.const.booleanValue => - // cond.select(defn.Boolean_&&).appliedTo(elsep) - // the other case ins't handled intentionally. See previous case for explanation - - case If(t @ Select(recv, _), thenp, elsep) if t.symbol eq defn.Boolean_! => - If(recv, elsep, thenp) - - case If(t @ Apply(Select(recv, _), Nil), thenp, elsep) if t.symbol eq defn.Boolean_! => - If(recv, elsep, thenp) - - // TODO: similar trick for comparisons. - // TODO: handle comparison with min\max values - case Apply(meth1 @ Select(Apply(meth2 @ Select(rec, _), Nil), _), Nil) - if meth1.symbol == defn.Boolean_! && meth2.symbol == defn.Boolean_! => - rec - - case meth1 @ Select(meth2 @ Select(rec, _), _) - if meth1.symbol == defn.Boolean_! && meth2.symbol == defn.Boolean_! && !ctx.erasedTypes => - rec - - case t @ Apply(Select(lhs, _), List(rhs)) => - val sym = t.symbol - (lhs, rhs) match { - case (lhs, Literal(_)) if !lhs.isInstanceOf[Literal] && symmetricOperations.contains(sym) => - rhs.select(sym).appliedTo(lhs) - case (l, _) if (sym == defn.Boolean_&&) && isConst(l.tpe) => - val const = asConst(l.tpe).value.booleanValue - if (const) Block(lhs :: Nil, rhs) - else l - - case (l, x: Literal) if sym == defn.Boolean_== && isBool(l.tpe) && isBool(x.tpe) => - if (x.const.booleanValue) l - else l.select(defn.Boolean_!).ensureApplied - - case (l, x: Literal) if sym == defn.Boolean_!= && isBool(l.tpe) && isBool(x.tpe) => - if (!x.const.booleanValue) l - else l.select(defn.Boolean_!).ensureApplied - - case (x: Literal, l) if sym == defn.Boolean_== && isBool(l.tpe) && isBool(x.tpe) => - if (x.const.booleanValue) l - else l.select(defn.Boolean_!).ensureApplied - - case (x: Literal, l) if sym == defn.Boolean_!= && isBool(l.tpe) && isBool(x.tpe) => - if (!x.const.booleanValue) l - else l.select(defn.Boolean_!).ensureApplied - - case (l: Literal, _) if (sym == defn.Boolean_||) && isConst(l.tpe) => - val const = asConst(l.tpe).value.booleanValue - if (l.const.booleanValue) l - else Block(lhs :: Nil, rhs) - - // case (Literal(Constant(1)), _) if sym == defn.Int_* => rhs - // case (Literal(Constant(0)), _) if sym == defn.Int_+ => rhs - // case (Literal(Constant(1L)), _) if sym == defn.Long_* => rhs - // case (Literal(Constant(0L)), _) if sym == defn.Long_+ => rhs - // // TODO: same for float, double, short - // // TODO: empty string concat - // // TODO: disctribute & reorder constants - // // TODO: merge subsequent casts - // case (_, Literal(Constant(1))) if sym == defn.Int_/ => lhs - // case (_, Literal(Constant(1L))) if sym == defn.Long_/ => lhs - // case (_, Literal(Constant(0))) if sym == defn.Int_/ => - // Block(List(lhs), - // ref(defn.throwMethod).appliedTo(New(defn.ArithmeticExceptionClass.typeRef, defn.ArithmeticExceptionClass_stringConstructor, Literal(Constant("/ by zero")) :: Nil))) - // case (_, Literal(Constant(0L))) if sym == defn.Long_/ => - // Block(List(lhs), - // ref(defn.throwMethod).appliedTo(New(defn.ArithmeticExceptionClass.typeRef, defn.ArithmeticExceptionClass_stringConstructor, Literal(Constant("/ by zero")) :: Nil))) - - case _ => t - } - - // This case can only be triggered when running Simplify before pattern matching: - // case t: Match - // if t.selector.tpe.isInstanceOf[ConstantType] && - // t.cases.forall { x => - // x.pat.tpe.isInstanceOf[ConstantType] || (isWildcardArg(x.pat) && x.guard.isEmpty) - // } => - // val selectorValue = t.selector.tpe.asInstanceOf[ConstantType].value - // val better = t.cases.find(x => isWildcardArg(x.pat) || (x.pat.tpe.asInstanceOf[ConstantType].value eq selectorValue)) - // if (better.nonEmpty) better.get.body - // else t - - case t: Literal => t - case t: CaseDef => t - case t if !isPureExpr(t) => t - case t => - val s = ConstFold.apply(t) - if ((s ne null) && s.tpe.isInstanceOf[ConstantType]) { - val constant = s.tpe.asInstanceOf[ConstantType].value - Literal(constant) - } else t - }} - ("constantFold", BeforeAndAfterErasure, NoVisitor, transformer) - } - - /** Inline case classes as vals, this essentially (local) implements multi - * parameter value classes. The main motivation is to get ride of all the - * intermediate tuples coming from pattern matching expressions. - */ - val inlineLocalObjects: Optimization = { implicit ctx: Context => - // In the end only calls constructor. Reason for unconditional inlining - val hasPerfectRHS = mutable.HashMap[Symbol, Boolean]() - // If all values have perfect RHS than key has perfect RHS - val checkGood = mutable.HashMap[Symbol, Set[Symbol]]() - val forwarderWritesTo = mutable.HashMap[Symbol, Symbol]() - val gettersCalled = mutable.HashSet[Symbol]() - def followTailPerfect(t: Tree, symbol: Symbol): Unit = { - t match { - case Block(_, expr) => followTailPerfect(expr, symbol) - case If(_, thenp, elsep) => followTailPerfect(thenp, symbol); followTailPerfect(elsep, symbol); - case Apply(fun, _) if fun.symbol.isConstructor && t.tpe.widenDealias == symbol.info.widenDealias.finalResultType.widenDealias => - hasPerfectRHS(symbol) = true - case Apply(fun, _) if fun.symbol.is(Label) && (fun.symbol ne symbol) => - checkGood.put(symbol, checkGood.getOrElse(symbol, Set.empty) + fun.symbol) - // assert(forwarderWritesTo.getOrElse(t.symbol, symbol) == symbol) - forwarderWritesTo(t.symbol) = symbol - case t: Ident if !t.symbol.owner.isClass && (t.symbol ne symbol) => - checkGood.put(symbol, checkGood.getOrElse(symbol, Set.empty) + t.symbol) - case _ => - } - } - val visitor: Visitor = { - case vdef: ValDef if (vdef.symbol.info.classSymbol is CaseClass) && - !vdef.symbol.is(Lazy) && - !vdef.symbol.info.classSymbol.caseAccessors.exists(x => x.is(Mutable)) => - followTailPerfect(vdef.rhs, vdef.symbol) - case Assign(lhs, rhs) if !lhs.symbol.owner.isClass => - checkGood.put(lhs.symbol, checkGood.getOrElse(lhs.symbol, Set.empty) + rhs.symbol) - case t @ Select(qual, _) if (t.symbol.isGetter && !t.symbol.is(Mutable)) || - (t.symbol.maybeOwner.derivesFrom(defn.ProductClass) && t.symbol.maybeOwner.is(CaseClass) && t.symbol.name.isSelectorName) || - (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) => - gettersCalled(qual.symbol) = true - case t: DefDef if t.symbol.is(Label) => - followTailPerfect(t.rhs, t.symbol) - case _ => - } - - val transformer: Transformer = () => { - var hasChanged = true - while(hasChanged) { - hasChanged = false - checkGood.foreach{case (key, values) => - values.foreach { value => - if (hasPerfectRHS.getOrElse(key, false)) { - hasChanged = !hasPerfectRHS.put(value, true).getOrElse(false) - } - } - } - } - - val newMappings: Map[Symbol, Map[Symbol, Symbol]] = - hasPerfectRHS.iterator.map(x => x._1).filter(x => !x.is(Method) && !x.is(Label) && gettersCalled.contains(x.symbol) && (x.symbol.info.classSymbol is CaseClass)) - .map { refVal => - simplify.println(s"replacing ${refVal.symbol.fullName} with stack-allocated fields") - var accessors = refVal.info.classSymbol.caseAccessors.filter(_.isGetter) // TODO: drop mutable ones - if (accessors.isEmpty) accessors = refVal.info.classSymbol.caseAccessors - val productAccessors = (1 to accessors.length).map(i => refVal.info.member(nme.productAccessorName(i)).symbol) // TODO: disambiguate - val newLocals = accessors.map(x => - // TODO: it would be nice to have an additional optimization that - // TODO: is capable of turning those mutable ones into immutable in common cases - ctx.newSymbol(ctx.owner.enclosingMethod, (refVal.name + "$" + x.name).toTermName, Synthetic | Mutable, x.asSeenFrom(refVal.info).info.finalResultType.widenDealias) - ) - val fieldMapping = accessors zip newLocals - val productMappings = productAccessors zip newLocals - (refVal, (fieldMapping ++ productMappings).toMap) - }.toMap - val toSplit: mutable.Set[Symbol] = mutable.Set.empty ++ newMappings.keySet - - def splitWrites(t: Tree, target: Symbol): Tree = { - t match { - case tree@ Block(stats, expr) => tpd.cpy.Block(tree)(stats, splitWrites(expr, target)) - case tree@ If(_, thenp, elsep) => tpd.cpy.If(tree)(thenp = splitWrites(thenp, target), elsep = splitWrites(elsep, target)) - case Apply(sel , args) if sel.symbol.isConstructor && t.tpe.widenDealias == target.info.widenDealias.finalResultType.widenDealias => - val fieldsByAccessors = newMappings(target) - var accessors = target.info.classSymbol.caseAccessors.filter(_.isGetter) // TODO: when is this filter needed? - if (accessors.isEmpty) accessors = target.info.classSymbol.caseAccessors - val assigns = (accessors zip args) map (x => ref(fieldsByAccessors(x._1)).becomes(x._2)) - val recreate = sel.appliedToArgs(accessors.map(x => ref(fieldsByAccessors(x)))) - Block(assigns, recreate) - case Apply(fun, _) if fun.symbol.is(Label) => - t // Do nothing. It will do on its own. - case t: Ident if !t.symbol.owner.isClass && newMappings.contains(t.symbol) && t.symbol.info.classSymbol == target.info.classSymbol => - val fieldsByAccessorslhs = newMappings(target) - val fieldsByAccessorsrhs = newMappings(t.symbol) - val accessors = target.info.classSymbol.caseAccessors.filter(_.isGetter) - val assigns = accessors map (x => ref(fieldsByAccessorslhs(x)).becomes(ref(fieldsByAccessorsrhs(x)))) - Block(assigns, t) - // If `t` is itself split, push writes. - case _ => - evalOnce(t){ev => - if (ev.tpe.derivesFrom(defn.NothingClass)) ev - else { - val fieldsByAccessors = newMappings(target) - val accessors = target.info.classSymbol.caseAccessors.filter(_.isGetter) - val assigns = accessors map (x => ref(fieldsByAccessors(x)).becomes(ev.select(x))) - Block(assigns, ev) - } - } // Need to eval-once and update fields. - - } - } - - def followCases(t: Symbol, limit: Int = 0): Symbol = if (t.symbol.is(Label)) { - // TODO: this can create cycles, see ./tests/pos/rbtree.scala - if (limit > 100 && limit > forwarderWritesTo.size + 1) NoSymbol - // There may be cycles in labels, that never in the end write to a valdef(the value is always on stack) - // there's not much we can do here, except finding such cases and bailing out - // there may not be a cycle bigger that hashmapSize > 1 - else followCases(forwarderWritesTo.getOrElse(t.symbol, NoSymbol), limit + 1) - } else t - - hasPerfectRHS.clear() - // checkGood.clear() - gettersCalled.clear() - - val res: Context => Tree => Tree = {localCtx => (t: Tree) => t match { - case ddef: DefDef if ddef.symbol.is(Label) => - newMappings.get(followCases(ddef.symbol)) match { - case Some(mappings) => - tpd.cpy.DefDef(ddef)(rhs = splitWrites(ddef.rhs, followCases(ddef.symbol))) - case _ => ddef - } - case a: ValDef if toSplit.contains(a.symbol) => - toSplit -= a.symbol - // Break ValDef apart into fields + boxed value - val newFields = newMappings(a.symbol).values.toSet - Thicket( - newFields.map(x => ValDef(x.asTerm, defaultValue(x.symbol.info.widenDealias))).toList ::: - List(tpd.cpy.ValDef(a)(rhs = splitWrites(a.rhs, a.symbol)))) - case ass: Assign => - newMappings.get(ass.lhs.symbol) match { - case None => ass - case Some(mapping) => - val updates = mapping.filter(x => x._1.is(CaseAccessor)).map(x => ref(x._2).becomes(ref(ass.lhs.symbol).select(x._1))).toList - Thicket(ass :: updates) - } - case sel @ Select(rec, _) if (t.symbol.isGetter && !t.symbol.is(Mutable)) || - (t.symbol.maybeOwner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) || - (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) => - newMappings.getOrElse(rec.symbol, Map.empty).get(sel.symbol) match { - case None => t - case Some(newSym) => ref(newSym) - } - case t => t - }} - - res - } - ("inlineLocalObjects", BeforeAndAfterErasure, visitor, transformer) - } - - private def collectTypeTests(t: Tree)(implicit ctx: Context): List[(Symbol, Type)] = { - def recur(t: Tree): List[(Symbol, Type)] = - t match { - case Apply(x, _) if (x.symbol == defn.Boolean_! || x.symbol == defn.Boolean_||) => List.empty - case Apply(fun @ Select(x, _), y) if (fun.symbol == defn.Boolean_&&) => recur(x) ++ recur(y.head) - case TypeApply(fun @ Select(x, _), List(tp)) if fun.symbol eq defn.Any_isInstanceOf => - if (x.symbol.exists && !x.symbol.owner.isClass && !x.symbol.is(Method|Mutable)) - (x.symbol, tp.tpe) :: Nil - else Nil - case _ => List.empty - } - recur(t) - } - - private def collectNullTests(t: Tree)(implicit ctx: Context): List[Symbol] = { - def recur(t: Tree): List[Symbol] = - t match { - case Apply(x, _) if (x.symbol == defn.Boolean_! || x.symbol == defn.Boolean_||) => List.empty - case Apply(fun @ Select(x, _), y) if (fun.symbol == defn.Boolean_&&) => recur(x) ++ recur(y.head) - case Apply(fun @ Select(x, _), List(tp)) if fun.symbol eq defn.Object_ne => - if (x.symbol.exists && !x.symbol.owner.isClass && !x.symbol.is(Method|Mutable)) - x.symbol :: Nil - else Nil - case _ => List.empty - } - recur(t) - } - - /** Eliminated casts and equality tests whose results can be locally - * determined at compile time: - * - * - a.asInstanceOf[T] → a when we know that a: T - * - Simplify (a == null) and (a != null) when the result is statically known - */ - val dropGoodCasts: Optimization = { implicit ctx: Context => - val transformer: Transformer = () => localCtx => { - case t @ If(cond, thenp, elsep) => - val newTypeTested = collectTypeTests(cond) - val nullTested = collectNullTests(cond).toSet - val testedMap = newTypeTested.foldRight[Map[Symbol, List[Type]]](Map.empty) { case (x, y) => - y + ((x._1, x._2 :: y.getOrElse(x._1, Nil))) - } - val dropGoodCastsInStats = new TreeMap() { - override def transform(tree: Tree)(implicit ctx: Context): Tree = { - def applyCondition(fun: Select, tree: Tree, const: Constant): Boolean = - const.tag == Constants.NullTag && - (fun.symbol == defn.Object_eq || fun.symbol == defn.Object_ne) && - (nullTested.contains(tree.symbol)) - - def applyBody(fun: Select): Tree = - if (fun.symbol == defn.Object_eq) Literal(Constant(false)) - else Literal(Constant(true)) - - super.transform(tree) match { - case t: Block => - val nstats = t.stats.filterConserve({ - case TypeApply(fun @ Select(rec, _), List(tp)) - if fun.symbol == defn.Any_asInstanceOf => - !testedMap.getOrElse(rec.symbol, Nil).exists(x => x <:< tp.tpe) - case _ => true - }) - if (nstats eq t.stats) t - else Block(nstats, t.expr) - case Apply(fun @ Select(lhs, _), List(Literal(const))) if applyCondition(fun, lhs, const) => - applyBody(fun) - case Apply(fun @ Select(Literal(const), _), List(rhs)) if applyCondition(fun, rhs, const) => - applyBody(fun) - case t => t - } - } - } - val nthenp = dropGoodCastsInStats.transform(thenp) - - tpd.cpy.If(t)(thenp = nthenp, elsep = elsep) - case t => t - } - ("dropGoodCasts", BeforeAndAfterErasure, NoVisitor, transformer) - } - - /** Eliminated null checks based on the following observations: - * - * - (this) cannot be null - * - (new C) cannot be null - * - literal is either null itself or non null - * - fallsback to `tpe.isNotNull`, which will eventually be true for non nullable types. - * - in (a.call; a == null), the first call throws a NPE if a is null; the test can be removed. - */ - val removeUnnecessaryNullChecks: Optimization = { implicit ctx: Context => - val initializedVals = mutable.HashSet[Symbol]() - val checkGood = mutable.HashMap[Symbol, Set[Symbol]]() - def isGood(t: Symbol) = { - t.exists && initializedVals.contains(t) && { - var changed = true - var set = Set(t) - while (changed) { - val oldSet = set - set = set ++ set.flatMap(x => checkGood.getOrElse(x, Nil)) - changed = set != oldSet - } - !set.exists(x => !initializedVals.contains(x)) - } - } - val visitor: Visitor = { - case vd: ValDef => - val rhs = vd.rhs - val rhsName = rhs.symbol.name - if (!vd.symbol.is(Mutable) && !rhs.isEmpty) { - def checkNonNull(t: Tree, target: Symbol): Boolean = t match { - case Block(_ , expr) => checkNonNull(expr, target) - case If(_, thenp, elsep) => checkNonNull(thenp, target) && checkNonNull(elsep, target) - case t: New => true - case t: Apply if t.symbol.isPrimaryConstructor => true - case t: Literal => t.const.value != null - case t: This => true - case t: Ident if !t.symbol.owner.isClass => - checkGood.put(target, checkGood.getOrElse(target, Set.empty) + t.symbol) - true - case t: Apply if !t.symbol.owner.isClass => - checkGood.put(target, checkGood.getOrElse(target, Set.empty) + t.symbol) - true - case t: Typed => - checkNonNull(t.expr, target) - case _ => t.tpe.isNotNull - } - if (checkNonNull(vd.rhs, vd.symbol)) - initializedVals += vd.symbol - } - case t: Tree => - } - - def isNullLiteral(tree: Tree) = tree match { - case literal: Literal => - literal.const.tag == Constants.NullTag - case _ => false - } - val transformer: Transformer = () => localCtx0 => { - implicit val ctx: Context = localCtx0 - val transformation: Tree => Tree = { - case check@Apply(Select(lhs, _), List(rhs)) => - val sym = check.symbol - if ( ((sym == defn.Object_eq) || (sym == defn.Object_ne)) && - ((isNullLiteral(lhs) && isGood(rhs.symbol)) || (isNullLiteral(rhs) && isGood(lhs.symbol)))) { - if (sym == defn.Object_eq) Block(List(lhs, rhs), Literal(Constant(false))) - else if(sym == defn.Object_ne) Block(List(lhs, rhs), Literal(Constant(true))) - else check - } else check - case t => t - } - transformation - } - ("removeUnnecessaryNullChecks", BeforeErasure, visitor, - transformer) - } - - /** Every pure statement preceding a ??? can be removed. - * - * This optimization makes it rather tricky meaningful examples since the - * compiler will often be able to reduce them to a single main with ???... - */ - val bubbleUpNothing: Optimization = { implicit ctx: Context => - object Notathing { - def unapply(t: Tree): Option[Tree] = Option(lookup(t)) - def lookup(t: Tree): Tree = t match { - case x if x.tpe.derivesFrom(defn.NothingClass) => t - case Typed(x, _) => lookup(x) - case Block(_, x) => lookup(x) - case _ => null - } - } - def notathing(t: Tree): Boolean = t match { - case Notathing(_) => true - case _ => false - } - val transformer: Transformer = () => localCtx => { - case t @ Apply(Select(Notathing(qual), _), args) => - Typed(qual, TypeTree(t.tpe)) - // This case leads to complications with multiple argument lists, - // how to do you rewrites tree.witType(???)(ctx).withType(???)(ctx) - // using Ycheckable steps? - - // Solution: only transform when having a complete application, - // steal code from tailRec - - // case t @ Apply(Select(qual, _), args) if args.exists(notathing) => - // val (keep, noth :: other) = args.span(x => !notathing(x)) - // Block(qual :: keep, Typed(noth, TypeTree(t.tpe))) - case Assign(_, rhs) if notathing(rhs) => - rhs - case t @ If(Notathing(cond), _, _) => - Typed(cond, TypeTree(t.tpe)) - case b: Block if b.stats.exists(x => !x.isDef && notathing(x)) => - val (keep, noth :: other) = b.stats.span(x => x.isDef || !notathing(x)) - val keepDefs = other.filter(x => x.isDef) - val body = keep ::: keepDefs - Typed(Block(body, noth), TypeTree(b.tpe)) - case t => t - } - ("bubbleUpNothing", BeforeErasure, NoVisitor, transformer) - } - - private def keepOnlySideEffects(t: Tree)(implicit ctx: Context): Tree = { - t match { - case l: Literal => - EmptyTree - case t: This => - EmptyTree - case Typed(exp, tpe) => - keepOnlySideEffects(exp) - case t @ If(cond, thenp, elsep) => - val nthenp = keepOnlySideEffects(thenp) - val nelsep = keepOnlySideEffects(elsep) - if (thenp.isEmpty && elsep.isEmpty) keepOnlySideEffects(cond) - else tpd.cpy.If(t)( - thenp = nthenp.orElse(if (thenp.isInstanceOf[Literal]) thenp else unitLiteral), - elsep = nelsep.orElse(if (elsep.isInstanceOf[Literal]) elsep else unitLiteral)) - case Select(rec, _) if - (t.symbol.isGetter && !t.symbol.is(Mutable | Lazy)) || - (t.symbol.owner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) || - (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) => - keepOnlySideEffects(rec) // Accessing a field of a product - case s @ Select(qual, name) if - // !name.eq(nme.TYPE_) && // Keep the .TYPE added by ClassOf, would be needed for AfterErasure - !t.symbol.is(Mutable | Lazy) && !t.symbol.is(Method) => - keepOnlySideEffects(qual) - case Block(List(t: DefDef), s: Closure) => - EmptyTree - case bl @ Block(stats, expr) => - val stats1 = stats.mapConserve(keepOnlySideEffects) - val stats2 = if (stats1 ne stats) stats1.filter(x=>x ne EmptyTree) else stats1 - val expr2: Tree = expr match { - case t: Literal if t.tpe.derivesFrom(defn.UnitClass) => expr - case _ => keepOnlySideEffects(expr).orElse(unitLiteral) - } - tpd.cpy.Block(bl)(stats2, expr2) - case t: Ident if !t.symbol.is(Method | Lazy) && !t.symbol.info.isInstanceOf[ExprType] || Analysis.effectsDontEscape(t) => - desugarIdent(t) match { - case Some(t) if !(t.qualifier.symbol.is(Flags.JavaDefined) && t.qualifier.symbol.is(Flags.Package)) => t - case _ => EmptyTree - } - case app: Apply if app.fun.symbol.is(Label) && !app.tpe.finalResultType.derivesFrom(defn.UnitClass) => - // This is "the scary hack". It changes the return type to Unit, then - // invalidates the denotation cache. Because this optimization only - // operates locally, this should be fine. - val denot = app.fun.symbol.denot - if (!denot.info.finalResultType.derivesFrom(defn.UnitClass)) { - val newLabelType = app.symbol.info match { - case mt: MethodType => - mt.derivedLambdaType(mt.paramNames, mt.paramInfos, defn.UnitType) - case et: ExprType => - et.derivedExprType(defn.UnitType) - } - val newD = app.symbol.asSymDenotation.copySymDenotation(info = newLabelType) - newD.installAfter(this) - } - - ref(app.symbol).appliedToArgs(app.args) - case t @ Apply(fun, _) if Analysis.effectsDontEscape(t) => - def getArgsss(a: Tree): List[Tree] = a match { - case a: Apply => getArgsss(a.fun) ::: a.args - case _ => Nil - } - def getSel(t: Tree): Tree = {t match { - case t: Apply => getSel(t.fun) - case t: Select => t.qualifier - case t: TypeApply => getSel(t.fun) - case _ => t - }} - val args = getArgsss(t) - val rec = getSel(t) - val prefix = rec match { - case t: New => - args.map(keepOnlySideEffects) - case _ => - rec :: args.map(keepOnlySideEffects) - } - Block(prefix, unitLiteral) - case t @ TypeApply(Select(rec, _), List(testType)) if t.symbol.eq(defn.Any_asInstanceOf) && testType.tpe.widenDealias.typeSymbol.exists => - val receiverType = TypeErasure.erasure(rec.tpe) - val erazedTestedType = TypeErasure.erasure(testType.tpe) - if (receiverType.derivesFrom(erazedTestedType.typeSymbol)) - EmptyTree - else t - case _ => t - } - } - - /** Removes side effect free statements in blocks. */ - val dropNoEffects: Optimization = { implicit ctx: Context => - val transformer: Transformer = () => localCtx => { - case Block(Nil, expr) => expr - case a: Block => - val newStats0 = a.stats.mapConserve(keepOnlySideEffects) - val newStats1 = if (newStats0 eq a.stats) newStats0 else newStats0.flatMap { - case x: Block => x.stats ::: List(x.expr) - case EmptyTree => Nil - case t => t :: Nil - } - val (newStats2, newExpr) = a.expr match { - case Block(stats2, expr) => (newStats1 ++ stats2, expr) - case _ => (newStats1, a.expr) - } - if (newStats2.nonEmpty) - tpd.cpy.Block(a)(stats = newStats2, newExpr) - else newExpr - case a: DefDef => - if (a.symbol.info.finalResultType.derivesFrom(defn.UnitClass) && - !a.rhs.tpe.derivesFrom(defn.UnitClass) && - !a.rhs.tpe.derivesFrom(defn.NothingClass)) { - def insertUnit(t: Tree) = { - if (!t.tpe.derivesFrom(defn.UnitClass)) Block(t :: Nil, unitLiteral) - else t - } - tpd.cpy.DefDef(a)(rhs = insertUnit(keepOnlySideEffects(a.rhs)), tpt = TypeTree(defn.UnitType)) - } else a - case t => t - } - // BoxedUnit messes up this phase after erasure - ("dropNoEffects", BeforeErasure, NoVisitor, transformer) - } - - /** Inlines LabelDef which are used exactly once. */ - val inlineLabelsCalledOnce: Optimization = { implicit ctx: Context => - val timesUsed = mutable.HashMap[Symbol, Int]() - val defined = mutable.HashMap[Symbol, DefDef]() - - val visitor: Visitor = { - case defdef: DefDef if defdef.symbol.is(Label) => - var isRecursive = false - defdef.rhs.foreachSubTree(x => if (x.symbol == defdef.symbol) isRecursive = true) - if (!isRecursive) defined.put(defdef.symbol, defdef) - case t: Apply if t.symbol.is(Label) => - val b4 = timesUsed.getOrElseUpdate(t.symbol, 0) - timesUsed.put(t.symbol, b4 + 1) - case _ => - } - - val transformer: Transformer = () => localCtx => { - case a: Apply => - defined.get(a.symbol) match { - case None => a - case Some(defDef) if a.symbol.is(Label) && timesUsed.getOrElse(a.symbol, 0) == 1 && a.symbol.info.paramInfoss == List(Nil) => - simplify.println(s"Inlining labeldef ${defDef.name}") - defDef.rhs.changeOwner(defDef.symbol, localCtx.owner) - case Some(defDef) if defDef.rhs.isInstanceOf[Literal] => - defDef.rhs - case Some(_) => - a - } - case a: DefDef if (a.symbol.is(Label) && timesUsed.getOrElse(a.symbol, 0) == 1 && defined.contains(a.symbol)) => - simplify.println(s"Dropping labeldef (used once) ${a.name} ${timesUsed.get(a.symbol)}") - defined.put(a.symbol, a) - EmptyTree - case a: DefDef if (a.symbol.is(Label) && timesUsed.getOrElse(a.symbol, 0) == 0 && defined.contains(a.symbol)) => - simplify.println(s"Dropping labeldef (never used) ${a.name} ${timesUsed.get(a.symbol)}") - EmptyTree - case t => t - } - ("inlineLabelsCalledOnce", BeforeErasure, visitor, transformer) - } - - /** Rewrites pairs of consecutive LabelDef jumps by jumping directly to the target. */ - val jumpjump: Optimization = { implicit ctx: Context => - // Optimize label defs that call other label-defs - val defined = mutable.HashMap[Symbol, Symbol]() - - val visitor: Visitor = { - case defdef: DefDef if defdef.symbol.is(Label) => - defdef.rhs match { - case Apply(t, args) if t.symbol.is(Label) && - TypeErasure.erasure(defdef.symbol.info.finalResultType).classSymbol == - TypeErasure.erasure(t.symbol.info.finalResultType).classSymbol && - args.size == defdef.vparamss.map(_.size).sum && - args.zip(defdef.vparamss.flatten).forall(x => x._1.symbol eq x._2.symbol) && - !(defdef.symbol eq t.symbol) => - defined(defdef.symbol) = t.symbol - case _ => - } - case _ => - } - - val transformer: Transformer = () => localCtx => { - case a: Apply if defined.contains(a.fun.symbol)=> - defined.get(a.symbol) match { - case None => a - case Some(fwd) => - ref(fwd).appliedToArgs(a.args) - } - case a: DefDef if defined.contains(a.symbol) => - simplify.println(s"Dropping ${a.symbol.showFullName} as forwarder to ${defined(a.symbol).showFullName}") - EmptyTree - case t => t - } - ("jumpjump", BeforeAndAfterErasure, visitor, transformer) - } - - /** Inlines Option methods whose result is known statically. */ - val inlineOptions: Optimization = { implicit ctx: Context => - val somes = mutable.HashMap[Symbol, Tree]() - val nones = mutable.HashSet[Symbol]() - - val visitor: Visitor = { - case valdef: ValDef if !valdef.symbol.is(Mutable) && - valdef.rhs.isInstanceOf[Apply] && valdef.rhs.tpe.derivesFrom(defn.SomeClass) && - valdef.rhs.symbol.isPrimaryConstructor => - val Apply(_, value) = valdef.rhs - somes(valdef.symbol) = value.head - - case valdef: ValDef if !valdef.symbol.is(Mutable) && - valdef.rhs.isInstanceOf[Apply] && valdef.rhs.tpe.derivesFrom(defn.NoneClass) => - nones += valdef.symbol - case _ => - } - - val transformer: Transformer = () => localCtx => tree => { - def rewriteSelect(x: Tree) = x match { - case Select(rec, nm) if nm == nme.get && somes.contains(rec.symbol) => - somes(rec.symbol) - case Select(rec, nm) if nm == nme.isDefined && - (/*rec.tpe.derivesFrom(defn.SomeClass) ||*/ somes.contains(rec.symbol)) => - Literal(Constant(true)) - case Select(rec, nm) if nm == nme.isEmpty && - (/*rec.tpe.derivesFrom(defn.SomeClass) ||*/ somes.contains(rec.symbol)) => - Literal(Constant(false)) - - case Select(rec, nm) if nm == nme.get && nones.contains(rec.symbol) => - ref(defn.NoneModuleRef) - case Select(rec, nm) if nm == nme.isDefined && - (/*rec.tpe.derivesFrom(defn.NoneClass) || */ nones.contains(rec.symbol)) => - Literal(Constant(false)) - case Select(rec, nm) if nm == nme.isEmpty && - (/*rec.tpe.derivesFrom(defn.NoneClass) ||*/ nones.contains(rec.symbol)) => - Literal(Constant(true)) - case t => t - } - def dropApply(a: Tree): Tree = a match { - case Apply(fun, Nil) => fun - case _ => a - } - val old = dropApply(tree) - val nw = rewriteSelect(old) - if (nw ne old) nw - else tree - } - ("inlineOptions", BeforeErasure, visitor, transformer) - } - - /** Rewrite vars with exactly one assignment as vals. */ - val valify: Optimization = { implicit ctx: Context => - // Either a duplicate or a read through series of immutable fields. - val defined: mutable.Map[Symbol, ValDef] = mutable.Map() - val firstRead: mutable.Map[Symbol, RefTree] = mutable.Map() - val firstWrite: mutable.Map[Symbol, Assign] = mutable.Map() - val secondWrite: mutable.Map[Symbol, Assign] = mutable.Map() - val visitor: Visitor = { - case t: ValDef if t.symbol.is(Mutable, Lazy) && !t.symbol.is(Method) && !t.symbol.owner.isClass => - if (isPureExpr(t.rhs)) - defined(t.symbol) = t - - case t: RefTree if t.symbol.exists && !t.symbol.is(Method) && !t.symbol.owner.isClass => - if (!firstWrite.contains(t.symbol)) firstRead(t.symbol) = t - - case t @ Assign(l, expr) if !l.symbol.is(Method) && !l.symbol.owner.isClass => - if (!firstRead.contains(l.symbol)) { - if (firstWrite.contains(l.symbol)) { - if (!secondWrite.contains(l.symbol)) - secondWrite(l.symbol) = t - } else if (!expr.existsSubTree(x => x match { - case tree: RefTree if x.symbol == l.symbol => firstRead(l.symbol) = tree; true - case _ => false - })) { - firstWrite(l.symbol) = t - } - } - case _ => - } - - val transformer: Transformer = () => localCtx => { - val transformation: Tree => Tree = { - case t: Block => // Drop non-side-effecting stats - val valdefs = t.stats.collect { - case t: ValDef if defined.contains(t.symbol) => t - } - - val assigns = t.stats.filter { - case t @ Assign(lhs, r) => - firstWrite.contains(lhs.symbol) && !secondWrite.contains(lhs.symbol) - case _ => false - } - - val pairs = valdefs.flatMap(x => assigns.find(y => y.asInstanceOf[Assign].lhs.symbol == x.symbol) match { - case Some(y: Assign) => List((x, y)) - case _ => Nil - }) - - val valsToDrop = pairs.map(_._1).toSet - val assignsToReplace: Map[Assign, ValDef] = pairs.map(_.swap).toMap - - val newStats = t.stats.mapConserve { - case x: ValDef if valsToDrop.contains(x) => EmptyTree - case t: Assign => assignsToReplace.get(t) match { - case Some(vd) => - val newD = vd.symbol.asSymDenotation.copySymDenotation(initFlags = vd.symbol.flags.&~(Mutable)) - newD.installAfter(this) - ValDef(vd.symbol.asTerm, t.rhs) - case None => t - } - case x => x - } - - if (newStats eq t.stats) t - else tpd.cpy.Block(t)(newStats, t.expr) - case tree => tree - } - - transformation - } - ("valify", BeforeAndAfterErasure, visitor, transformer) - } - - /** Inline vals */ - val devalify: Optimization = { implicit ctx: Context => - val timesUsed = mutable.HashMap[Symbol, Int]() - val timesUsedAsType = mutable.HashMap[Symbol, Int]() - - val defined = mutable.HashSet[Symbol]() - val usedInInnerClass = mutable.HashMap[Symbol, Int]() - // Either a duplicate or a read through series of immutable fields - val copies = mutable.HashMap[Symbol, Tree]() - def visitType(tp: Type): Unit = { - tp.foreachPart(x => x match { - case TermRef(NoPrefix, _) => - val b4 = timesUsedAsType.getOrElseUpdate(x.termSymbol, 0) - timesUsedAsType.put(x.termSymbol, b4 + 1) - case _ => - }) - } - def doVisit(tree: Tree, used: mutable.HashMap[Symbol, Int]): Unit = tree match { - case valdef: ValDef if !valdef.symbol.is(Param | Mutable | Module | Lazy) && - valdef.symbol.exists && !valdef.symbol.owner.isClass => - defined += valdef.symbol - - dropCasts(valdef.rhs) match { - case t: Tree if readingOnlyVals(t) => - copies.put(valdef.symbol, valdef.rhs) - case _ => - } - visitType(valdef.symbol.info) - case t: New => - val normalized = t.tpt.tpe.normalizedPrefix - val symIfExists = normalized.termSymbol - val b4 = used.getOrElseUpdate(symIfExists, 0) - used.put(symIfExists, b4 + 1) - visitType(normalized) - - case valdef: ValDef if valdef.symbol.exists && !valdef.symbol.owner.isClass && - !valdef.symbol.is(Param | Module | Lazy) => - // TODO: handle params after constructors. Start changing public signatures by eliminating unused arguments. - defined += valdef.symbol - - case valdef: ValDef => visitType(valdef.symbol.info) - case t: DefDef => visitType(t.symbol.info) - case t: Typed => visitType(t.tpt.tpe) - case t: TypeApply => t.args.foreach(x => visitType(x.tpe)) - case t: RefTree => - val b4 = used.getOrElseUpdate(t.symbol, 0) - used.put(t.symbol, b4 + 1) - case _ => - } - - val visitor: Visitor = { tree => - def crossingClassBoundaries(t: Tree): Boolean = t match { - case _: New => true - case _: Template => true - case _ => false - } - // We shouldn't inline `This` nodes, which we approximate by not inlining - // anything across class boundaries. To do so, we visit every class a - // second time and record what's used in the usedInInnerClass Set. - if (crossingClassBoundaries(tree)) { - // Doing a foreachSubTree(tree) here would work, but would also - // be exponential for deeply nested classes. Instead we do a short - // circuit traversal that doesn't visit further nested classes. - val reVisitClass = new TreeAccumulator[Unit] { - def apply(u: Unit, t: Tree)(implicit ctx: Context): Unit = { - doVisit(t, usedInInnerClass) - if (!crossingClassBoundaries(t)) - foldOver((), t) - } - } - reVisitClass.foldOver((), tree) - } - doVisit(tree, timesUsed) - } - - val transformer: Transformer = () => localCtx => { - val valsToDrop = defined -- timesUsed.keySet -- timesUsedAsType.keySet - val copiesToReplaceAsDuplicates = copies.filter { x => - val rhs = dropCasts(x._2) - rhs.isInstanceOf[Literal] || (!rhs.symbol.owner.isClass && !rhs.symbol.is(Method | Mutable)) - } -- timesUsedAsType.keySet - // TODO: if a non-synthetic val is duplicate of a synthetic one, rename a synthetic one and drop synthetic flag? - - val copiesToReplaceAsUsedOnce = - timesUsed.filter(x => x._2 == 1) - .flatMap(x => copies.get(x._1) match { - case Some(tr) => List((x._1, tr)) - case None => Nil - }) -- timesUsedAsType.keySet - - val replacements = copiesToReplaceAsDuplicates ++ copiesToReplaceAsUsedOnce -- usedInInnerClass.keySet - - val deepReplacer = new TreeMap() { - override def transform(tree: Tree)(implicit ctx: Context): Tree = { - def loop(tree: Tree): Tree = - tree match { - case t: RefTree if replacements.contains(t.symbol) => - loop(replacements(t.symbol)) - case _ => tree - } - super.transform(loop(tree)) - } - } - - val transformation: Tree => Tree = { - case t: ValDef if valsToDrop.contains(t.symbol) => - // TODO: Could emit a warning for non synthetic code? This valdef is - // probably something users would want to remove from source... - simplify.println(s"Dropping definition of ${t.symbol.showFullName} as not used") - t.rhs.changeOwner(t.symbol, t.symbol.owner) - case t: ValDef if replacements.contains(t.symbol) => - simplify.println(s"Dropping definition of ${t.symbol.showFullName} as an alias") - EmptyTree - case t: New => - val symIfExists = t.tpt.tpe.normalizedPrefix.termSymbol - if (replacements.contains(symIfExists)) { - val newPrefix = deepReplacer.transform(replacements(symIfExists)) - val newTpt = t.tpt.tpe match { - case t: NamedType => - t.derivedSelect(newPrefix.tpe) - } - New(newTpt) - } - else t - case t: RefTree if !t.symbol.is(Method | Param | Mutable) => - if (replacements.contains(t.symbol)) - deepReplacer.transform(replacements(t.symbol)).ensureConforms(t.tpe.widen) - else t - case tree => tree - } - - transformation - } - // See tests/pos/devalify.scala for examples of why this needs to be after Erasure. - ("devalify", BeforeAndAfterErasure, visitor, transformer) - } - - /** Inline val with exactly one assignment to a var. For example: - * - * { - * val l = - * var r = l - * // code not using l - * } - * - * becomes: - * - * { - * var r = - * // code not using l - * } - */ - val varify: Optimization = { implicit ctx: Context => - val paramsTimesUsed = mutable.HashMap[Symbol, Int]() - val possibleRenames = mutable.HashMap[Symbol, Set[Symbol]]() - val visitor: Visitor = { - case t: ValDef - if t.symbol.is(Param) => - paramsTimesUsed += (t.symbol -> 0) - case valDef: ValDef - if valDef.symbol.is(Mutable) => - valDef.rhs.foreachSubTree { subtree => - if (paramsTimesUsed.contains(subtree.symbol) && - valDef.symbol.info.widenDealias <:< subtree.symbol.info.widenDealias) { - val newSet = possibleRenames.getOrElse(valDef.symbol, Set.empty) + subtree.symbol - possibleRenames.put(valDef.symbol, newSet) - } - } - case t: RefTree - if paramsTimesUsed.contains(t.symbol) => - val param = t.symbol - val current = paramsTimesUsed.get(param) - current foreach { c => paramsTimesUsed += (param -> (c + 1)) } - case _ => - } - val transformer: Transformer = () => localCtx => { - val paramCandidates = paramsTimesUsed.filter(kv => kv._2 == 1).keySet - val renames: Map[Symbol, Symbol] = possibleRenames.iterator - .map(kv => (kv._1, kv._2.intersect(paramCandidates))) - .filter(x => x._2.nonEmpty) - .map(x => (x._1, x._2.head)) - .toMap - val transformation: Tree => Tree = { - case t: RefTree - if renames.contains(t.symbol) => - ref(renames(t.symbol)) - case t: ValDef - if renames.contains(t.symbol) => - val replaced = renames(t.symbol) - if (t.rhs.symbol == replaced) EmptyTree - else ref(replaced).becomes(t.rhs) - case t: ValDef - if paramCandidates.contains(t.symbol) => - t.symbol.flags = Mutable - t - case t => t - } - transformation - } - ("varify", AfterErasure, visitor, transformer) - } - - private def unzip4[A, B, C, D](seq: Seq[(A, B, C, D)]): (Seq[A], Seq[B], Seq[C], Seq[D]) = { - val listBuilderA = new mutable.ListBuffer[A]() - val listBuilderB = new mutable.ListBuffer[B]() - val listBuilderC = new mutable.ListBuffer[C]() - val listBuilderD = new mutable.ListBuffer[D]() - seq.foreach { x => - listBuilderA += x._1 - listBuilderB += x._2 - listBuilderC += x._3 - listBuilderD += x._4 - } - (listBuilderA.toList, listBuilderB.toList, listBuilderC.toList, listBuilderD.toList) - } -} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala b/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala new file mode 100644 index 000000000000..2d3c4de475de --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala @@ -0,0 +1,57 @@ +package dotty.tools.dotc +package transform.localopt + +import core.Contexts.Context +import core.Symbols._ +import ast.Trees._ + +/** Every pure statement preceding a ??? can be removed. + * + * This optimisation makes it rather tricky meaningful examples since the + * compiler will often be able to reduce them to a single main with ???... + */ +class BubbleUpNothing(implicit val ctx: Context) extends Optimisation { + import ast.tpd._ + + val visitor = NoVisitor + + def transformer(localCtx: Context): Tree => Tree = { + case t @ Apply(Select(Notathing(qual), _), args) => + Typed(qual, TypeTree(t.tpe)) + // This case leads to complications with multiple argument lists, + // how to do you rewrites tree.witType(???)(ctx).withType(???)(ctx) + // using Ycheckable steps? + + // Solution: only transform when having a complete application, + // steal code from tailRec + + // case t @ Apply(Select(qual, _), args) if args.exists(notathing) => + // val (keep, noth :: other) = args.span(x => !notathing(x)) + // Block(qual :: keep, Typed(noth, TypeTree(t.tpe))) + case Assign(_, rhs) if notathing(rhs) => + rhs + case t @ If(Notathing(cond), _, _) => + Typed(cond, TypeTree(t.tpe)) + case b: Block if b.stats.exists(x => !x.isDef && notathing(x)) => + val (keep, noth :: other) = b.stats.span(x => x.isDef || !notathing(x)) + val keepDefs = other.filter(x => x.isDef) + val body = keep ::: keepDefs + Typed(Block(body, noth), TypeTree(b.tpe)) + case t => t + } + + object Notathing { + def unapply(t: Tree): Option[Tree] = Option(lookup(t)) + def lookup(t: Tree): Tree = t match { + case x if x.tpe.derivesFrom(defn.NothingClass) => t + case Typed(x, _) => lookup(x) + case Block(_, x) => lookup(x) + case _ => null + } + } + + def notathing(t: Tree): Boolean = t match { + case Notathing(_) => true + case _ => false + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala b/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala new file mode 100644 index 000000000000..20e1a62818a1 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala @@ -0,0 +1,212 @@ +package dotty.tools.dotc +package transform.localopt + +import core.Contexts.Context +import core.Symbols._ +import core.Types._ +import typer.ConstFold +import ast.Trees._ +import Simplify.desugarIdent + +/** Various constant folding. + * + * - Starts/ends with the constant folding implemented in typer (ConstFold). + * + * - (if) specific optimisation that propagate booleans, negation, and factor + * out (nested) if with equivalent branches wrt to isSimilar (using &&,||). + * + * - Constant propagation over pattern matching. + */ + class ConstantFold(implicit val ctx: Context) extends Optimisation { + import ast.tpd._ + + val visitor = NoVisitor + + def transformer(localCtx: Context): Tree => Tree = { x => preEval(x) match { + // TODO: include handling of isInstanceOf similar to one in IsInstanceOfEvaluator + // TODO: include methods such as Int.int2double(see ./tests/pos/harmonize.scala) + case If(cond1, thenp, elsep) if isSimilar(thenp, elsep) => + Block(cond1 :: Nil, thenp) + + case If(cond1, If(cond2, thenp2, elsep2), elsep1) if isSimilar(elsep1, elsep2) => + If(cond1.select(defn.Boolean_&&).appliedTo(cond2), thenp2, elsep1) + + case If(cond1, If(cond2, thenp2, elsep2), elsep1) if isSimilar(elsep1, thenp2) => + If(cond1.select(defn.Boolean_!).ensureApplied.select(defn.Boolean_||).appliedTo(cond2), elsep1, elsep2) + + case If(cond1, thenp1, If(cond2, thenp2, elsep2)) if isSimilar(thenp1, thenp2) => + If(cond1.select(defn.Boolean_||).appliedTo(cond2), thenp1, elsep2) + + case If(cond1, thenp1, If(cond2, thenp2, elsep2)) if isSimilar(thenp1, elsep2) => + If(cond1.select(defn.Boolean_||).appliedTo(cond2.select(defn.Boolean_!).ensureApplied), thenp1, thenp2) + + case If(t: Literal, thenp, elsep) => + if (t.const.booleanValue) thenp + else elsep + + case ift @ If(cond, thenp: Literal, elsep: Literal) + if isBool(ift.tpe) && thenp.const.booleanValue && !elsep.const.booleanValue => + cond + + // the lower two are disabled, as it may make the isSimilar rule not apply for a nested structure of iffs. + // see the example below: + // (b1, b2) match { + // case (true, true) => true + // case (false, false) => true + // case _ => false + // } + // case ift @ If(cond, thenp: Literal, elsep) + // if isBool(ift.tpe) && thenp.const.booleanValue => + // if (thenp.const.booleanValue) + // cond.select(defn.Boolean_||).appliedTo(elsep) + // else // thenp is false, this tree is bigger then the original + // cond.select(defn.Boolean_!).ensureApplied.select(defn.Boolean_&&).appliedTo(elsep) + // case ift @ If(cond, thenp, elsep :Literal) if + // isBool(ift.tpe) && !elsep.const.booleanValue => + // cond.select(defn.Boolean_&&).appliedTo(elsep) + // the other case ins't handled intentionally. See previous case for explanation + + case If(t @ Select(recv, _), thenp, elsep) if t.symbol eq defn.Boolean_! => + If(recv, elsep, thenp) + + case If(t @ Apply(Select(recv, _), Nil), thenp, elsep) if t.symbol eq defn.Boolean_! => + If(recv, elsep, thenp) + + // TODO: similar trick for comparisons. + // TODO: handle comparison with min\max values + case Apply(meth1 @ Select(Apply(meth2 @ Select(rec, _), Nil), _), Nil) + if meth1.symbol == defn.Boolean_! && meth2.symbol == defn.Boolean_! => + rec + + case meth1 @ Select(meth2 @ Select(rec, _), _) + if meth1.symbol == defn.Boolean_! && meth2.symbol == defn.Boolean_! && !ctx.erasedTypes => + rec + + case t @ Apply(Select(lhs, _), List(rhs)) => + val sym = t.symbol + (lhs, rhs) match { + case (lhs, Literal(_)) if !lhs.isInstanceOf[Literal] && defn.CommutativePrimitiveOperations().contains(sym) => + rhs.select(sym).appliedTo(lhs) + + case (l, _) if (sym == defn.Boolean_&&) && isConst(l.tpe) => + val const = asConst(l.tpe).value.booleanValue + if (const) Block(lhs :: Nil, rhs) + else l + + case (l, x: Literal) if sym == defn.Boolean_== && isBool(l.tpe) && isBool(x.tpe) => + if (x.const.booleanValue) l + else l.select(defn.Boolean_!).ensureApplied + + case (l, x: Literal) if sym == defn.Boolean_!= && isBool(l.tpe) && isBool(x.tpe) => + if (!x.const.booleanValue) l + else l.select(defn.Boolean_!).ensureApplied + + case (x: Literal, l) if sym == defn.Boolean_== && isBool(l.tpe) && isBool(x.tpe) => + if (x.const.booleanValue) l + else l.select(defn.Boolean_!).ensureApplied + + case (x: Literal, l) if sym == defn.Boolean_!= && isBool(l.tpe) && isBool(x.tpe) => + if (!x.const.booleanValue) l + else l.select(defn.Boolean_!).ensureApplied + + case (l: Literal, _) if (sym == defn.Boolean_||) && isConst(l.tpe) => + val const = asConst(l.tpe).value.booleanValue + if (l.const.booleanValue) l + else Block(lhs :: Nil, rhs) + + // case (Literal(Constant(1)), _) if sym == defn.Int_* => rhs + // case (Literal(Constant(0)), _) if sym == defn.Int_+ => rhs + // case (Literal(Constant(1L)), _) if sym == defn.Long_* => rhs + // case (Literal(Constant(0L)), _) if sym == defn.Long_+ => rhs + // // TODO: same for float, double, short + // // TODO: empty string concat + // // TODO: disctribute & reorder constants + // // TODO: merge subsequent casts + // case (_, Literal(Constant(1))) if sym == defn.Int_/ => lhs + // case (_, Literal(Constant(1L))) if sym == defn.Long_/ => lhs + // case (_, Literal(Constant(0))) if sym == defn.Int_/ => + // Block(List(lhs), + // ref(defn.throwMethod).appliedTo(New(defn.ArithmeticExceptionClass.typeRef, defn.ArithmeticExceptionClass_stringConstructor, Literal(Constant("/ by zero")) :: Nil))) + // case (_, Literal(Constant(0L))) if sym == defn.Long_/ => + // Block(List(lhs), + // ref(defn.throwMethod).appliedTo(New(defn.ArithmeticExceptionClass.typeRef, defn.ArithmeticExceptionClass_stringConstructor, Literal(Constant("/ by zero")) :: Nil))) + + case _ => t + } + + // This case can only be triggered when running Simplify before pattern matching: + // case t: Match + // if t.selector.tpe.isInstanceOf[ConstantType] && + // t.cases.forall { x => + // x.pat.tpe.isInstanceOf[ConstantType] || (isWildcardArg(x.pat) && x.guard.isEmpty) + // } => + // val selectorValue = t.selector.tpe.asInstanceOf[ConstantType].value + // val better = t.cases.find(x => isWildcardArg(x.pat) || (x.pat.tpe.asInstanceOf[ConstantType].value eq selectorValue)) + // if (better.nonEmpty) better.get.body + // else t + + case t: Literal => t + case t: CaseDef => t + case t if !isPureExpr(t) => t + case t => + val s = ConstFold.apply(t) + if ((s ne null) && s.tpe.isInstanceOf[ConstantType]) { + val constant = s.tpe.asInstanceOf[ConstantType].value + Literal(constant) + } else t + } + } + + def preEval(t: Tree) = { + if (t.isInstanceOf[Literal] || t.isInstanceOf[CaseDef] || !isPureExpr(t)) t + else { + val s = ConstFold.apply(t) + if ((s ne null) && s.tpe.isInstanceOf[ConstantType]) { + val constant = s.tpe.asInstanceOf[ConstantType].value + Literal(constant) + } else t + } + } + + def isSimilar(t1: Tree, t2: Tree): Boolean = t1 match { + case t1: Apply => + t2 match { + case t2: Apply => + (t1.symbol == t2.symbol) && + (t1.args zip t2.args).forall(x => isSimilar(x._1, x._2)) && + isSimilar(t1.fun, t2.fun) + case _ => false + } + case t1: Ident => + desugarIdent(t1) match { + case Some(t) => + val t2i = t2 match { + case t2: Ident => desugarIdent(t2).getOrElse(t2) + case _ => t2 + } + isSimilar(t, t2i) + case None => t1.symbol eq t2.symbol + } + case t1: Select => t2 match { + case t2: Select => + (t1.symbol eq t2.symbol) && + isSimilar(t1.qualifier, t2.qualifier) + case t2: Ident => desugarIdent(t2) match { + case Some(t2) => isSimilar(t1, t2) + case None => false + } + case _ => false + } + case t1: Literal => t2 match { + case t2: Literal => + t1.const.tag == t2.const.tag && + t1.const.value == t2.const.value + case _ => false + } + case _ => false + } + + def isBool(tpe: Type): Boolean = tpe.derivesFrom(defn.BooleanClass) + def isConst(tpe: Type): Boolean = tpe.isInstanceOf[ConstantType] + def asConst(tpe: Type): ConstantType = tpe.asInstanceOf[ConstantType] +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala new file mode 100644 index 000000000000..020346932e4e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala @@ -0,0 +1,212 @@ +package dotty.tools.dotc +package transform.localopt + +import core.Constants.Constant +import core.Contexts.Context +import core.Flags._ +import core.NameOps._ +import core.Symbols._ +import core.Types._ +import ast.Trees._ +import scala.collection.mutable +import config.Printers.simplify +import Simplify.desugarIdent +import transform.SymUtils._ + +/** Inline vals */ +class Devalify(implicit val ctx: Context) extends Optimisation { + import ast.tpd._ + + val timesUsed = mutable.HashMap[Symbol, Int]() + val timesUsedAsType = mutable.HashMap[Symbol, Int]() + + val defined = mutable.HashSet[Symbol]() + val usedInInnerClass = mutable.HashMap[Symbol, Int]() + // Either a duplicate or a read through series of immutable fields + val copies = mutable.HashMap[Symbol, Tree]() + + def visitType(tp: Type): Unit = { + tp.foreachPart(x => x match { + case TermRef(NoPrefix, _) => + val b4 = timesUsedAsType.getOrElseUpdate(x.termSymbol, 0) + timesUsedAsType.put(x.termSymbol, b4 + 1) + case _ => + }) + } + + def doVisit(tree: Tree, used: mutable.HashMap[Symbol, Int]): Unit = tree match { + case valdef: ValDef if !valdef.symbol.is(Param | Mutable | Module | Lazy) && + valdef.symbol.exists && !valdef.symbol.owner.isClass => + defined += valdef.symbol + + dropCasts(valdef.rhs) match { + case t: Tree if readingOnlyVals(t) => + copies.put(valdef.symbol, valdef.rhs) + case _ => + } + visitType(valdef.symbol.info) + case t: New => + val normalized = t.tpt.tpe.normalizedPrefix + val symIfExists = normalized.termSymbol + val b4 = used.getOrElseUpdate(symIfExists, 0) + used.put(symIfExists, b4 + 1) + visitType(normalized) + + case valdef: ValDef if valdef.symbol.exists && !valdef.symbol.owner.isClass && + !valdef.symbol.is(Param | Module | Lazy) => + // TODO: handle params after constructors. Start changing public signatures by eliminating unused arguments. + defined += valdef.symbol + + case valdef: ValDef => visitType(valdef.symbol.info) + case t: DefDef => visitType(t.symbol.info) + case t: Typed => visitType(t.tpt.tpe) + case t: TypeApply => t.args.foreach(x => visitType(x.tpe)) + case t: RefTree => + val b4 = used.getOrElseUpdate(t.symbol, 0) + used.put(t.symbol, b4 + 1) + case _ => + } + + def visitor: Tree => Unit = { tree => + def crossingClassBoundaries(t: Tree): Boolean = t match { + case _: New => true + case _: Template => true + case _ => false + } + // We shouldn't inline `This` nodes, which we approximate by not inlining + // anything across class boundaries. To do so, we visit every class a + // second time and record what's used in the usedInInnerClass Set. + if (crossingClassBoundaries(tree)) { + // Doing a foreachSubTree(tree) here would work, but would also + // be exponential for deeply nested classes. Instead we do a short + // circuit traversal that doesn't visit further nested classes. + val reVisitClass = new TreeAccumulator[Unit] { + def apply(u: Unit, t: Tree)(implicit ctx: Context): Unit = { + doVisit(t, usedInInnerClass) + if (!crossingClassBoundaries(t)) + foldOver((), t) + } + } + reVisitClass.foldOver((), tree) + } + doVisit(tree, timesUsed) + } + + def transformer(localCtx: Context): Tree => Tree = { + val valsToDrop = defined -- timesUsed.keySet -- timesUsedAsType.keySet + val copiesToReplaceAsDuplicates = copies.filter { x => + val rhs = dropCasts(x._2) + rhs.isInstanceOf[Literal] || (!rhs.symbol.owner.isClass && !rhs.symbol.is(Method | Mutable)) + } -- timesUsedAsType.keySet + // TODO: if a non-synthetic val is duplicate of a synthetic one, rename a synthetic one and drop synthetic flag? + + val copiesToReplaceAsUsedOnce = + timesUsed.filter(x => x._2 == 1) + .flatMap(x => copies.get(x._1) match { + case Some(tr) => List((x._1, tr)) + case None => Nil + }) -- timesUsedAsType.keySet + + val replacements = copiesToReplaceAsDuplicates ++ copiesToReplaceAsUsedOnce -- usedInInnerClass.keySet + + val deepReplacer = new TreeMap() { + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + def loop(tree: Tree): Tree = + tree match { + case t: RefTree if replacements.contains(t.symbol) => + loop(replacements(t.symbol)) + case _ => tree + } + super.transform(loop(tree)) + } + } + + val transformation: Tree => Tree = { + case t: ValDef if valsToDrop.contains(t.symbol) => + // TODO: Could emit a warning for non synthetic code? This valdef is + // probably something users would want to remove from source... + simplify.println(s"Dropping definition of ${t.symbol.showFullName} as not used") + t.rhs.changeOwner(t.symbol, t.symbol.owner) + case t: ValDef if replacements.contains(t.symbol) => + simplify.println(s"Dropping definition of ${t.symbol.showFullName} as an alias") + EmptyTree + case t: New => + val symIfExists = t.tpt.tpe.normalizedPrefix.termSymbol + if (replacements.contains(symIfExists)) { + val newPrefix = deepReplacer.transform(replacements(symIfExists)) + val newTpt = t.tpt.tpe match { + case t: NamedType => + t.derivedSelect(newPrefix.tpe) + } + New(newTpt) + } + else t + case t: RefTree if !t.symbol.is(Method | Param | Mutable) => + if (replacements.contains(t.symbol)) + deepReplacer.transform(replacements(t.symbol)).ensureConforms(t.tpe.widen) + else t + case tree => tree + } + + transformation + } + + def dropCasts(t: Tree)(implicit ctx: Context): Tree = t match { + // case TypeApply(aio@Select(rec, nm), _) if aio.symbol == defn.Any_asInstanceOf => dropCasts(rec) + case Typed(t, tpe) => t + case _ => t + } + + def readingOnlyVals(t: Tree)(implicit ctx: Context): Boolean = dropCasts(t) match { + case Typed(exp, _) => readingOnlyVals(exp) + case TypeApply(fun @ Select(rec, _), List(tp)) => + if ((fun.symbol eq defn.Any_asInstanceOf) && rec.tpe.derivesFrom(tp.tpe.classSymbol)) + readingOnlyVals(rec) + else false + case Apply(Select(rec, _), Nil) => + def isGetterOfAImmutableField = t.symbol.isGetter && !t.symbol.is(Mutable) + def isCaseClassWithVar = t.symbol.info.decls.exists(_.is(Mutable)) + def isAccessingProductField = t.symbol.exists && + t.symbol.owner.derivesFrom(defn.ProductClass) && + t.symbol.owner.is(CaseClass) && + t.symbol.name.isSelectorName && + !isCaseClassWithVar // Conservative Covers case class A(var x: Int) + def isImmutableCaseAccessor = t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable) + if (isGetterOfAImmutableField || isAccessingProductField || isImmutableCaseAccessor) + readingOnlyVals(rec) + else false + case Select(rec, _) if t.symbol.is(Method) => + if (t.symbol.isGetter && !t.symbol.is(Mutable)) readingOnlyVals(rec) // getter of a immutable field + else if (t.symbol.owner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) { + def isImmutableField = { + val fieldId = t.symbol.name.toString.drop(1).toInt - 1 + !t.symbol.owner.caseAccessors(ctx)(fieldId).is(Mutable) + } + if (isImmutableField) readingOnlyVals(rec) // accessing a field of a product + else false + } else if (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) + readingOnlyVals(rec) + else false + case Select(qual, _) if !t.symbol.is(Mutable) => + readingOnlyVals(qual) + case t: Ident if !t.symbol.is(Mutable) && !t.symbol.is(Method) && !t.symbol.info.dealias.isInstanceOf[ExprType] => + desugarIdent(t) match { + case Some(t) => readingOnlyVals(t) + case None => true + } + case t: This => true + // null => false, or the following fails devalify: + // trait I { + // def foo: Any = null + // } + // object Main { + // def main = { + // val s: I = null + // s.foo + // } + // } + case Literal(Constant(null)) => false + case t: Literal => true + case _ => false + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala b/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala new file mode 100644 index 000000000000..21bb6eab6b6f --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala @@ -0,0 +1,94 @@ +package dotty.tools.dotc +package transform.localopt + +import core._ +import core.Constants.Constant +import core.Contexts.Context +import core.Decorators._ +import core.Symbols._ +import core.Types._ +import core.Flags._ +import ast.Trees._ +import transform.SymUtils._ + +/** Eliminated casts and equality tests whose results can be locally + * determined at compile time: + * + * - a.asInstanceOf[T] → a when we know that a: T + * - Simplify (a == null) and (a != null) when the result is statically known + */ + class DropGoodCasts(implicit val ctx: Context) extends Optimisation { + import ast.tpd._ + + val visitor = NoVisitor + + def transformer(localCtx: Context): Tree => Tree = { + case t @ If(cond, thenp, elsep) => + val newTypeTested = collectTypeTests(cond) + val nullTested = collectNullTests(cond).toSet + val testedMap = newTypeTested.foldRight[Map[Symbol, List[Type]]](Map.empty) { case (x, y) => + y + ((x._1, x._2 :: y.getOrElse(x._1, Nil))) + } + val dropGoodCastsInStats = new TreeMap() { + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + def applyCondition(fun: Select, tree: Tree, const: Constant): Boolean = + const.tag == Constants.NullTag && + (fun.symbol == defn.Object_eq || fun.symbol == defn.Object_ne) && + (nullTested.contains(tree.symbol)) + + def applyBody(fun: Select): Tree = + if (fun.symbol == defn.Object_eq) Literal(Constant(false)) + else Literal(Constant(true)) + + super.transform(tree) match { + case t: Block => + val nstats = t.stats.filterConserve({ + case TypeApply(fun @ Select(rec, _), List(tp)) + if fun.symbol == defn.Any_asInstanceOf => + !testedMap.getOrElse(rec.symbol, Nil).exists(x => x <:< tp.tpe) + case _ => true + }) + if (nstats eq t.stats) t + else Block(nstats, t.expr) + case Apply(fun @ Select(lhs, _), List(Literal(const))) if applyCondition(fun, lhs, const) => + applyBody(fun) + case Apply(fun @ Select(Literal(const), _), List(rhs)) if applyCondition(fun, rhs, const) => + applyBody(fun) + case t => t + } + } + } + val nthenp = dropGoodCastsInStats.transform(thenp) + + cpy.If(t)(thenp = nthenp, elsep = elsep) + case t => t + } + + def collectTypeTests(t: Tree)(implicit ctx: Context): List[(Symbol, Type)] = { + def recur(t: Tree): List[(Symbol, Type)] = + t match { + case Apply(x, _) if (x.symbol == defn.Boolean_! || x.symbol == defn.Boolean_||) => List.empty + case Apply(fun @ Select(x, _), y) if (fun.symbol == defn.Boolean_&&) => recur(x) ++ recur(y.head) + case TypeApply(fun @ Select(x, _), List(tp)) if fun.symbol eq defn.Any_isInstanceOf => + if (x.symbol.exists && !x.symbol.owner.isClass && !x.symbol.is(Method|Mutable)) + (x.symbol, tp.tpe) :: Nil + else Nil + case _ => List.empty + } + recur(t) + } + + def collectNullTests(t: Tree)(implicit ctx: Context): List[Symbol] = { + def recur(t: Tree): List[Symbol] = + t match { + case Apply(x, _) if (x.symbol == defn.Boolean_! || x.symbol == defn.Boolean_||) => List.empty + case Apply(fun @ Select(x, _), y) if (fun.symbol == defn.Boolean_&&) => recur(x) ++ recur(y.head) + case Apply(fun @ Select(x, _), List(tp)) if fun.symbol eq defn.Object_ne => + if (x.symbol.exists && !x.symbol.owner.isClass && !x.symbol.is(Method|Mutable)) + x.symbol :: Nil + else Nil + case _ => List.empty + } + recur(t) + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala new file mode 100644 index 000000000000..618e1ae7d399 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala @@ -0,0 +1,189 @@ +package dotty.tools.dotc +package transform.localopt + +import core.TypeErasure +import core.Contexts.Context +import core.NameOps._ +import core.Symbols._ +import core.Types._ +import core.Flags._ +import ast.Trees._ +import Simplify.desugarIdent + +/** Removes side effect free statements in blocks. + * Note: BoxedUnit currently messes up this phase when run after erasure + */ +class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) extends Optimisation { + import ast.tpd._ + + val visitor = NoVisitor + + def transformer(localCtx: Context): Tree => Tree = { + case Block(Nil, expr) => expr + case a: Block => + val newStats0 = a.stats.mapConserve(keepOnlySideEffects) + val newStats1 = if (newStats0 eq a.stats) newStats0 else newStats0.flatMap { + case x: Block => x.stats ::: List(x.expr) + case EmptyTree => Nil + case t => t :: Nil + } + val (newStats2, newExpr) = a.expr match { + case Block(stats2, expr) => (newStats1 ++ stats2, expr) + case _ => (newStats1, a.expr) + } + if (newStats2.nonEmpty) + cpy.Block(a)(stats = newStats2, newExpr) + else newExpr + case a: DefDef => + if (a.symbol.info.finalResultType.derivesFrom(defn.UnitClass) && + !a.rhs.tpe.derivesFrom(defn.UnitClass) && + !a.rhs.tpe.derivesFrom(defn.NothingClass)) { + def insertUnit(t: Tree) = { + if (!t.tpe.derivesFrom(defn.UnitClass)) Block(t :: Nil, unitLiteral) + else t + } + cpy.DefDef(a)(rhs = insertUnit(keepOnlySideEffects(a.rhs)), tpt = TypeTree(defn.UnitType)) + } else a + case t => t + } + + def keepOnlySideEffects(t: Tree): Tree = { + t match { + case l: Literal => + EmptyTree + case t: This => + EmptyTree + case Typed(exp, tpe) => + keepOnlySideEffects(exp) + case t @ If(cond, thenp, elsep) => + val nthenp = keepOnlySideEffects(thenp) + val nelsep = keepOnlySideEffects(elsep) + if (thenp.isEmpty && elsep.isEmpty) keepOnlySideEffects(cond) + else cpy.If(t)( + thenp = nthenp.orElse(if (thenp.isInstanceOf[Literal]) thenp else unitLiteral), + elsep = nelsep.orElse(if (elsep.isInstanceOf[Literal]) elsep else unitLiteral)) + case Select(rec, _) if + (t.symbol.isGetter && !t.symbol.is(Mutable | Lazy)) || + (t.symbol.owner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) || + (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) => + keepOnlySideEffects(rec) // Accessing a field of a product + case s @ Select(qual, name) if + // !name.eq(nme.TYPE_) && // Keep the .TYPE added by ClassOf, would be needed for AfterErasure + !t.symbol.is(Mutable | Lazy) && !t.symbol.is(Method) => + keepOnlySideEffects(qual) + case Block(List(t: DefDef), s: Closure) => + EmptyTree + case bl @ Block(stats, expr) => + val stats1 = stats.mapConserve(keepOnlySideEffects) + val stats2 = if (stats1 ne stats) stats1.filter(x=>x ne EmptyTree) else stats1 + val expr2: Tree = expr match { + case t: Literal if t.tpe.derivesFrom(defn.UnitClass) => expr + case _ => keepOnlySideEffects(expr).orElse(unitLiteral) + } + cpy.Block(bl)(stats2, expr2) + case t: Ident if !t.symbol.is(Method | Lazy) && !t.symbol.info.isInstanceOf[ExprType] || effectsDontEscape(t) => + desugarIdent(t) match { + case Some(t) if !(t.qualifier.symbol.is(JavaDefined) && t.qualifier.symbol.is(Package)) => t + case _ => EmptyTree + } + case app: Apply if app.fun.symbol.is(Label) && !app.tpe.finalResultType.derivesFrom(defn.UnitClass) => + // This is "the scary hack". It changes the return type to Unit, then + // invalidates the denotation cache. Because this optimisation only + // operates locally, this should be fine. + val denot = app.fun.symbol.denot + if (!denot.info.finalResultType.derivesFrom(defn.UnitClass)) { + val newLabelType = app.symbol.info match { + case mt: MethodType => + mt.derivedLambdaType(mt.paramNames, mt.paramInfos, defn.UnitType) + case et: ExprType => + et.derivedExprType(defn.UnitType) + } + val newD = app.symbol.asSymDenotation.copySymDenotation(info = newLabelType) + newD.installAfter(simplifyPhase) + } + + ref(app.symbol).appliedToArgs(app.args) + case t @ Apply(fun, _) if effectsDontEscape(t) => + def getArgsss(a: Tree): List[Tree] = a match { + case a: Apply => getArgsss(a.fun) ::: a.args + case _ => Nil + } + def getSel(t: Tree): Tree = {t match { + case t: Apply => getSel(t.fun) + case t: Select => t.qualifier + case t: TypeApply => getSel(t.fun) + case _ => t + }} + val args = getArgsss(t) + val rec = getSel(t) + val prefix = rec match { + case t: New => + args.map(keepOnlySideEffects) + case _ => + rec :: args.map(keepOnlySideEffects) + } + Block(prefix, unitLiteral) + case t @ TypeApply(Select(rec, _), List(testType)) if t.symbol.eq(defn.Any_asInstanceOf) && testType.tpe.widenDealias.typeSymbol.exists => + val receiverType = TypeErasure.erasure(rec.tpe) + val erazedTestedType = TypeErasure.erasure(testType.tpe) + if (receiverType.derivesFrom(erazedTestedType.typeSymbol)) + EmptyTree + else t + case _ => t + } + } + + val constructorWhiteList: Set[String] = Set( + "scala.Tuple2", + "scala.Tuple3", + "scala.Tuple4", + "scala.Tuple5", + "scala.Tuple6", + "scala.Tuple7", + "scala.Tuple8", + "scala.Tuple9", + "scala.Tuple10", + "scala.Tuple11", + "scala.Tuple12", + "scala.Tuple13", + "scala.Tuple14", + "scala.Tuple15", + "scala.Tuple16", + "scala.Tuple17", + "scala.Tuple18", + "scala.Tuple19", + "scala.Tuple20", + "scala.Tuple21", + "scala.Tuple22", + "scala.Some" + ) + + val moduleWhiteList: Set[String] = + constructorWhiteList.map(x => x + "$") + + val methodsWhiteList: List[String] = List( + "java.lang.Math.min", + "java.lang.Math.max", + "java.lang.Object.eq", + "java.lang.Object.ne", + "scala.Boolean.$amp$amp", + "scala.runtime.BoxesRunTime.unboxToBoolean", + "scala.runtime.BoxesRunTime.unboxToLong", + "scala.runtime.BoxesRunTime.unboxToInt", + "scala.runtime.BoxesRunTime.unboxToShort", + "scala.runtime.BoxesRunTime.unboxToDouble", + "scala.runtime.BoxesRunTime.unboxToChar", + "scala.runtime.BoxesRunTime.unboxToFloat" + ) + + def effectsDontEscape(t: Tree): Boolean = t match { + case Apply(fun, args) if fun.symbol.isConstructor && constructorWhiteList.contains(fun.symbol.owner.fullName.toString) => + true + case Apply(fun, args) if methodsWhiteList.contains(fun.symbol.fullName.toString) => + true + case Ident(_) if t.symbol.is(Module) && (t.symbol.is(Synthetic) || moduleWhiteList.contains(t.symbol.fullName.toString)) => + true + case _ => + false + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala new file mode 100644 index 000000000000..100f39c6cd80 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala @@ -0,0 +1,134 @@ +package dotty.tools.dotc +package transform.localopt + +import core.Constants.Constant +import core.Contexts.Context +import core.StdNames._ +import core.Symbols._ +import core.Types._ +import core.Flags._ +import ast.Trees._ +import transform.SymUtils._ +import Simplify.desugarIdent + +/** Inline case class specific methods using desugarings assumptions. */ +class InlineCaseIntrinsics(implicit val ctx: Context) extends Optimisation { + import ast.tpd._ + + val visitor = NoVisitor + + def transformer(localCtx: Context): Tree => Tree = { + // For synthetic applies on case classes (both dotty/scalac) + // - CC.apply(args) → new CC(args) + case a: Apply if !a.tpe.isInstanceOf[MethodicType] && + a.symbol.is(Synthetic) && + a.symbol.owner.is(Module) && + (a.symbol.name == nme.apply) && + a.symbol.owner.companionClass.is(CaseClass) && + !a.tpe.derivesFrom(defn.EnumClass) && + (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => + + def unrollArgs(t: Tree, l: List[List[Tree]]): List[List[Tree]] = t match { + case Apply(t, args) => unrollArgs(t, args :: l) + case _ => l + } + val argss = unrollArgs(a.fun, a.args :: Nil) + def rollInArgs(l: List[List[Tree]], fun: Tree): Tree = l match { + case head :: tail => rollInArgs(tail, fun.appliedToArgs(head)) + case _ => fun + } + val constructor = a.symbol.owner.companionClass.primaryConstructor.asTerm + evalReciever(a, rollInArgs(argss.tail, New(a.tpe.widenDealias, constructor, argss.head))) + + // For synthetic dotty unapplies on case classes: + // - CC.unapply(arg): CC → arg + // - CC.unapply(arg): Boolean → true, dotty only + // - CC.unapply(arg): Option[CC] → new Some(new scala.TupleN(arg._1, ..., arg._N)) + case a: Apply if a.symbol.is(Synthetic) && + a.symbol.owner.is(Module) && + (a.symbol.name == nme.unapply) && + a.symbol.owner.companionClass.is(CaseClass) && + !a.tpe.derivesFrom(defn.EnumClass) && + (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => + + val args = a.args.head + val isDottyUnapply = !a.symbol.owner.is(Scala2x) + val isScalaOptionUnapply = + a.tpe.derivesFrom(defn.OptionClass) && + a.args.head.tpe.derivesFrom(a.symbol.owner.companionClass) + + if (isDottyUnapply) { // dotty only + if (a.tpe.derivesFrom(defn.BooleanClass)) + // CC.unapply(arg): Boolean → true + evalReciever(a, Literal(Constant(true))) + else + // CC.unapply(arg): CC → arg + evalReciever(a, a.args.head) + } + else if (isScalaOptionUnapply) { + // CC.unapply(arg): Option[CC] → new Some(new scala.TupleN(arg._1, ..., arg._N)) + // The output is defined as a Tree => Tree to go thought tpd.evalOnce. + def some(e: Tree) = { + val accessors = e.tpe.widenDealias.classSymbol.caseAccessors.filter(_.is(Method)) + val fields = accessors.map(x => e.select(x).ensureApplied) + val tplType = a.tpe.baseArgTypes(defn.OptionClass).head + val someTpe = a.tpe.translateParameterized(defn.OptionClass, defn.SomeClass) + + if (fields.tail.nonEmpty) + New(someTpe, New(tplType, fields) :: Nil) + else // scalac does not have Tuple1 + New(someTpe, fields.head :: Nil) + } + val none = ref(defn.NoneModuleRef) + def isNull(e: Tree) = e.select(defn.Object_eq).appliedTo(Literal(Constant(null))) + def fi(e: Tree) = If(isNull(e), none, some(e)) + evalReciever(a, evalOnce(a.args.head)(fi)) + } + else a + + // Seq.unapplySeq(arg) → new Some(arg) + // Where Seq is any companion of type <: SeqFactoryClass + case a: Apply if (a.symbol.name == nme.unapplySeq) && + a.symbol.owner.derivesFrom(defn.SeqFactoryClass) && + a.symbol.extendedOverriddenSymbols.isEmpty && + (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => + + def reciever(t: Tree): Type = t match { + case t: Apply => reciever(t.fun) + case t: TypeApply => reciever(t.fun) + case t: Ident => desugarIdent(t) match { + case Some(t) => reciever(t) + case _ => NoType + } + case t: Select => t.qualifier.tpe.widenDealias + } + + val recv = reciever(a) + if (recv.typeSymbol.is(Module)) { + val someTpe = a.tpe.translateParameterized(defn.OptionClass, defn.SomeClass) + evalReciever(a, New(someTpe, a.args.head :: Nil)) + } + else a + case t => t + } + + // Apply fun may be a side-effectful function. E.g. a block, see tests/run/t4859.scala + // we need to maintain expressions that were in this block + def evalReciever(a: Apply, res: Tree) = { + def receiver(t: Tree): Tree = t match { + case TypeApply(fun, targs) if fun.symbol eq t.symbol => receiver(fun) + case Apply(fn, args) if fn.symbol == t.symbol => receiver(fn) + case Select(qual, _) => qual + case x => x + } + val recv = receiver(a) + if (recv.isEmpty || isPureRef(recv)) + res + else + Block(recv :: Nil, res) + } + + // To run this optimisation after erasure one would need to specialize it + // for constructor with outer pointer and values classes. There is probably + // no need to run this more than once. +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala new file mode 100644 index 000000000000..708948b0a0d7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala @@ -0,0 +1,50 @@ +package dotty.tools.dotc +package transform.localopt + +import core.Contexts.Context +import core.Symbols._ +import core.Flags._ +import transform.SymUtils._ +import scala.collection.mutable +import config.Printers.simplify + +/** Inlines LabelDef which are used exactly once. */ +class InlineLabelsCalledOnce(implicit val ctx: Context) extends Optimisation { + import ast.tpd._ + + val timesUsed = mutable.HashMap[Symbol, Int]() + val defined = mutable.HashMap[Symbol, DefDef]() + + val visitor: Tree => Unit = { + case defdef: DefDef if defdef.symbol.is(Label) => + var isRecursive = false + defdef.rhs.foreachSubTree(x => if (x.symbol == defdef.symbol) isRecursive = true) + if (!isRecursive) defined.put(defdef.symbol, defdef) + case t: Apply if t.symbol.is(Label) => + val b4 = timesUsed.getOrElseUpdate(t.symbol, 0) + timesUsed.put(t.symbol, b4 + 1) + case _ => + } + + def transformer(localCtx: Context): Tree => Tree = { + case a: Apply => + defined.get(a.symbol) match { + case None => a + case Some(defDef) if a.symbol.is(Label) && timesUsed.getOrElse(a.symbol, 0) == 1 && a.symbol.info.paramInfoss == List(Nil) => + simplify.println(s"Inlining labeldef ${defDef.name}") + defDef.rhs.changeOwner(defDef.symbol, localCtx.owner) + case Some(defDef) if defDef.rhs.isInstanceOf[Literal] => + defDef.rhs + case Some(_) => + a + } + case a: DefDef if (a.symbol.is(Label) && timesUsed.getOrElse(a.symbol, 0) == 1 && defined.contains(a.symbol)) => + simplify.println(s"Dropping labeldef (used once) ${a.name} ${timesUsed.get(a.symbol)}") + defined.put(a.symbol, a) + EmptyTree + case a: DefDef if (a.symbol.is(Label) && timesUsed.getOrElse(a.symbol, 0) == 0 && defined.contains(a.symbol)) => + simplify.println(s"Dropping labeldef (never used) ${a.name} ${timesUsed.get(a.symbol)}") + EmptyTree + case t => t + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLocalObjects.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLocalObjects.scala new file mode 100644 index 000000000000..dbb2772a4e9e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLocalObjects.scala @@ -0,0 +1,176 @@ +package dotty.tools.dotc +package transform.localopt + +import core.Constants.Constant +import core.Contexts.Context +import core.Decorators._ +import core.NameOps._ +import core.StdNames._ +import core.Symbols._ +import core.Flags._ +import ast.Trees._ +import scala.collection.mutable +import transform.SymUtils._ +import config.Printers.simplify + +/** Inline case classes as vals, this essentially (local) implements multi +* parameter value classes. The main motivation is to get ride of all the +* intermediate tuples coming from pattern matching expressions. +*/ +class InlineLocalObjects(implicit val ctx: Context) extends Optimisation { + import ast.tpd._ + + // In the end only calls constructor. Reason for unconditional inlining + val hasPerfectRHS = mutable.HashMap[Symbol, Boolean]() + // If all values have perfect RHS than key has perfect RHS + val checkGood = mutable.HashMap[Symbol, Set[Symbol]]() + val forwarderWritesTo = mutable.HashMap[Symbol, Symbol]() + val gettersCalled = mutable.HashSet[Symbol]() + + def followTailPerfect(t: Tree, symbol: Symbol): Unit = { + t match { + case Block(_, expr) => followTailPerfect(expr, symbol) + case If(_, thenp, elsep) => followTailPerfect(thenp, symbol); followTailPerfect(elsep, symbol); + case Apply(fun, _) if fun.symbol.isConstructor && t.tpe.widenDealias == symbol.info.widenDealias.finalResultType.widenDealias => + hasPerfectRHS(symbol) = true + case Apply(fun, _) if fun.symbol.is(Label) && (fun.symbol ne symbol) => + checkGood.put(symbol, checkGood.getOrElse(symbol, Set.empty) + fun.symbol) + // assert(forwarderWritesTo.getOrElse(t.symbol, symbol) == symbol) + forwarderWritesTo(t.symbol) = symbol + case t: Ident if !t.symbol.owner.isClass && (t.symbol ne symbol) => + checkGood.put(symbol, checkGood.getOrElse(symbol, Set.empty) + t.symbol) + case _ => + } + } + + val visitor: Tree => Unit = { + case vdef: ValDef if (vdef.symbol.info.classSymbol is CaseClass) && + !vdef.symbol.is(Lazy) && + !vdef.symbol.info.classSymbol.caseAccessors.exists(x => x.is(Mutable)) => + followTailPerfect(vdef.rhs, vdef.symbol) + + case Assign(lhs, rhs) if !lhs.symbol.owner.isClass => + checkGood.put(lhs.symbol, checkGood.getOrElse(lhs.symbol, Set.empty) + rhs.symbol) + + case t @ Select(qual, _) if (t.symbol.isGetter && !t.symbol.is(Mutable)) || + (t.symbol.maybeOwner.derivesFrom(defn.ProductClass) && t.symbol.maybeOwner.is(CaseClass) && t.symbol.name.isSelectorName) || + (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) => + gettersCalled(qual.symbol) = true + + case t: DefDef if t.symbol.is(Label) => + followTailPerfect(t.rhs, t.symbol) + + case _ => + } + + def transformer(localCtx: Context): Tree => Tree = { + var hasChanged = true + while(hasChanged) { + hasChanged = false + checkGood.foreach{case (key, values) => + values.foreach { value => + if (hasPerfectRHS.getOrElse(key, false)) { + hasChanged = !hasPerfectRHS.put(value, true).getOrElse(false) + } + } + } + } + + val newMappings: Map[Symbol, Map[Symbol, Symbol]] = + hasPerfectRHS.iterator.map(x => x._1).filter(x => !x.is(Method) && !x.is(Label) && gettersCalled.contains(x.symbol) && (x.symbol.info.classSymbol is CaseClass)) + .map { refVal => + simplify.println(s"replacing ${refVal.symbol.fullName} with stack-allocated fields") + var accessors = refVal.info.classSymbol.caseAccessors.filter(_.isGetter) // TODO: drop mutable ones + if (accessors.isEmpty) accessors = refVal.info.classSymbol.caseAccessors + val productAccessors = (1 to accessors.length).map(i => refVal.info.member(nme.productAccessorName(i)).symbol) // TODO: disambiguate + val newLocals = accessors.map(x => + // TODO: it would be nice to have an additional optimisation that + // TODO: is capable of turning those mutable ones into immutable in common cases + ctx.newSymbol(ctx.owner.enclosingMethod, (refVal.name + "$" + x.name).toTermName, Synthetic | Mutable, x.asSeenFrom(refVal.info).info.finalResultType.widenDealias) + ) + val fieldMapping = accessors zip newLocals + val productMappings = productAccessors zip newLocals + (refVal, (fieldMapping ++ productMappings).toMap) + }.toMap + val toSplit: mutable.Set[Symbol] = mutable.Set.empty ++ newMappings.keySet + + def splitWrites(t: Tree, target: Symbol): Tree = { + t match { + case tree@ Block(stats, expr) => cpy.Block(tree)(stats, splitWrites(expr, target)) + case tree@ If(_, thenp, elsep) => cpy.If(tree)(thenp = splitWrites(thenp, target), elsep = splitWrites(elsep, target)) + case Apply(sel , args) if sel.symbol.isConstructor && t.tpe.widenDealias == target.info.widenDealias.finalResultType.widenDealias => + val fieldsByAccessors = newMappings(target) + var accessors = target.info.classSymbol.caseAccessors.filter(_.isGetter) // TODO: when is this filter needed? + if (accessors.isEmpty) accessors = target.info.classSymbol.caseAccessors + val assigns = (accessors zip args) map (x => ref(fieldsByAccessors(x._1)).becomes(x._2)) + val recreate = sel.appliedToArgs(accessors.map(x => ref(fieldsByAccessors(x)))) + Block(assigns, recreate) + case Apply(fun, _) if fun.symbol.is(Label) => + t // Do nothing. It will do on its own. + case t: Ident if !t.symbol.owner.isClass && newMappings.contains(t.symbol) && t.symbol.info.classSymbol == target.info.classSymbol => + val fieldsByAccessorslhs = newMappings(target) + val fieldsByAccessorsrhs = newMappings(t.symbol) + val accessors = target.info.classSymbol.caseAccessors.filter(_.isGetter) + val assigns = accessors map (x => ref(fieldsByAccessorslhs(x)).becomes(ref(fieldsByAccessorsrhs(x)))) + Block(assigns, t) + // If `t` is itself split, push writes. + case _ => + evalOnce(t){ev => + if (ev.tpe.derivesFrom(defn.NothingClass)) ev + else { + val fieldsByAccessors = newMappings(target) + val accessors = target.info.classSymbol.caseAccessors.filter(_.isGetter) + val assigns = accessors map (x => ref(fieldsByAccessors(x)).becomes(ev.select(x))) + Block(assigns, ev) + } + } // Need to eval-once and update fields. + + } + } + + def followCases(t: Symbol, limit: Int = 0): Symbol = if (t.symbol.is(Label)) { + // TODO: this can create cycles, see ./tests/pos/rbtree.scala + if (limit > 100 && limit > forwarderWritesTo.size + 1) NoSymbol + // There may be cycles in labels, that never in the end write to a valdef(the value is always on stack) + // there's not much we can do here, except finding such cases and bailing out + // there may not be a cycle bigger that hashmapSize > 1 + else followCases(forwarderWritesTo.getOrElse(t.symbol, NoSymbol), limit + 1) + } else t + + hasPerfectRHS.clear() + // checkGood.clear() + gettersCalled.clear() + + val res: Tree => Tree = { + case ddef: DefDef if ddef.symbol.is(Label) => + newMappings.get(followCases(ddef.symbol)) match { + case Some(mappings) => + cpy.DefDef(ddef)(rhs = splitWrites(ddef.rhs, followCases(ddef.symbol))) + case _ => ddef + } + case a: ValDef if toSplit.contains(a.symbol) => + toSplit -= a.symbol + // Break ValDef apart into fields + boxed value + val newFields = newMappings(a.symbol).values.toSet + Thicket( + newFields.map(x => ValDef(x.asTerm, defaultValue(x.symbol.info.widenDealias))).toList ::: + List(cpy.ValDef(a)(rhs = splitWrites(a.rhs, a.symbol)))) + case ass: Assign => + newMappings.get(ass.lhs.symbol) match { + case None => ass + case Some(mapping) => + val updates = mapping.filter(x => x._1.is(CaseAccessor)).map(x => ref(x._2).becomes(ref(ass.lhs.symbol).select(x._1))).toList + Thicket(ass :: updates) + } + case t @ Select(rec, _) if (t.symbol.isGetter && !t.symbol.is(Mutable)) || + (t.symbol.maybeOwner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) || + (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) => + newMappings.getOrElse(rec.symbol, Map.empty).get(t.symbol) match { + case None => t + case Some(newSym) => ref(newSym) + } + case t => t + } + res + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineOptions.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineOptions.scala new file mode 100644 index 000000000000..64fea344471e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineOptions.scala @@ -0,0 +1,51 @@ +package dotty.tools.dotc +package transform.localopt + +import core.Constants.Constant +import core.Contexts.Context +import core.StdNames._ +import core.Symbols._ +import core.Flags._ +import ast.Trees._ +import scala.collection.mutable + +/** Inlines Option methods whose result is known statically. */ +class InlineOptions(implicit val ctx: Context) extends Optimisation { + import ast.tpd._ + + val somes = mutable.HashMap[Symbol, Tree]() + val nones = mutable.HashSet[Symbol]() + + val visitor: Tree => Unit = { + case valdef: ValDef if !valdef.symbol.is(Mutable) && + valdef.rhs.isInstanceOf[Apply] && valdef.rhs.tpe.derivesFrom(defn.SomeClass) && + valdef.rhs.symbol.isPrimaryConstructor => + val Apply(_, value) = valdef.rhs + somes(valdef.symbol) = value.head + + case valdef: ValDef if !valdef.symbol.is(Mutable) && + valdef.rhs.isInstanceOf[Apply] && valdef.rhs.tpe.derivesFrom(defn.NoneClass) => + nones += valdef.symbol + case _ => + } + + def transformer(localCtx: Context): Tree => Tree = { tree => + def rewriteSelect(x: Tree) = x match { + case Select(rec, nm) if nm == nme.get && somes.contains(rec.symbol) => somes(rec.symbol) + case Select(rec, nm) if nm == nme.isDefined && somes.contains(rec.symbol) => Literal(Constant(true)) + case Select(rec, nm) if nm == nme.isEmpty && somes.contains(rec.symbol) => Literal(Constant(false)) + case Select(rec, nm) if nm == nme.get && nones.contains(rec.symbol) => ref(defn.NoneModuleRef) + case Select(rec, nm) if nm == nme.isDefined && nones.contains(rec.symbol) => Literal(Constant(false)) + case Select(rec, nm) if nm == nme.isEmpty && nones.contains(rec.symbol) => Literal(Constant(true)) + case t => t + } + def dropApply(a: Tree): Tree = a match { + case Apply(fun, Nil) => fun + case _ => a + } + val old = dropApply(tree) + val nw = rewriteSelect(old) + if (nw ne old) nw + else tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala new file mode 100644 index 000000000000..dd1b2356af75 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala @@ -0,0 +1,49 @@ +package dotty.tools.dotc +package transform.localopt + +import core.TypeErasure +import core.Constants.Constant +import core.Contexts.Context +import core.Decorators._ +import core.Symbols._ +import ast.Trees._ +import scala.collection.mutable +import config.Printers.simplify +import core.Flags._ + +/** Rewrites pairs of consecutive LabelDef jumps by jumping directly to the target. */ +class Jumpjump(implicit val ctx: Context) extends Optimisation { + import ast.tpd._ + + val defined = mutable.HashMap[Symbol, Symbol]() + + val visitor: Tree => Unit = { + case defdef: DefDef if defdef.symbol.is(Label) => + defdef.rhs match { + case Apply(t, args) if t.symbol.is(Label) && + TypeErasure.erasure(defdef.symbol.info.finalResultType).classSymbol == + TypeErasure.erasure(t.symbol.info.finalResultType).classSymbol && + args.size == defdef.vparamss.map(_.size).sum && + args.zip(defdef.vparamss.flatten).forall(x => x._1.symbol eq x._2.symbol) && + !(defdef.symbol eq t.symbol) => + defined(defdef.symbol) = t.symbol + case _ => + } + case _ => + } + + def transformer(localCtx: Context): Tree => Tree = { + case a: Apply if defined.contains(a.fun.symbol) => + defined.get(a.symbol) match { + case None => a + case Some(fwd) => + ref(fwd).appliedToArgs(a.args) + } + + case a: DefDef if defined.contains(a.symbol) => + simplify.println(s"Dropping ${a.symbol.showFullName} as forwarder to ${defined(a.symbol).showFullName}") + EmptyTree + + case t => t + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala new file mode 100644 index 000000000000..fc8e58674513 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala @@ -0,0 +1,20 @@ +package dotty.tools.dotc +package transform.localopt + +import core.Contexts.Context +import ast.tpd.Tree + +trait Optimisation { + + /** Run first to gather information on Trees (using mutation) */ + def visitor: Tree => Unit + + /** Does the actual Tree => Tree transformation, possibly using a different + * context from the one using in Optimisation. + */ + def transformer(localCtx: Context): Tree => Tree + + def name: String = this.getClass.getName.init + + val NoVisitor: Tree => Unit = _ => () +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala b/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala new file mode 100644 index 000000000000..f99c22919341 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala @@ -0,0 +1,89 @@ +package dotty.tools.dotc +package transform.localopt + +import core.Constants.{Constant, NullTag} +import core.Contexts.Context +import core.Symbols._ +import core.Types._ +import core.Flags._ +import ast.Trees._ +import scala.collection.mutable + +/** Eliminated null checks based on the following observations: + * + * - (this) cannot be null + * - (new C) cannot be null + * - literal is either null itself or non null + * - fallsback to `tpe.isNotNull`, which will eventually be true for non nullable types. + * - in (a.call; a == null), the first call throws a NPE if a is null; the test can be removed. + */ + class RemoveUnnecessaryNullChecks(implicit val ctx: Context) extends Optimisation { + import ast.tpd._ + + val initializedVals = mutable.HashSet[Symbol]() + + val checkGood = mutable.HashMap[Symbol, Set[Symbol]]() + + def isGood(t: Symbol) = { + t.exists && initializedVals.contains(t) && { + var changed = true + var set = Set(t) + while (changed) { + val oldSet = set + set = set ++ set.flatMap(x => checkGood.getOrElse(x, Nil)) + changed = set != oldSet + } + !set.exists(x => !initializedVals.contains(x)) + } + } + + val visitor: Tree => Unit = { + case vd: ValDef => + val rhs = vd.rhs + val rhsName = rhs.symbol.name + if (!vd.symbol.is(Mutable) && !rhs.isEmpty) { + def checkNonNull(t: Tree, target: Symbol): Boolean = t match { + case Block(_ , expr) => checkNonNull(expr, target) + case If(_, thenp, elsep) => checkNonNull(thenp, target) && checkNonNull(elsep, target) + case t: New => true + case t: Apply if t.symbol.isPrimaryConstructor => true + case t: Literal => t.const.value != null + case t: This => true + case t: Ident if !t.symbol.owner.isClass => + checkGood.put(target, checkGood.getOrElse(target, Set.empty) + t.symbol) + true + case t: Apply if !t.symbol.owner.isClass => + checkGood.put(target, checkGood.getOrElse(target, Set.empty) + t.symbol) + true + case t: Typed => + checkNonNull(t.expr, target) + case _ => t.tpe.isNotNull + } + if (checkNonNull(vd.rhs, vd.symbol)) + initializedVals += vd.symbol + } + case t: Tree => + } + + + def transformer(localCtx: Context): Tree => Tree = { + implicit val ctx: Context = localCtx + def isNullLiteral(tree: Tree) = tree match { + case literal: Literal => + literal.const.tag == NullTag + case _ => false + } + val transformation: Tree => Tree = { + case check@Apply(Select(lhs, _), List(rhs)) => + val sym = check.symbol + if ( ((sym == defn.Object_eq) || (sym == defn.Object_ne)) && + ((isNullLiteral(lhs) && isGood(rhs.symbol)) || (isNullLiteral(rhs) && isGood(lhs.symbol)))) { + if (sym == defn.Object_eq) Block(List(lhs, rhs), Literal(Constant(false))) + else if(sym == defn.Object_ne) Block(List(lhs, rhs), Literal(Constant(true))) + else check + } else check + case t => t + } + transformation + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala new file mode 100644 index 000000000000..dcc247adb568 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala @@ -0,0 +1,102 @@ +package dotty.tools.dotc +package transform.localopt + +import core.Contexts.Context +import core.DenotTransformers.IdentityDenotTransformer +import core.Types._ +import transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo} +import config.Printers.simplify +import core.Flags._ +import ast.tpd + +/** 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. + */ +class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { + import tpd._ + override def phaseName: String = "simplify" + override val cpy = tpd.cpy + + def beforeErasure(implicit ctx: Context): List[Optimisation] = + new InlineCaseIntrinsics :: + new RemoveUnnecessaryNullChecks :: + new InlineOptions :: + new InlineLabelsCalledOnce :: + new Valify(this) :: + new Devalify :: + new Jumpjump :: + new DropGoodCasts :: + new DropNoEffects(this) :: + // new InlineLocalObjects :: // followCases needs to be fixed, see ./tests/pos/rbtree.scala + // new Varify :: // varify could stop other transformations from being applied. postponed. + // new BubbleUpNothing :: + new ConstantFold :: + Nil + + def afterErasure(implicit ctx: Context): List[Optimisation] = + // new InlineCaseIntrinsics :: + // new RemoveUnnecessaryNullChecks :: + // new InlineOptions :: + // new InlineLabelsCalledOnce :: + new Valify(this) :: + new Devalify :: + new Jumpjump :: + new DropGoodCasts :: + // new DropNoEffects(this) :: + // new InlineLocalObjects :: // followCases needs to be fixed, see ./tests/pos/rbtree.scala + // new Varify :: // varify could stop other transformations from being applied. postponed. + // new BubbleUpNothing :: + new ConstantFold :: + Nil + + // The entry point of local optimisation: DefDefs + override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = { + val ctx0 = ctx + if (ctx.settings.optimise.value && !tree.symbol.is(Label)) { + implicit val ctx: Context = ctx0.withOwner(tree.symbol(ctx0)) + val optimisations = if (ctx.erasedTypes) afterErasure else beforeErasure + + var rhs0 = tree.rhs + var rhs1: Tree = null + while (rhs1 ne rhs0) { + rhs1 = rhs0 + val context = ctx.withOwner(tree.symbol) + // TODO: fuse for performance + optimisations.foreach { optimisation => + rhs0.foreachSubTree(optimisation.visitor) + + val rhst = 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 + optimisation.transformer(ctx)(super.transform(tree)(innerCtx)) + } + }.transform(rhs0) + + if (rhst ne rhs0) { + simplify.println(s"${tree.symbol} was simplified by ${optimisation.name}: ${rhs0.show}") + simplify.println(s"became: ${rhst.show}") + } + rhs0 = rhst + } + } + if (rhs0 ne tree.rhs) tpd.cpy.DefDef(tree)(rhs = rhs0) + else tree + } else tree + } +} + +object Simplify { + import tpd._ + def desugarIdent(i: Ident)(implicit ctx: Context): Option[Select] = { + i.tpe match { + case TermRef(prefix: TermRef, name) => + Some(ref(prefix).select(i.symbol)) + case TermRef(prefix: ThisType, name) => + Some(This(prefix.cls).select(i.symbol)) + case _ => None + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala new file mode 100644 index 000000000000..510a1d2eaadb --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala @@ -0,0 +1,83 @@ +package dotty.tools.dotc +package transform.localopt + +import core.Contexts.Context +import core.Symbols._ +import core.Types._ +import core.Flags._ +import ast.Trees._ +import scala.collection.mutable + +/** Rewrite vars with exactly one assignment as vals. */ +class Valify(val simplifyPhase: Simplify)(implicit val ctx: Context) extends Optimisation { + import ast.tpd._ + + // Either a duplicate or a read through series of immutable fields. + val defined: mutable.Map[Symbol, ValDef] = mutable.Map() + + val firstRead: mutable.Map[Symbol, RefTree] = mutable.Map() + + val firstWrite: mutable.Map[Symbol, Assign] = mutable.Map() + + val secondWrite: mutable.Map[Symbol, Assign] = mutable.Map() + + val visitor: Tree => Unit = { + case t: ValDef if t.symbol.is(Mutable, Lazy) && !t.symbol.is(Method) && !t.symbol.owner.isClass => + if (isPureExpr(t.rhs)) + defined(t.symbol) = t + + case t: RefTree if t.symbol.exists && !t.symbol.is(Method) && !t.symbol.owner.isClass => + if (!firstWrite.contains(t.symbol)) firstRead(t.symbol) = t + + case t @ Assign(l, expr) if !l.symbol.is(Method) && !l.symbol.owner.isClass => + if (!firstRead.contains(l.symbol)) { + if (firstWrite.contains(l.symbol)) { + if (!secondWrite.contains(l.symbol)) + secondWrite(l.symbol) = t + } else if (!expr.existsSubTree(x => x match { + case tree: RefTree if x.symbol == l.symbol => firstRead(l.symbol) = tree; true + case _ => false + })) { + firstWrite(l.symbol) = t + } + } + case _ => + } + + def transformer(localCtx: Context): Tree => Tree = { + case t: Block => // Drop non-side-effecting stats + val valdefs = t.stats.collect { + case t: ValDef if defined.contains(t.symbol) => t + } + + val assigns = t.stats.filter { + case t @ Assign(lhs, r) => + firstWrite.contains(lhs.symbol) && !secondWrite.contains(lhs.symbol) + case _ => false + } + + val pairs = valdefs.flatMap(x => assigns.find(y => y.asInstanceOf[Assign].lhs.symbol == x.symbol) match { + case Some(y: Assign) => List((x, y)) + case _ => Nil + }) + + val valsToDrop = pairs.map(_._1).toSet + val assignsToReplace: Map[Assign, ValDef] = pairs.map(_.swap).toMap + + val newStats = t.stats.mapConserve { + case x: ValDef if valsToDrop.contains(x) => EmptyTree + case t: Assign => assignsToReplace.get(t) match { + case Some(vd) => + val newD = vd.symbol.asSymDenotation.copySymDenotation(initFlags = vd.symbol.flags.&~(Mutable)) + newD.installAfter(simplifyPhase) + ValDef(vd.symbol.asTerm, t.rhs) + case None => t + } + case x => x + } + + if (newStats eq t.stats) t + else cpy.Block(t)(newStats, t.expr) + case tree => tree + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala new file mode 100644 index 000000000000..8b77e02f926c --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala @@ -0,0 +1,77 @@ +package dotty.tools.dotc +package transform.localopt + +import core._ +import core.Contexts.Context +import core.Symbols._ +import core.Flags._ +import scala.collection.mutable + +/** Inline val with exactly one assignment to a var. For example: + * + * { + * val l = + * var r = l + * // code not using l + * } + * + * becomes: + * + * { + * var r = + * // code not using l + * } + */ + class Varify(implicit val ctx: Context) extends Optimisation { + import ast.tpd._ + + val paramsTimesUsed = mutable.HashMap[Symbol, Int]() + + val possibleRenames = mutable.HashMap[Symbol, Set[Symbol]]() + + val visitor: Tree => Unit = { + case t: ValDef + if t.symbol.is(Param) => + paramsTimesUsed += (t.symbol -> 0) + case valDef: ValDef + if valDef.symbol.is(Mutable) => + valDef.rhs.foreachSubTree { subtree => + if (paramsTimesUsed.contains(subtree.symbol) && + valDef.symbol.info.widenDealias <:< subtree.symbol.info.widenDealias) { + val newSet = possibleRenames.getOrElse(valDef.symbol, Set.empty) + subtree.symbol + possibleRenames.put(valDef.symbol, newSet) + } + } + case t: RefTree + if paramsTimesUsed.contains(t.symbol) => + val param = t.symbol + val current = paramsTimesUsed.get(param) + current foreach { c => paramsTimesUsed += (param -> (c + 1)) } + case _ => + } + + def transformer(localCtx: Context): Tree => Tree = { + val paramCandidates = paramsTimesUsed.filter(kv => kv._2 == 1).keySet + val renames: Map[Symbol, Symbol] = possibleRenames.iterator + .map(kv => (kv._1, kv._2.intersect(paramCandidates))) + .filter(x => x._2.nonEmpty) + .map(x => (x._1, x._2.head)) + .toMap + val transformation: Tree => Tree = { + case t: RefTree + if renames.contains(t.symbol) => + ref(renames(t.symbol)) + case t: ValDef + if renames.contains(t.symbol) => + val replaced = renames(t.symbol) + if (t.rhs.symbol == replaced) EmptyTree + else ref(replaced).becomes(t.rhs) + case t: ValDef + if paramCandidates.contains(t.symbol) => + t.symbol.flags = Mutable + t + case t => t + } + transformation + } +} From e93b49950b8e6febf35fd9a6397c254662f26ffc Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Fri, 2 Jun 2017 09:50:59 +0200 Subject: [PATCH 02/16] Add -Yopt-phases, -Yopt-fuel, bisect.sh, update main Simplify loop --- .../tools/dotc/config/ScalaSettings.scala | 4 +- .../dotty/tools/dotc/transform/CtxLazy.scala | 3 +- .../transform/localopt/BubbleUpNothing.scala | 5 +- .../transform/localopt/Optimisation.scala | 4 +- .../dotc/transform/localopt/Simplify.scala | 65 +++++++++++++------ compiler/test/bisect.sh | 40 ++++++++++++ compiler/test/partest | 4 -- 7 files changed, 94 insertions(+), 31 deletions(-) create mode 100644 compiler/test/bisect.sh delete mode 100755 compiler/test/partest diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 8eeaf9e9f976..afe0ff90e4ec 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -107,7 +107,9 @@ class ScalaSettings extends Settings.SettingGroup { val YnoInline = BooleanSetting("-Yno-inline", "Suppress inlining.") /** Linker specific flags */ - val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying optimisations to the program") withAbbreviation "-optimize" + val YoptPhases = PhasesSetting("-Yopt-phases", "Restrict the optimisation phases to execute under -optimise.") + val YoptFuel = IntSetting("-Yopt-fuel", "Maximum number of optimisations performed under -optimise.", -1) + val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying local optimisations to the .program") withAbbreviation "-optimize" /** Dottydoc specific settings */ val siteRoot = StringSetting( diff --git a/compiler/src/dotty/tools/dotc/transform/CtxLazy.scala b/compiler/src/dotty/tools/dotc/transform/CtxLazy.scala index 7b317abefe97..c0306ca2b1d0 100644 --- a/compiler/src/dotty/tools/dotc/transform/CtxLazy.scala +++ b/compiler/src/dotty/tools/dotc/transform/CtxLazy.scala @@ -1,5 +1,6 @@ package dotty.tools.dotc package transform + import core.Contexts.Context /** Utility class for lazy values whose evaluation depends on a context. @@ -20,4 +21,4 @@ class CtxLazy[T](expr: Context => T) { } myValue } -} \ No newline at end of file +} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala b/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala index 2d3c4de475de..94c5a8bc625f 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala @@ -7,8 +7,9 @@ import ast.Trees._ /** Every pure statement preceding a ??? can be removed. * - * This optimisation makes it rather tricky meaningful examples since the - * compiler will often be able to reduce them to a single main with ???... + * This optimisation makes it rather tricky to write meaningful examples + * since the compiler will often be able to reduce them to a single main + * method with body = ???. */ class BubbleUpNothing(implicit val ctx: Context) extends Optimisation { import ast.tpd._ diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala index fc8e58674513..15a5a6e9da6e 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala @@ -10,11 +10,11 @@ trait Optimisation { def visitor: Tree => Unit /** Does the actual Tree => Tree transformation, possibly using a different - * context from the one using in Optimisation. + * context from the one used in Optimisation. */ def transformer(localCtx: Context): Tree => Tree - def name: String = this.getClass.getName.init + def name: String = this.getClass.getName.split('.').last val NoVisitor: Tree => Unit = _ => () } diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala index dcc247adb568..07069ac863d2 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala @@ -20,7 +20,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { override def phaseName: String = "simplify" override val cpy = tpd.cpy - def beforeErasure(implicit ctx: Context): List[Optimisation] = + private def beforeErasure(implicit ctx: Context): List[Optimisation] = new InlineCaseIntrinsics :: new RemoveUnnecessaryNullChecks :: new InlineOptions :: @@ -36,50 +36,73 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { new ConstantFold :: Nil - def afterErasure(implicit ctx: Context): List[Optimisation] = - // new InlineCaseIntrinsics :: - // new RemoveUnnecessaryNullChecks :: - // new InlineOptions :: - // new InlineLabelsCalledOnce :: + private def afterErasure(implicit ctx: Context): List[Optimisation] = new Valify(this) :: new Devalify :: new Jumpjump :: new DropGoodCasts :: - // new DropNoEffects(this) :: - // new InlineLocalObjects :: // followCases needs to be fixed, see ./tests/pos/rbtree.scala - // new Varify :: // varify could stop other transformations from being applied. postponed. - // new BubbleUpNothing :: new ConstantFold :: 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) = { + val maxFuel = ctx.settings.YoptFuel.value + if (fuel < 0 && maxFuel > 0) // Both defaults are at -1 + fuel = maxFuel + this + } + // The entry point of local optimisation: DefDefs override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = { val ctx0 = ctx if (ctx.settings.optimise.value && !tree.symbol.is(Label)) { implicit val ctx: Context = ctx0.withOwner(tree.symbol(ctx0)) - val optimisations = if (ctx.erasedTypes) afterErasure else beforeErasure + val 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)) + } var rhs0 = tree.rhs var rhs1: Tree = null while (rhs1 ne rhs0) { rhs1 = rhs0 val context = ctx.withOwner(tree.symbol) - // TODO: fuse for performance - optimisations.foreach { optimisation => + optimisations.foreach { optimisation => // TODO: fuse for performance + // Visit rhs0.foreachSubTree(optimisation.visitor) - val rhst = new TreeMap() { + // 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 - optimisation.transformer(ctx)(super.transform(tree)(innerCtx)) + val childOptimizedTree = super.transform(tree)(innerCtx) + + if (fuel == 0) + childOptimizedTree + else { + val fullyOptimizedTree = optimisation.transformer(ctx)(childOptimizedTree) + + if (tree ne fullyOptimizedTree) { + if (fuel > 0) fuel -= 1 + if (fuel != -1 && fuel < 10) { + println(s"${tree.symbol} was simplified by ${optimisation.name} (fuel=$fuel): ${tree.show}") + println(s"became after ${optimisation.name}: (fuel=$fuel) ${fullyOptimizedTree.show}") + } + } + fullyOptimizedTree + } } }.transform(rhs0) - - if (rhst ne rhs0) { - simplify.println(s"${tree.symbol} was simplified by ${optimisation.name}: ${rhs0.show}") - simplify.println(s"became: ${rhst.show}") - } - rhs0 = rhst } } if (rhs0 ne tree.rhs) tpd.cpy.DefDef(tree)(rhs = rhs0) diff --git a/compiler/test/bisect.sh b/compiler/test/bisect.sh new file mode 100644 index 000000000000..309a9717eaf7 --- /dev/null +++ b/compiler/test/bisect.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +# Bisect a shell command. Takes a `xargs -I{}` style lambda as argument, finds +# the first value of `{}` < 65536 such that the command succeeds. Usage: +# +# $ sh bisect.sh test {} -lt 42 + +cmd="$@" + +# Example of bisection to isolate -optimise bootstrap errors using -Yopt-fuel. +# See comments in dotty/tools/dotc/transform/localopt/Simplify.scala + +# cmd=$(cat < Date: Tue, 6 Jun 2017 18:41:38 +0200 Subject: [PATCH 03/16] DropNoEffects: fix first bootstrap bug --- .../dotc/transform/localopt/DropNoEffects.scala | 4 +++- tests/run/drop-no-effects.scala | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 tests/run/drop-no-effects.scala diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala index 618e1ae7d399..5f37e3809c16 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala @@ -123,12 +123,14 @@ class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) exte rec :: args.map(keepOnlySideEffects) } Block(prefix, unitLiteral) + case t @ TypeApply(Select(rec, _), List(testType)) if t.symbol.eq(defn.Any_asInstanceOf) && testType.tpe.widenDealias.typeSymbol.exists => val receiverType = TypeErasure.erasure(rec.tpe) val erazedTestedType = TypeErasure.erasure(testType.tpe) if (receiverType.derivesFrom(erazedTestedType.typeSymbol)) - EmptyTree + rec else t + case _ => t } } diff --git a/tests/run/drop-no-effects.scala b/tests/run/drop-no-effects.scala new file mode 100644 index 000000000000..d758e6466c9c --- /dev/null +++ b/tests/run/drop-no-effects.scala @@ -0,0 +1,12 @@ +object Test { + def main(args: Array[String]): Unit = { + var run = false + + val one = { + run = true + 1 + }.asInstanceOf[Int] + + assert(run) + } +} From 0ed8e0a25a17efe61733536295b769ff2a85ef87 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Tue, 6 Jun 2017 18:42:41 +0200 Subject: [PATCH 04/16] DropNoEffects: comments and formating --- .../transform/localopt/DropNoEffects.scala | 210 ++++++++++-------- .../dotc/transform/localopt/Simplify.scala | 1 + 2 files changed, 115 insertions(+), 96 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala index 5f37e3809c16..6106fa2e4020 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala @@ -10,7 +10,7 @@ import core.Flags._ import ast.Trees._ import Simplify.desugarIdent -/** Removes side effect free statements in blocks. +/** Removes side effect free statements in blocks and Defdef. * Note: BoxedUnit currently messes up this phase when run after erasure */ class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) extends Optimisation { @@ -19,11 +19,16 @@ class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) exte val visitor = NoVisitor def transformer(localCtx: Context): Tree => Tree = { + // Remove empty blocks case Block(Nil, expr) => expr + + // Keep only side effect free statements in blocks case a: Block => val newStats0 = a.stats.mapConserve(keepOnlySideEffects) + + // Flatten nested blocks val newStats1 = if (newStats0 eq a.stats) newStats0 else newStats0.flatMap { - case x: Block => x.stats ::: List(x.expr) + case x: Block => x.stats ::: List(x.expr) case EmptyTree => Nil case t => t :: Nil } @@ -31,108 +36,120 @@ class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) exte case Block(stats2, expr) => (newStats1 ++ stats2, expr) case _ => (newStats1, a.expr) } + if (newStats2.nonEmpty) cpy.Block(a)(stats = newStats2, newExpr) else newExpr - case a: DefDef => - if (a.symbol.info.finalResultType.derivesFrom(defn.UnitClass) && - !a.rhs.tpe.derivesFrom(defn.UnitClass) && - !a.rhs.tpe.derivesFrom(defn.NothingClass)) { - def insertUnit(t: Tree) = { - if (!t.tpe.derivesFrom(defn.UnitClass)) Block(t :: Nil, unitLiteral) - else t - } - cpy.DefDef(a)(rhs = insertUnit(keepOnlySideEffects(a.rhs)), tpt = TypeTree(defn.UnitType)) - } else a + + // Keep only side effect free statements unit returning functions + case a: DefDef if (a.symbol.info.finalResultType.derivesFrom(defn.UnitClass) && + !a.rhs.tpe.derivesFrom(defn.UnitClass) && + !a.rhs.tpe.derivesFrom(defn.NothingClass)) => + def insertUnit(t: Tree) = { + if (!t.tpe.derivesFrom(defn.UnitClass)) Block(t :: Nil, unitLiteral) + else t + } + cpy.DefDef(a)(rhs = insertUnit(keepOnlySideEffects(a.rhs)), tpt = TypeTree(defn.UnitType)) + case t => t } - def keepOnlySideEffects(t: Tree): Tree = { - t match { - case l: Literal => - EmptyTree - case t: This => - EmptyTree - case Typed(exp, tpe) => - keepOnlySideEffects(exp) - case t @ If(cond, thenp, elsep) => - val nthenp = keepOnlySideEffects(thenp) - val nelsep = keepOnlySideEffects(elsep) - if (thenp.isEmpty && elsep.isEmpty) keepOnlySideEffects(cond) - else cpy.If(t)( - thenp = nthenp.orElse(if (thenp.isInstanceOf[Literal]) thenp else unitLiteral), - elsep = nelsep.orElse(if (elsep.isInstanceOf[Literal]) elsep else unitLiteral)) - case Select(rec, _) if - (t.symbol.isGetter && !t.symbol.is(Mutable | Lazy)) || - (t.symbol.owner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) || - (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) => - keepOnlySideEffects(rec) // Accessing a field of a product - case s @ Select(qual, name) if - // !name.eq(nme.TYPE_) && // Keep the .TYPE added by ClassOf, would be needed for AfterErasure - !t.symbol.is(Mutable | Lazy) && !t.symbol.is(Method) => - keepOnlySideEffects(qual) - case Block(List(t: DefDef), s: Closure) => - EmptyTree - case bl @ Block(stats, expr) => - val stats1 = stats.mapConserve(keepOnlySideEffects) - val stats2 = if (stats1 ne stats) stats1.filter(x=>x ne EmptyTree) else stats1 - val expr2: Tree = expr match { - case t: Literal if t.tpe.derivesFrom(defn.UnitClass) => expr - case _ => keepOnlySideEffects(expr).orElse(unitLiteral) - } - cpy.Block(bl)(stats2, expr2) - case t: Ident if !t.symbol.is(Method | Lazy) && !t.symbol.info.isInstanceOf[ExprType] || effectsDontEscape(t) => - desugarIdent(t) match { - case Some(t) if !(t.qualifier.symbol.is(JavaDefined) && t.qualifier.symbol.is(Package)) => t - case _ => EmptyTree - } - case app: Apply if app.fun.symbol.is(Label) && !app.tpe.finalResultType.derivesFrom(defn.UnitClass) => - // This is "the scary hack". It changes the return type to Unit, then - // invalidates the denotation cache. Because this optimisation only - // operates locally, this should be fine. - val denot = app.fun.symbol.denot - if (!denot.info.finalResultType.derivesFrom(defn.UnitClass)) { - val newLabelType = app.symbol.info match { - case mt: MethodType => - mt.derivedLambdaType(mt.paramNames, mt.paramInfos, defn.UnitType) - case et: ExprType => - et.derivedExprType(defn.UnitType) - } - val newD = app.symbol.asSymDenotation.copySymDenotation(info = newLabelType) - newD.installAfter(simplifyPhase) - } + val keepOnlySideEffects: Tree => Tree = { + case l: Literal => + EmptyTree - ref(app.symbol).appliedToArgs(app.args) - case t @ Apply(fun, _) if effectsDontEscape(t) => - def getArgsss(a: Tree): List[Tree] = a match { - case a: Apply => getArgsss(a.fun) ::: a.args - case _ => Nil - } - def getSel(t: Tree): Tree = {t match { - case t: Apply => getSel(t.fun) - case t: Select => t.qualifier - case t: TypeApply => getSel(t.fun) - case _ => t - }} - val args = getArgsss(t) - val rec = getSel(t) - val prefix = rec match { - case t: New => - args.map(keepOnlySideEffects) - case _ => - rec :: args.map(keepOnlySideEffects) + case t: This => + EmptyTree + + case Typed(exp, tpe) => + keepOnlySideEffects(exp) + + // If is pure, propagade the simplification + case t @ If(cond, thenp, elsep) => + val nthenp = keepOnlySideEffects(thenp) + val nelsep = keepOnlySideEffects(elsep) + if (thenp.isEmpty && elsep.isEmpty) keepOnlySideEffects(cond) + else cpy.If(t)( + thenp = nthenp.orElse(if (thenp.isInstanceOf[Literal]) thenp else unitLiteral), + elsep = nelsep.orElse(if (elsep.isInstanceOf[Literal]) elsep else unitLiteral)) + + // Accessing a field of a product + case t @ Select(rec, _) if + (t.symbol.isGetter && !t.symbol.is(Mutable | Lazy)) || + (t.symbol.owner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) || + (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) => + keepOnlySideEffects(rec) + + case s @ Select(qual, name) if + // !name.eq(nme.TYPE_) && // Keep the .TYPE added by ClassOf, would be needed for AfterErasure + !s.symbol.is(Mutable | Lazy) && !s.symbol.is(Method) => + keepOnlySideEffects(qual) + + case Block(List(t: DefDef), s: Closure) => + EmptyTree + + case bl @ Block(stats, expr) => + val stats1 = stats.mapConserve(keepOnlySideEffects) + val stats2 = if (stats1 ne stats) stats1.filter(_ ne EmptyTree) else stats1 + val expr2: Tree = expr match { + case t: Literal if t.tpe.derivesFrom(defn.UnitClass) => expr + case _ => keepOnlySideEffects(expr).orElse(unitLiteral) + } + cpy.Block(bl)(stats2, expr2) + + case t: Ident if !t.symbol.is(Method | Lazy) && !t.symbol.info.isInstanceOf[ExprType] || effectsDontEscape(t) => + desugarIdent(t) match { + case Some(t) if !(t.qualifier.symbol.is(JavaDefined) && t.qualifier.symbol.is(Package)) => t + case _ => EmptyTree + } + + case app: Apply if app.fun.symbol.is(Label) && !app.tpe.finalResultType.derivesFrom(defn.UnitClass) => + // This is "the scary hack". It changes the return type to Unit, then + // invalidates the denotation cache. Because this optimisation only + // operates locally, this should be fine. + val denot = app.fun.symbol.denot + if (!denot.info.finalResultType.derivesFrom(defn.UnitClass)) { + val newLabelType = app.symbol.info match { + case mt: MethodType => + mt.derivedLambdaType(mt.paramNames, mt.paramInfos, defn.UnitType) + case et: ExprType => + et.derivedExprType(defn.UnitType) } - Block(prefix, unitLiteral) + val newD = app.symbol.asSymDenotation.copySymDenotation(info = newLabelType) + newD.installAfter(simplifyPhase) + } - case t @ TypeApply(Select(rec, _), List(testType)) if t.symbol.eq(defn.Any_asInstanceOf) && testType.tpe.widenDealias.typeSymbol.exists => - val receiverType = TypeErasure.erasure(rec.tpe) - val erazedTestedType = TypeErasure.erasure(testType.tpe) - if (receiverType.derivesFrom(erazedTestedType.typeSymbol)) - rec - else t + ref(app.symbol).appliedToArgs(app.args) - case _ => t - } + case t @ Apply(fun, _) if effectsDontEscape(t) => + def getArgsss(a: Tree): List[Tree] = a match { + case a: Apply => getArgsss(a.fun) ::: a.args + case _ => Nil + } + def getSel(t: Tree): Tree = {t match { + case t: Apply => getSel(t.fun) + case t: Select => t.qualifier + case t: TypeApply => getSel(t.fun) + case _ => t + }} + val args = getArgsss(t) + val rec = getSel(t) + val prefix = rec match { + case t: New => + args.map(keepOnlySideEffects) + case _ => + rec :: args.map(keepOnlySideEffects) + } + Block(prefix, unitLiteral) + + case t @ TypeApply(Select(rec, _), List(testType)) if t.symbol.eq(defn.Any_asInstanceOf) && testType.tpe.widenDealias.typeSymbol.exists => + val receiverType = TypeErasure.erasure(rec.tpe) + val erazedTestedType = TypeErasure.erasure(testType.tpe) + if (receiverType.derivesFrom(erazedTestedType.typeSymbol)) + rec + else t + + case t => t } val constructorWhiteList: Set[String] = Set( @@ -178,10 +195,11 @@ class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) exte "scala.runtime.BoxesRunTime.unboxToFloat" ) + /** Does this tree has side effects? This is an approximation awaiting real purity analysis... */ def effectsDontEscape(t: Tree): Boolean = t match { - case Apply(fun, args) if fun.symbol.isConstructor && constructorWhiteList.contains(fun.symbol.owner.fullName.toString) => + case Apply(fun, _) if fun.symbol.isConstructor && constructorWhiteList.contains(fun.symbol.owner.fullName.toString) => true - case Apply(fun, args) if methodsWhiteList.contains(fun.symbol.fullName.toString) => + case Apply(fun, _) if methodsWhiteList.contains(fun.symbol.fullName.toString) => true case Ident(_) if t.symbol.is(Module) && (t.symbol.is(Synthetic) || moduleWhiteList.contains(t.symbol.fullName.toString)) => true diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala index 07069ac863d2..80d601ced08f 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala @@ -113,6 +113,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { object Simplify { import tpd._ + // TODO: This function is duplicated in jvm/DottyBackendInterface.scala, let's factor these out! def desugarIdent(i: Ident)(implicit ctx: Context): Option[Select] = { i.tpe match { case TermRef(prefix: TermRef, name) => From 9c152e8f753cf4bf85770797bbd41f45215885ee Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Wed, 7 Jun 2017 10:36:54 +0200 Subject: [PATCH 05/16] DropNoEffects: Remove TypeApply case, this is covered by DropGoodCasts --- .../transform/localopt/DropGoodCasts.scala | 39 ++++++++++++------- .../transform/localopt/DropNoEffects.scala | 7 ---- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala b/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala index 21bb6eab6b6f..139d7f8908cf 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala @@ -67,13 +67,19 @@ import transform.SymUtils._ def collectTypeTests(t: Tree)(implicit ctx: Context): List[(Symbol, Type)] = { def recur(t: Tree): List[(Symbol, Type)] = t match { - case Apply(x, _) if (x.symbol == defn.Boolean_! || x.symbol == defn.Boolean_||) => List.empty - case Apply(fun @ Select(x, _), y) if (fun.symbol == defn.Boolean_&&) => recur(x) ++ recur(y.head) - case TypeApply(fun @ Select(x, _), List(tp)) if fun.symbol eq defn.Any_isInstanceOf => - if (x.symbol.exists && !x.symbol.owner.isClass && !x.symbol.is(Method|Mutable)) - (x.symbol, tp.tpe) :: Nil - else Nil - case _ => List.empty + case Apply(x, _) if (x.symbol == defn.Boolean_! || x.symbol == defn.Boolean_||) => + Nil + + case Apply(fun @ Select(x, _), y) if (fun.symbol == defn.Boolean_&&) => + recur(x) ++ recur(y.head) + + case TypeApply(fun @ Select(x, _), List(tp)) + if fun.symbol.eq(defn.Any_isInstanceOf) && + !x.symbol.is(Method | Mutable) && + x.symbol.exists && !x.symbol.owner.isClass => + (x.symbol, tp.tpe) :: Nil + + case _ => Nil } recur(t) } @@ -81,13 +87,18 @@ import transform.SymUtils._ def collectNullTests(t: Tree)(implicit ctx: Context): List[Symbol] = { def recur(t: Tree): List[Symbol] = t match { - case Apply(x, _) if (x.symbol == defn.Boolean_! || x.symbol == defn.Boolean_||) => List.empty - case Apply(fun @ Select(x, _), y) if (fun.symbol == defn.Boolean_&&) => recur(x) ++ recur(y.head) - case Apply(fun @ Select(x, _), List(tp)) if fun.symbol eq defn.Object_ne => - if (x.symbol.exists && !x.symbol.owner.isClass && !x.symbol.is(Method|Mutable)) - x.symbol :: Nil - else Nil - case _ => List.empty + case Apply(x, _) if (x.symbol == defn.Boolean_! || x.symbol == defn.Boolean_||) => Nil + + case Apply(fun @ Select(x, _), y) if (fun.symbol == defn.Boolean_&&) => + recur(x) ++ recur(y.head) + + case Apply(fun @ Select(x, _), List(tp)) + if fun.symbol.eq(defn.Object_ne) && + !x.symbol.is(Method | Mutable) && + x.symbol.exists && !x.symbol.owner.isClass => + x.symbol :: Nil + + case _ => Nil } recur(t) } diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala index 6106fa2e4020..047e2bbd0d90 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala @@ -142,13 +142,6 @@ class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) exte } Block(prefix, unitLiteral) - case t @ TypeApply(Select(rec, _), List(testType)) if t.symbol.eq(defn.Any_asInstanceOf) && testType.tpe.widenDealias.typeSymbol.exists => - val receiverType = TypeErasure.erasure(rec.tpe) - val erazedTestedType = TypeErasure.erasure(testType.tpe) - if (receiverType.derivesFrom(erazedTestedType.typeSymbol)) - rec - else t - case t => t } From 03c0ca6111702cc9ef7ff6f15631323a7fcad5ad Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Wed, 7 Jun 2017 15:41:25 +0200 Subject: [PATCH 06/16] =?UTF-8?q?Typo:=20receiver=20=E2=86=92=20receiver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/jvm/DottyBackendInterface.scala | 2 +- .../localopt/InlineCaseIntrinsics.scala | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 709ce4616f9b..8539d99b684d 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -173,7 +173,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma val primitives: Primitives = new Primitives { val primitives = new DottyPrimitives(ctx) - def getPrimitive(app: Apply, reciever: Type): Int = primitives.getPrimitive(app, reciever) + def getPrimitive(app: Apply, receiver: Type): Int = primitives.getPrimitive(app, receiver) def getPrimitive(sym: Symbol): Int = primitives.getPrimitive(sym) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala index 100f39c6cd80..3c9205176590 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala @@ -38,7 +38,7 @@ class InlineCaseIntrinsics(implicit val ctx: Context) extends Optimisation { case _ => fun } val constructor = a.symbol.owner.companionClass.primaryConstructor.asTerm - evalReciever(a, rollInArgs(argss.tail, New(a.tpe.widenDealias, constructor, argss.head))) + evalreceiver(a, rollInArgs(argss.tail, New(a.tpe.widenDealias, constructor, argss.head))) // For synthetic dotty unapplies on case classes: // - CC.unapply(arg): CC → arg @@ -60,10 +60,10 @@ class InlineCaseIntrinsics(implicit val ctx: Context) extends Optimisation { if (isDottyUnapply) { // dotty only if (a.tpe.derivesFrom(defn.BooleanClass)) // CC.unapply(arg): Boolean → true - evalReciever(a, Literal(Constant(true))) + evalreceiver(a, Literal(Constant(true))) else // CC.unapply(arg): CC → arg - evalReciever(a, a.args.head) + evalreceiver(a, a.args.head) } else if (isScalaOptionUnapply) { // CC.unapply(arg): Option[CC] → new Some(new scala.TupleN(arg._1, ..., arg._N)) @@ -82,7 +82,7 @@ class InlineCaseIntrinsics(implicit val ctx: Context) extends Optimisation { val none = ref(defn.NoneModuleRef) def isNull(e: Tree) = e.select(defn.Object_eq).appliedTo(Literal(Constant(null))) def fi(e: Tree) = If(isNull(e), none, some(e)) - evalReciever(a, evalOnce(a.args.head)(fi)) + evalreceiver(a, evalOnce(a.args.head)(fi)) } else a @@ -93,20 +93,20 @@ class InlineCaseIntrinsics(implicit val ctx: Context) extends Optimisation { a.symbol.extendedOverriddenSymbols.isEmpty && (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => - def reciever(t: Tree): Type = t match { - case t: Apply => reciever(t.fun) - case t: TypeApply => reciever(t.fun) + def receiver(t: Tree): Type = t match { + case t: Apply => receiver(t.fun) + case t: TypeApply => receiver(t.fun) case t: Ident => desugarIdent(t) match { - case Some(t) => reciever(t) + case Some(t) => receiver(t) case _ => NoType } case t: Select => t.qualifier.tpe.widenDealias } - val recv = reciever(a) + val recv = receiver(a) if (recv.typeSymbol.is(Module)) { val someTpe = a.tpe.translateParameterized(defn.OptionClass, defn.SomeClass) - evalReciever(a, New(someTpe, a.args.head :: Nil)) + evalreceiver(a, New(someTpe, a.args.head :: Nil)) } else a case t => t @@ -114,7 +114,7 @@ class InlineCaseIntrinsics(implicit val ctx: Context) extends Optimisation { // Apply fun may be a side-effectful function. E.g. a block, see tests/run/t4859.scala // we need to maintain expressions that were in this block - def evalReciever(a: Apply, res: Tree) = { + def evalreceiver(a: Apply, res: Tree) = { def receiver(t: Tree): Tree = t match { case TypeApply(fun, targs) if fun.symbol eq t.symbol => receiver(fun) case Apply(fn, args) if fn.symbol == t.symbol => receiver(fn) From f6e46327ae66f7d93162f31aec89ff3ad53ef946 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Wed, 7 Jun 2017 16:09:41 +0200 Subject: [PATCH 07/16] Formatting, adressing smarter comments --- .../transform/localopt/DropNoEffects.scala | 26 +++++----- .../localopt/InlineCaseIntrinsics.scala | 48 ++++++++++--------- .../localopt/InlineLabelsCalledOnce.scala | 43 ++++++++++++----- .../localopt/InlineLocalObjects.scala | 6 +-- .../dotc/transform/localopt/Jumpjump.scala | 16 ++++--- .../RemoveUnnecessaryNullChecks.scala | 35 ++++++++++---- .../dotc/transform/localopt/Varify.scala | 33 ++++++------- 7 files changed, 124 insertions(+), 83 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala index 047e2bbd0d90..969fb147515a 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala @@ -42,9 +42,11 @@ class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) exte else newExpr // Keep only side effect free statements unit returning functions - case a: DefDef if (a.symbol.info.finalResultType.derivesFrom(defn.UnitClass) && - !a.rhs.tpe.derivesFrom(defn.UnitClass) && - !a.rhs.tpe.derivesFrom(defn.NothingClass)) => + case a: DefDef + if a.symbol.info.finalResultType.derivesFrom(defn.UnitClass) && + !a.rhs.tpe.derivesFrom(defn.UnitClass) && + !a.rhs.tpe.derivesFrom(defn.NothingClass) => + def insertUnit(t: Tree) = { if (!t.tpe.derivesFrom(defn.UnitClass)) Block(t :: Nil, unitLiteral) else t @@ -74,15 +76,15 @@ class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) exte elsep = nelsep.orElse(if (elsep.isInstanceOf[Literal]) elsep else unitLiteral)) // Accessing a field of a product - case t @ Select(rec, _) if - (t.symbol.isGetter && !t.symbol.is(Mutable | Lazy)) || - (t.symbol.owner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) || - (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) => - keepOnlySideEffects(rec) - - case s @ Select(qual, name) if - // !name.eq(nme.TYPE_) && // Keep the .TYPE added by ClassOf, would be needed for AfterErasure - !s.symbol.is(Mutable | Lazy) && !s.symbol.is(Method) => + case t @ Select(rec, _) + if (t.symbol.isGetter && !t.symbol.is(Mutable | Lazy)) || + (t.symbol.owner.derivesFrom(defn.ProductClass) && t.symbol.owner.is(CaseClass) && t.symbol.name.isSelectorName) || + (t.symbol.is(CaseAccessor) && !t.symbol.is(Mutable)) => + + keepOnlySideEffects(rec) + + // !name.eq(nme.TYPE_) && // Keep the .TYPE added by ClassOf, would be needed for AfterErasure + case s @ Select(qual, name) if !s.symbol.is(Mutable | Lazy | Method) => keepOnlySideEffects(qual) case Block(List(t: DefDef), s: Closure) => diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala index 3c9205176590..df32a68bc355 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala @@ -11,7 +11,12 @@ import ast.Trees._ import transform.SymUtils._ import Simplify.desugarIdent -/** Inline case class specific methods using desugarings assumptions. */ +/** Inline case class specific methods using desugarings assumptions. + * + * Note: to run this optimisation after erasure one would need to specialize + * it for constructor with outer pointer and values classes. There is + * probably no need to run this more than once. + */ class InlineCaseIntrinsics(implicit val ctx: Context) extends Optimisation { import ast.tpd._ @@ -20,13 +25,14 @@ class InlineCaseIntrinsics(implicit val ctx: Context) extends Optimisation { def transformer(localCtx: Context): Tree => Tree = { // For synthetic applies on case classes (both dotty/scalac) // - CC.apply(args) → new CC(args) - case a: Apply if !a.tpe.isInstanceOf[MethodicType] && - a.symbol.is(Synthetic) && - a.symbol.owner.is(Module) && - (a.symbol.name == nme.apply) && - a.symbol.owner.companionClass.is(CaseClass) && - !a.tpe.derivesFrom(defn.EnumClass) && - (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => + case a: Apply + if !a.tpe.isInstanceOf[MethodicType] && + a.symbol.is(Synthetic) && + a.symbol.owner.is(Module) && + a.symbol.name == nme.apply && + a.symbol.owner.companionClass.is(CaseClass) && + !a.tpe.derivesFrom(defn.EnumClass) && + (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => def unrollArgs(t: Tree, l: List[List[Tree]]): List[List[Tree]] = t match { case Apply(t, args) => unrollArgs(t, args :: l) @@ -44,12 +50,13 @@ class InlineCaseIntrinsics(implicit val ctx: Context) extends Optimisation { // - CC.unapply(arg): CC → arg // - CC.unapply(arg): Boolean → true, dotty only // - CC.unapply(arg): Option[CC] → new Some(new scala.TupleN(arg._1, ..., arg._N)) - case a: Apply if a.symbol.is(Synthetic) && - a.symbol.owner.is(Module) && - (a.symbol.name == nme.unapply) && - a.symbol.owner.companionClass.is(CaseClass) && - !a.tpe.derivesFrom(defn.EnumClass) && - (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => + case a: Apply + if a.symbol.is(Synthetic) && + a.symbol.owner.is(Module) && + a.symbol.name == nme.unapply && + a.symbol.owner.companionClass.is(CaseClass) && + !a.tpe.derivesFrom(defn.EnumClass) && + (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => val args = a.args.head val isDottyUnapply = !a.symbol.owner.is(Scala2x) @@ -88,10 +95,11 @@ class InlineCaseIntrinsics(implicit val ctx: Context) extends Optimisation { // Seq.unapplySeq(arg) → new Some(arg) // Where Seq is any companion of type <: SeqFactoryClass - case a: Apply if (a.symbol.name == nme.unapplySeq) && - a.symbol.owner.derivesFrom(defn.SeqFactoryClass) && - a.symbol.extendedOverriddenSymbols.isEmpty && - (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => + case a: Apply + if a.symbol.name == nme.unapplySeq && + a.symbol.owner.derivesFrom(defn.SeqFactoryClass) && + a.symbol.extendedOverriddenSymbols.isEmpty && + (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => def receiver(t: Tree): Type = t match { case t: Apply => receiver(t.fun) @@ -127,8 +135,4 @@ class InlineCaseIntrinsics(implicit val ctx: Context) extends Optimisation { else Block(recv :: Nil, res) } - - // To run this optimisation after erasure one would need to specialize it - // for constructor with outer pointer and values classes. There is probably - // no need to run this more than once. } diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala index 708948b0a0d7..ac7002f8ffe9 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala @@ -13,38 +13,55 @@ class InlineLabelsCalledOnce(implicit val ctx: Context) extends Optimisation { import ast.tpd._ val timesUsed = mutable.HashMap[Symbol, Int]() - val defined = mutable.HashMap[Symbol, DefDef]() + val defined = mutable.HashMap[Symbol, DefDef]() val visitor: Tree => Unit = { - case defdef: DefDef if defdef.symbol.is(Label) => + case d: DefDef if d.symbol.is(Label) => var isRecursive = false - defdef.rhs.foreachSubTree(x => if (x.symbol == defdef.symbol) isRecursive = true) - if (!isRecursive) defined.put(defdef.symbol, defdef) + d.rhs.foreachSubTree { x => + if (x.symbol == d.symbol) + isRecursive = true + } + if (!isRecursive) + defined.put(d.symbol, d) + case t: Apply if t.symbol.is(Label) => val b4 = timesUsed.getOrElseUpdate(t.symbol, 0) timesUsed.put(t.symbol, b4 + 1) + case _ => } def transformer(localCtx: Context): Tree => Tree = { case a: Apply => defined.get(a.symbol) match { - case None => a - case Some(defDef) if a.symbol.is(Label) && timesUsed.getOrElse(a.symbol, 0) == 1 && a.symbol.info.paramInfoss == List(Nil) => + case Some(defDef) if usedOnce(a) && a.symbol.info.paramInfoss == List(Nil) => simplify.println(s"Inlining labeldef ${defDef.name}") defDef.rhs.changeOwner(defDef.symbol, localCtx.owner) + case Some(defDef) if defDef.rhs.isInstanceOf[Literal] => defDef.rhs - case Some(_) => - a + + case _ => a } - case a: DefDef if (a.symbol.is(Label) && timesUsed.getOrElse(a.symbol, 0) == 1 && defined.contains(a.symbol)) => - simplify.println(s"Dropping labeldef (used once) ${a.name} ${timesUsed.get(a.symbol)}") - defined.put(a.symbol, a) + + case d: DefDef if usedOnce(d) => + simplify.println(s"Dropping labeldef (used once) ${d.name} ${timesUsed.get(d.symbol)}") + defined.put(d.symbol, d) EmptyTree - case a: DefDef if (a.symbol.is(Label) && timesUsed.getOrElse(a.symbol, 0) == 0 && defined.contains(a.symbol)) => - simplify.println(s"Dropping labeldef (never used) ${a.name} ${timesUsed.get(a.symbol)}") + + case d: DefDef if neverUsed(d) => + simplify.println(s"Dropping labeldef (never used) ${d.name} ${timesUsed.get(d.symbol)}") EmptyTree + case t => t } + + def usedN(t: Tree, n: Int): Boolean = + t.symbol.is(Label) && + timesUsed.getOrElse(t.symbol, 0) == n && + defined.contains(t.symbol) + + def usedOnce(t: Tree): Boolean = usedN(t, 1) + def neverUsed(t: Tree): Boolean = usedN(t, 0) } diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLocalObjects.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLocalObjects.scala index dbb2772a4e9e..50f53aad68f2 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLocalObjects.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLocalObjects.scala @@ -14,9 +14,9 @@ import transform.SymUtils._ import config.Printers.simplify /** Inline case classes as vals, this essentially (local) implements multi -* parameter value classes. The main motivation is to get ride of all the -* intermediate tuples coming from pattern matching expressions. -*/ + * parameter value classes. The main motivation is to get ride of all the + * intermediate tuples coming from pattern matching expressions. + */ class InlineLocalObjects(implicit val ctx: Context) extends Optimisation { import ast.tpd._ diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala index dd1b2356af75..4f02d2ded683 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala @@ -20,13 +20,15 @@ class Jumpjump(implicit val ctx: Context) extends Optimisation { val visitor: Tree => Unit = { case defdef: DefDef if defdef.symbol.is(Label) => defdef.rhs match { - case Apply(t, args) if t.symbol.is(Label) && - TypeErasure.erasure(defdef.symbol.info.finalResultType).classSymbol == - TypeErasure.erasure(t.symbol.info.finalResultType).classSymbol && - args.size == defdef.vparamss.map(_.size).sum && - args.zip(defdef.vparamss.flatten).forall(x => x._1.symbol eq x._2.symbol) && - !(defdef.symbol eq t.symbol) => - defined(defdef.symbol) = t.symbol + case Apply(t, args) + if t.symbol.is(Label) && + TypeErasure.erasure(defdef.symbol.info.finalResultType).classSymbol == + TypeErasure.erasure(t.symbol.info.finalResultType).classSymbol && + args.size == defdef.vparamss.map(_.size).sum && + args.zip(defdef.vparamss.flatten).forall(x => x._1.symbol eq x._2.symbol) && + defdef.symbol != t.symbol => + + defined(defdef.symbol) = t.symbol case _ => } case _ => diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala b/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala index f99c22919341..1409925f056c 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala @@ -24,7 +24,7 @@ import scala.collection.mutable val checkGood = mutable.HashMap[Symbol, Set[Symbol]]() - def isGood(t: Symbol) = { + def isGood(t: Symbol): Boolean = { t.exists && initializedVals.contains(t) && { var changed = true var set = Set(t) @@ -43,21 +43,31 @@ import scala.collection.mutable val rhsName = rhs.symbol.name if (!vd.symbol.is(Mutable) && !rhs.isEmpty) { def checkNonNull(t: Tree, target: Symbol): Boolean = t match { - case Block(_ , expr) => checkNonNull(expr, target) - case If(_, thenp, elsep) => checkNonNull(thenp, target) && checkNonNull(elsep, target) - case t: New => true + case Block(_ , expr) => + checkNonNull(expr, target) + + case If(_, thenp, elsep) => + checkNonNull(thenp, target) && checkNonNull(elsep, target) + + case _: New | _: This => true + case t: Apply if t.symbol.isPrimaryConstructor => true + case t: Literal => t.const.value != null - case t: This => true + case t: Ident if !t.symbol.owner.isClass => checkGood.put(target, checkGood.getOrElse(target, Set.empty) + t.symbol) true + case t: Apply if !t.symbol.owner.isClass => checkGood.put(target, checkGood.getOrElse(target, Set.empty) + t.symbol) true + case t: Typed => checkNonNull(t.expr, target) + case _ => t.tpe.isNotNull + } if (checkNonNull(vd.rhs, vd.symbol)) initializedVals += vd.symbol @@ -74,14 +84,19 @@ import scala.collection.mutable case _ => false } val transformation: Tree => Tree = { - case check@Apply(Select(lhs, _), List(rhs)) => + case check @ Apply(Select(lhs, _), List(rhs)) => val sym = check.symbol - if ( ((sym == defn.Object_eq) || (sym == defn.Object_ne)) && - ((isNullLiteral(lhs) && isGood(rhs.symbol)) || (isNullLiteral(rhs) && isGood(lhs.symbol)))) { - if (sym == defn.Object_eq) Block(List(lhs, rhs), Literal(Constant(false))) - else if(sym == defn.Object_ne) Block(List(lhs, rhs), Literal(Constant(true))) + val eqOrNe = sym == defn.Object_eq || sym == defn.Object_ne + val nullLhs = isNullLiteral(lhs) && isGood(rhs.symbol) + val nullRhs = isNullLiteral(rhs) && isGood(lhs.symbol) + + if (eqOrNe && (nullLhs || nullRhs)) { + def block(b: Boolean) = Block(List(lhs, rhs), Literal(Constant(b))) + if (sym == defn.Object_eq) block(false) + else if (sym == defn.Object_ne) block(true) else check } else check + case t => t } transformation diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala index 8b77e02f926c..5cc90e3bb8bf 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala @@ -30,23 +30,23 @@ import scala.collection.mutable val possibleRenames = mutable.HashMap[Symbol, Set[Symbol]]() val visitor: Tree => Unit = { - case t: ValDef - if t.symbol.is(Param) => + case t: ValDef if t.symbol.is(Param) => paramsTimesUsed += (t.symbol -> 0) - case valDef: ValDef - if valDef.symbol.is(Mutable) => - valDef.rhs.foreachSubTree { subtree => + + case t: ValDef if t.symbol.is(Mutable) => + t.rhs.foreachSubTree { subtree => if (paramsTimesUsed.contains(subtree.symbol) && - valDef.symbol.info.widenDealias <:< subtree.symbol.info.widenDealias) { - val newSet = possibleRenames.getOrElse(valDef.symbol, Set.empty) + subtree.symbol - possibleRenames.put(valDef.symbol, newSet) + t.symbol.info.widenDealias <:< subtree.symbol.info.widenDealias) { + val newSet = possibleRenames.getOrElse(t.symbol, Set.empty) + subtree.symbol + possibleRenames.put(t.symbol, newSet) } } - case t: RefTree - if paramsTimesUsed.contains(t.symbol) => + + case t: RefTree if paramsTimesUsed.contains(t.symbol) => val param = t.symbol val current = paramsTimesUsed.get(param) current foreach { c => paramsTimesUsed += (param -> (c + 1)) } + case _ => } @@ -57,19 +57,20 @@ import scala.collection.mutable .filter(x => x._2.nonEmpty) .map(x => (x._1, x._2.head)) .toMap + val transformation: Tree => Tree = { - case t: RefTree - if renames.contains(t.symbol) => + case t: RefTree if renames.contains(t.symbol) => ref(renames(t.symbol)) - case t: ValDef - if renames.contains(t.symbol) => + + case t: ValDef if renames.contains(t.symbol) => val replaced = renames(t.symbol) if (t.rhs.symbol == replaced) EmptyTree else ref(replaced).becomes(t.rhs) - case t: ValDef - if paramCandidates.contains(t.symbol) => + + case t: ValDef if paramCandidates.contains(t.symbol) => t.symbol.flags = Mutable t + case t => t } transformation From d69cd1374d7cfa1e9e4574ca160ffcbefc2add74 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Fri, 9 Jun 2017 18:32:04 +0200 Subject: [PATCH 08/16] Improve Simplify's documentation --- .../dotc/transform/localopt/BubbleUpNothing.scala | 10 +++++++++- .../dotc/transform/localopt/ConstantFold.scala | 7 +++++-- .../tools/dotc/transform/localopt/Devalify.scala | 8 +++++++- .../dotc/transform/localopt/DropGoodCasts.scala | 2 ++ .../dotc/transform/localopt/DropNoEffects.scala | 3 +++ .../transform/localopt/InlineCaseIntrinsics.scala | 3 +++ .../localopt/RemoveUnnecessaryNullChecks.scala | 1 - .../tools/dotc/transform/localopt/Simplify.scala | 13 +++++++++++++ .../tools/dotc/transform/localopt/Varify.scala | 2 ++ 9 files changed, 44 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala b/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala index 94c5a8bc625f..bb105dcdd2cc 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala @@ -5,11 +5,19 @@ import core.Contexts.Context import core.Symbols._ import ast.Trees._ -/** Every pure statement preceding a ??? can be removed. +/** + * If a block has a statement that evaluates to Nothing: + * - Every pure statement dirrectly preceding an expression that returns Nothing can be removed, + * - as every statement after an expression that returns Nothing can be removed + * + * If an If condition evalutates to Nothing, the entire If can be replaced by condition + * If an argument evaluates to Nothing, the entire call can be replaced by evaluation of arguments. * * This optimisation makes it rather tricky to write meaningful examples * since the compiler will often be able to reduce them to a single main * method with body = ???. + * + * @author DarkDimius, OlivierBlanvillain */ class BubbleUpNothing(implicit val ctx: Context) extends Optimisation { import ast.tpd._ diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala b/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala index 20e1a62818a1..8819df167b14 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala @@ -11,11 +11,14 @@ import Simplify.desugarIdent /** Various constant folding. * * - Starts/ends with the constant folding implemented in typer (ConstFold). - * + * - Join branches if they are "similar" + * - regularize arithmetic and boolean expressions to have constants on the left, ie. 6 * 2 * a * 5 => 60 * a * - (if) specific optimisation that propagate booleans, negation, and factor - * out (nested) if with equivalent branches wrt to isSimilar (using &&,||). + * out (nested) if with equivalent branches wrt to isSimilar (using &&,||). Dark: ping @OlivierBlanvillain, I didn't understand this * * - Constant propagation over pattern matching. + * + * @author DarkDimius, OlivierBlanvillain */ class ConstantFold(implicit val ctx: Context) extends Optimisation { import ast.tpd._ diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala index 020346932e4e..71e92c5240e3 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala @@ -13,7 +13,13 @@ import config.Printers.simplify import Simplify.desugarIdent import transform.SymUtils._ -/** Inline vals */ +/** Inline vals and remove vals that are aliases to other vals + * + * Notion of alias is a by-value notion, so "good" casts are ignored. + * + * This phase has to be careful not to eliminate vals that are parts of other types + * @author DarkDimius, OlivierBlanvillain + * */ class Devalify(implicit val ctx: Context) extends Optimisation { import ast.tpd._ diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala b/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala index 139d7f8908cf..358e31beed69 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala @@ -16,6 +16,8 @@ import transform.SymUtils._ * * - a.asInstanceOf[T] → a when we know that a: T * - Simplify (a == null) and (a != null) when the result is statically known + * + * @author DarkDimius, OlivierBlanvillain */ class DropGoodCasts(implicit val ctx: Context) extends Optimisation { import ast.tpd._ diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala index 969fb147515a..d03d4cebf120 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala @@ -11,7 +11,10 @@ import ast.Trees._ import Simplify.desugarIdent /** Removes side effect free statements in blocks and Defdef. + * Flattens blocks(except Closure-blocks) * Note: BoxedUnit currently messes up this phase when run after erasure + * + * @author DarkDimius, OlivierBlanvillain */ class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) extends Optimisation { import ast.tpd._ diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala index df32a68bc355..1470184115db 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala @@ -10,12 +10,15 @@ import core.Flags._ import ast.Trees._ import transform.SymUtils._ import Simplify.desugarIdent +import dotty.tools.dotc.ast.tpd /** Inline case class specific methods using desugarings assumptions. * * Note: to run this optimisation after erasure one would need to specialize * it for constructor with outer pointer and values classes. There is * probably no need to run this more than once. + * + * @author DarkDimius, OlivierBlanvillain */ class InlineCaseIntrinsics(implicit val ctx: Context) extends Optimisation { import ast.tpd._ diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala b/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala index 1409925f056c..b35ad15d70db 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala @@ -40,7 +40,6 @@ import scala.collection.mutable val visitor: Tree => Unit = { case vd: ValDef => val rhs = vd.rhs - val rhsName = rhs.symbol.name if (!vd.symbol.is(Mutable) && !rhs.isEmpty) { def checkNonNull(t: Tree, target: Symbol): Boolean = t match { case Block(_ , expr) => diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala index 80d601ced08f..6ba2c8155f2f 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala @@ -14,12 +14,24 @@ import ast.tpd * * 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 MiniPhaseTransform with IdentityDenotTransformer { import tpd._ override def phaseName: String = "simplify" override val cpy = tpd.cpy + /** 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(implicit ctx: Context): List[Optimisation] = new InlineCaseIntrinsics :: new RemoveUnnecessaryNullChecks :: @@ -36,6 +48,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { new ConstantFold :: Nil + /** see comment on beforeErasure */ private def afterErasure(implicit ctx: Context): List[Optimisation] = new Valify(this) :: new Devalify :: diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala index 5cc90e3bb8bf..bef9cb5428f7 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala @@ -11,6 +11,7 @@ import scala.collection.mutable * * { * val l = + * // code that may use l * var r = l * // code not using l * } @@ -19,6 +20,7 @@ import scala.collection.mutable * * { * var r = + * // code that may use l * // code not using l * } */ From ba0caa8bba6a709709a34e822d44b2e0df3ef94f Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Mon, 12 Jun 2017 10:37:25 +0200 Subject: [PATCH 09/16] Use getSimpleName --- .../src/dotty/tools/dotc/transform/localopt/Optimisation.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala index 15a5a6e9da6e..de32a41d8cf2 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala @@ -14,7 +14,7 @@ trait Optimisation { */ def transformer(localCtx: Context): Tree => Tree - def name: String = this.getClass.getName.split('.').last + def name: String = this.getClass.getSimpleName val NoVisitor: Tree => Unit = _ => () } From 93e1b6da5ca57cf81db5bdaef3e581be1d54066f Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Mon, 12 Jun 2017 11:15:54 +0200 Subject: [PATCH 10/16] Collapse ctx and localCtx together --- .../transform/localopt/BubbleUpNothing.scala | 17 ++++++++++------ .../transform/localopt/ConstantFold.scala | 16 +++++++-------- .../dotc/transform/localopt/Devalify.scala | 10 +++++----- .../transform/localopt/DropGoodCasts.scala | 6 +++--- .../transform/localopt/DropNoEffects.scala | 10 +++++----- .../localopt/InlineCaseIntrinsics.scala | 8 ++++---- .../localopt/InlineLabelsCalledOnce.scala | 20 +++++++++++-------- .../localopt/InlineLocalObjects.scala | 8 ++++---- .../transform/localopt/InlineOptions.scala | 16 +++++++++------ .../dotc/transform/localopt/Jumpjump.scala | 11 ++++++---- .../transform/localopt/Optimisation.scala | 4 ++-- .../RemoveUnnecessaryNullChecks.scala | 12 ++++++----- .../dotc/transform/localopt/Simplify.scala | 4 ++-- .../dotc/transform/localopt/Valify.scala | 11 ++++++---- .../dotc/transform/localopt/Varify.scala | 6 +++--- 15 files changed, 90 insertions(+), 69 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala b/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala index bb105dcdd2cc..e379d01b77d4 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala @@ -4,6 +4,7 @@ package transform.localopt import core.Contexts.Context import core.Symbols._ import ast.Trees._ +import dotty.tools.dotc.ast.tpd /** * If a block has a statement that evaluates to Nothing: @@ -19,12 +20,16 @@ import ast.Trees._ * * @author DarkDimius, OlivierBlanvillain */ -class BubbleUpNothing(implicit val ctx: Context) extends Optimisation { +class BubbleUpNothing extends Optimisation { import ast.tpd._ - val visitor = NoVisitor + def visitor(implicit ctx: Context) = NoVisitor - def transformer(localCtx: Context): Tree => Tree = { + + /** Does the actual Tree => Tree transformation, possibly using a different + * context from the one used in Optimisation. + */ + def transformer(implicit ctx: Context): (tpd.Tree) => tpd.Tree = { case t @ Apply(Select(Notathing(qual), _), args) => Typed(qual, TypeTree(t.tpe)) // This case leads to complications with multiple argument lists, @@ -50,8 +55,8 @@ class BubbleUpNothing(implicit val ctx: Context) extends Optimisation { } object Notathing { - def unapply(t: Tree): Option[Tree] = Option(lookup(t)) - def lookup(t: Tree): Tree = t match { + def unapply(t: Tree)(implicit ctx: Context): Option[Tree] = Option(lookup(t)) + def lookup(t: Tree)(implicit ctx: Context): Tree = t match { case x if x.tpe.derivesFrom(defn.NothingClass) => t case Typed(x, _) => lookup(x) case Block(_, x) => lookup(x) @@ -59,7 +64,7 @@ class BubbleUpNothing(implicit val ctx: Context) extends Optimisation { } } - def notathing(t: Tree): Boolean = t match { + def notathing(t: Tree)(implicit ctx: Context): Boolean = t match { case Notathing(_) => true case _ => false } diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala b/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala index 8819df167b14..3b8f8c6a5be1 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala @@ -20,12 +20,12 @@ import Simplify.desugarIdent * * @author DarkDimius, OlivierBlanvillain */ - class ConstantFold(implicit val ctx: Context) extends Optimisation { + class ConstantFold extends Optimisation { import ast.tpd._ - val visitor = NoVisitor + def visitor(implicit ctx: Context) = NoVisitor - def transformer(localCtx: Context): Tree => Tree = { x => preEval(x) match { + def transformer(implicit ctx: Context): Tree => Tree = { x => preEval(x) match { // TODO: include handling of isInstanceOf similar to one in IsInstanceOfEvaluator // TODO: include methods such as Int.int2double(see ./tests/pos/harmonize.scala) case If(cond1, thenp, elsep) if isSimilar(thenp, elsep) => @@ -160,7 +160,7 @@ import Simplify.desugarIdent } } - def preEval(t: Tree) = { + def preEval(t: Tree)(implicit ctx: Context) = { if (t.isInstanceOf[Literal] || t.isInstanceOf[CaseDef] || !isPureExpr(t)) t else { val s = ConstFold.apply(t) @@ -171,7 +171,7 @@ import Simplify.desugarIdent } } - def isSimilar(t1: Tree, t2: Tree): Boolean = t1 match { + def isSimilar(t1: Tree, t2: Tree)(implicit ctx: Context): Boolean = t1 match { case t1: Apply => t2 match { case t2: Apply => @@ -209,7 +209,7 @@ import Simplify.desugarIdent case _ => false } - def isBool(tpe: Type): Boolean = tpe.derivesFrom(defn.BooleanClass) - def isConst(tpe: Type): Boolean = tpe.isInstanceOf[ConstantType] - def asConst(tpe: Type): ConstantType = tpe.asInstanceOf[ConstantType] + def isBool(tpe: Type)(implicit ctx: Context): Boolean = tpe.derivesFrom(defn.BooleanClass) + def isConst(tpe: Type)(implicit ctx: Context): Boolean = tpe.isInstanceOf[ConstantType] + def asConst(tpe: Type)(implicit ctx: Context): ConstantType = tpe.asInstanceOf[ConstantType] } diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala index 71e92c5240e3..d94779e7ed5b 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala @@ -20,7 +20,7 @@ import transform.SymUtils._ * This phase has to be careful not to eliminate vals that are parts of other types * @author DarkDimius, OlivierBlanvillain * */ -class Devalify(implicit val ctx: Context) extends Optimisation { +class Devalify extends Optimisation { import ast.tpd._ val timesUsed = mutable.HashMap[Symbol, Int]() @@ -31,7 +31,7 @@ class Devalify(implicit val ctx: Context) extends Optimisation { // Either a duplicate or a read through series of immutable fields val copies = mutable.HashMap[Symbol, Tree]() - def visitType(tp: Type): Unit = { + def visitType(tp: Type)(implicit ctx: Context): Unit = { tp.foreachPart(x => x match { case TermRef(NoPrefix, _) => val b4 = timesUsedAsType.getOrElseUpdate(x.termSymbol, 0) @@ -40,7 +40,7 @@ class Devalify(implicit val ctx: Context) extends Optimisation { }) } - def doVisit(tree: Tree, used: mutable.HashMap[Symbol, Int]): Unit = tree match { + def doVisit(tree: Tree, used: mutable.HashMap[Symbol, Int])(implicit ctx: Context): Unit = tree match { case valdef: ValDef if !valdef.symbol.is(Param | Mutable | Module | Lazy) && valdef.symbol.exists && !valdef.symbol.owner.isClass => defined += valdef.symbol @@ -73,7 +73,7 @@ class Devalify(implicit val ctx: Context) extends Optimisation { case _ => } - def visitor: Tree => Unit = { tree => + def visitor(implicit ctx: Context): Tree => Unit = { tree => def crossingClassBoundaries(t: Tree): Boolean = t match { case _: New => true case _: Template => true @@ -98,7 +98,7 @@ class Devalify(implicit val ctx: Context) extends Optimisation { doVisit(tree, timesUsed) } - def transformer(localCtx: Context): Tree => Tree = { + override def transformer(implicit ctx: Context): Tree => Tree = { val valsToDrop = defined -- timesUsed.keySet -- timesUsedAsType.keySet val copiesToReplaceAsDuplicates = copies.filter { x => val rhs = dropCasts(x._2) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala b/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala index 358e31beed69..c6b467dbf549 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala @@ -19,12 +19,12 @@ import transform.SymUtils._ * * @author DarkDimius, OlivierBlanvillain */ - class DropGoodCasts(implicit val ctx: Context) extends Optimisation { + class DropGoodCasts extends Optimisation { import ast.tpd._ - val visitor = NoVisitor + def visitor(implicit ctx: Context) = NoVisitor - def transformer(localCtx: Context): Tree => Tree = { + def transformer(implicit ctx: Context): Tree => Tree = { case t @ If(cond, thenp, elsep) => val newTypeTested = collectTypeTests(cond) val nullTested = collectNullTests(cond).toSet diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala index d03d4cebf120..cf2965e05833 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala @@ -16,12 +16,12 @@ import Simplify.desugarIdent * * @author DarkDimius, OlivierBlanvillain */ -class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) extends Optimisation { +class DropNoEffects(val simplifyPhase: Simplify) extends Optimisation { import ast.tpd._ - val visitor = NoVisitor + def visitor(implicit ctx: Context) = NoVisitor - def transformer(localCtx: Context): Tree => Tree = { + def transformer(implicit ctx: Context): Tree => Tree = { // Remove empty blocks case Block(Nil, expr) => expr @@ -59,7 +59,7 @@ class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) exte case t => t } - val keepOnlySideEffects: Tree => Tree = { + def keepOnlySideEffects(t: Tree)(implicit ctx: Context): Tree = t match { case l: Literal => EmptyTree @@ -194,7 +194,7 @@ class DropNoEffects(val simplifyPhase: Simplify)(implicit val ctx: Context) exte ) /** Does this tree has side effects? This is an approximation awaiting real purity analysis... */ - def effectsDontEscape(t: Tree): Boolean = t match { + def effectsDontEscape(t: Tree)(implicit ctx: Context): Boolean = t match { case Apply(fun, _) if fun.symbol.isConstructor && constructorWhiteList.contains(fun.symbol.owner.fullName.toString) => true case Apply(fun, _) if methodsWhiteList.contains(fun.symbol.fullName.toString) => diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala index 1470184115db..1e4d8e10b67a 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala @@ -20,12 +20,12 @@ import dotty.tools.dotc.ast.tpd * * @author DarkDimius, OlivierBlanvillain */ -class InlineCaseIntrinsics(implicit val ctx: Context) extends Optimisation { +class InlineCaseIntrinsics extends Optimisation { import ast.tpd._ - val visitor = NoVisitor + def visitor(implicit ctx: Context): (tpd.Tree) => Unit = NoVisitor - def transformer(localCtx: Context): Tree => Tree = { + def transformer(implicit localCtx: Context): Tree => Tree = { // For synthetic applies on case classes (both dotty/scalac) // - CC.apply(args) → new CC(args) case a: Apply @@ -125,7 +125,7 @@ class InlineCaseIntrinsics(implicit val ctx: Context) extends Optimisation { // Apply fun may be a side-effectful function. E.g. a block, see tests/run/t4859.scala // we need to maintain expressions that were in this block - def evalreceiver(a: Apply, res: Tree) = { + def evalreceiver(a: Apply, res: Tree)(implicit ctx: Context) = { def receiver(t: Tree): Tree = t match { case TypeApply(fun, targs) if fun.symbol eq t.symbol => receiver(fun) case Apply(fn, args) if fn.symbol == t.symbol => receiver(fn) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala index ac7002f8ffe9..9d12f7983e29 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala @@ -8,14 +8,18 @@ import transform.SymUtils._ import scala.collection.mutable import config.Printers.simplify -/** Inlines LabelDef which are used exactly once. */ -class InlineLabelsCalledOnce(implicit val ctx: Context) extends Optimisation { +/** Inlines LabelDef which are used exactly once. + * + * @author DarkDimius, OlivierBlanvillain + * + * */ +class InlineLabelsCalledOnce extends Optimisation { import ast.tpd._ val timesUsed = mutable.HashMap[Symbol, Int]() val defined = mutable.HashMap[Symbol, DefDef]() - val visitor: Tree => Unit = { + def visitor(implicit ctx: Context): Tree => Unit = { case d: DefDef if d.symbol.is(Label) => var isRecursive = false d.rhs.foreachSubTree { x => @@ -32,12 +36,12 @@ class InlineLabelsCalledOnce(implicit val ctx: Context) extends Optimisation { case _ => } - def transformer(localCtx: Context): Tree => Tree = { + def transformer(implicit ctx: Context): Tree => Tree = { case a: Apply => defined.get(a.symbol) match { case Some(defDef) if usedOnce(a) && a.symbol.info.paramInfoss == List(Nil) => simplify.println(s"Inlining labeldef ${defDef.name}") - defDef.rhs.changeOwner(defDef.symbol, localCtx.owner) + defDef.rhs.changeOwner(defDef.symbol, ctx.owner) case Some(defDef) if defDef.rhs.isInstanceOf[Literal] => defDef.rhs @@ -57,11 +61,11 @@ class InlineLabelsCalledOnce(implicit val ctx: Context) extends Optimisation { case t => t } - def usedN(t: Tree, n: Int): Boolean = + def usedN(t: Tree, n: Int)(implicit ctx: Context): Boolean = t.symbol.is(Label) && timesUsed.getOrElse(t.symbol, 0) == n && defined.contains(t.symbol) - def usedOnce(t: Tree): Boolean = usedN(t, 1) - def neverUsed(t: Tree): Boolean = usedN(t, 0) + def usedOnce(t: Tree)(implicit ctx: Context): Boolean = usedN(t, 1) + def neverUsed(t: Tree)(implicit ctx: Context): Boolean = usedN(t, 0) } diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLocalObjects.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLocalObjects.scala index 50f53aad68f2..623583a54ad5 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLocalObjects.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLocalObjects.scala @@ -17,7 +17,7 @@ import config.Printers.simplify * parameter value classes. The main motivation is to get ride of all the * intermediate tuples coming from pattern matching expressions. */ -class InlineLocalObjects(implicit val ctx: Context) extends Optimisation { +class InlineLocalObjects extends Optimisation { import ast.tpd._ // In the end only calls constructor. Reason for unconditional inlining @@ -27,7 +27,7 @@ class InlineLocalObjects(implicit val ctx: Context) extends Optimisation { val forwarderWritesTo = mutable.HashMap[Symbol, Symbol]() val gettersCalled = mutable.HashSet[Symbol]() - def followTailPerfect(t: Tree, symbol: Symbol): Unit = { + def followTailPerfect(t: Tree, symbol: Symbol)(implicit ctx: Context): Unit = { t match { case Block(_, expr) => followTailPerfect(expr, symbol) case If(_, thenp, elsep) => followTailPerfect(thenp, symbol); followTailPerfect(elsep, symbol); @@ -43,7 +43,7 @@ class InlineLocalObjects(implicit val ctx: Context) extends Optimisation { } } - val visitor: Tree => Unit = { + def visitor(implicit ctx: Context): Tree => Unit = { case vdef: ValDef if (vdef.symbol.info.classSymbol is CaseClass) && !vdef.symbol.is(Lazy) && !vdef.symbol.info.classSymbol.caseAccessors.exists(x => x.is(Mutable)) => @@ -63,7 +63,7 @@ class InlineLocalObjects(implicit val ctx: Context) extends Optimisation { case _ => } - def transformer(localCtx: Context): Tree => Tree = { + def transformer(implicit ctx: Context): Tree => Tree = { var hasChanged = true while(hasChanged) { hasChanged = false diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineOptions.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineOptions.scala index 64fea344471e..a18b4c8a037b 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineOptions.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineOptions.scala @@ -9,14 +9,18 @@ import core.Flags._ import ast.Trees._ import scala.collection.mutable -/** Inlines Option methods whose result is known statically. */ -class InlineOptions(implicit val ctx: Context) extends Optimisation { +/** Inlines Option methods whose result is known statically. + * + * + * @author DarkDimius, OlivierBlanvillain + * */ +class InlineOptions extends Optimisation { import ast.tpd._ - val somes = mutable.HashMap[Symbol, Tree]() - val nones = mutable.HashSet[Symbol]() + private val somes = mutable.HashMap[Symbol, Tree]() + private val nones = mutable.HashSet[Symbol]() - val visitor: Tree => Unit = { + def visitor(implicit ctx: Context): Tree => Unit = { case valdef: ValDef if !valdef.symbol.is(Mutable) && valdef.rhs.isInstanceOf[Apply] && valdef.rhs.tpe.derivesFrom(defn.SomeClass) && valdef.rhs.symbol.isPrimaryConstructor => @@ -29,7 +33,7 @@ class InlineOptions(implicit val ctx: Context) extends Optimisation { case _ => } - def transformer(localCtx: Context): Tree => Tree = { tree => + def transformer(implicit ctx: Context): Tree => Tree = { tree => def rewriteSelect(x: Tree) = x match { case Select(rec, nm) if nm == nme.get && somes.contains(rec.symbol) => somes(rec.symbol) case Select(rec, nm) if nm == nme.isDefined && somes.contains(rec.symbol) => Literal(Constant(true)) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala index 4f02d2ded683..d127cd8a73a1 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala @@ -11,13 +11,16 @@ import scala.collection.mutable import config.Printers.simplify import core.Flags._ -/** Rewrites pairs of consecutive LabelDef jumps by jumping directly to the target. */ -class Jumpjump(implicit val ctx: Context) extends Optimisation { +/** Rewrites pairs of consecutive LabelDef jumps by jumping directly to the target. + * + * @author DarkDimius, OlivierBlanvillain + * */ +class Jumpjump extends Optimisation { import ast.tpd._ val defined = mutable.HashMap[Symbol, Symbol]() - val visitor: Tree => Unit = { + def visitor(implicit ctx: Context): Tree => Unit = { case defdef: DefDef if defdef.symbol.is(Label) => defdef.rhs match { case Apply(t, args) @@ -34,7 +37,7 @@ class Jumpjump(implicit val ctx: Context) extends Optimisation { case _ => } - def transformer(localCtx: Context): Tree => Tree = { + def transformer(implicit ctx: Context): Tree => Tree = { case a: Apply if defined.contains(a.fun.symbol) => defined.get(a.symbol) match { case None => a diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala index de32a41d8cf2..d7b76417c456 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala @@ -7,12 +7,12 @@ import ast.tpd.Tree trait Optimisation { /** Run first to gather information on Trees (using mutation) */ - def visitor: Tree => Unit + def visitor(implicit ctx: Context): Tree => Unit /** Does the actual Tree => Tree transformation, possibly using a different * context from the one used in Optimisation. */ - def transformer(localCtx: Context): Tree => Tree + def transformer(implicit ctx: Context): Tree => Tree def name: String = this.getClass.getSimpleName diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala b/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala index b35ad15d70db..1b64b90fbe0c 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala @@ -16,15 +16,18 @@ import scala.collection.mutable * - literal is either null itself or non null * - fallsback to `tpe.isNotNull`, which will eventually be true for non nullable types. * - in (a.call; a == null), the first call throws a NPE if a is null; the test can be removed. + * + * + * @author DarkDimius, Jvican, OlivierBlanvillain */ - class RemoveUnnecessaryNullChecks(implicit val ctx: Context) extends Optimisation { + class RemoveUnnecessaryNullChecks extends Optimisation { import ast.tpd._ val initializedVals = mutable.HashSet[Symbol]() val checkGood = mutable.HashMap[Symbol, Set[Symbol]]() - def isGood(t: Symbol): Boolean = { + def isGood(t: Symbol)(implicit ctx: Context): Boolean = { t.exists && initializedVals.contains(t) && { var changed = true var set = Set(t) @@ -37,7 +40,7 @@ import scala.collection.mutable } } - val visitor: Tree => Unit = { + def visitor(implicit ctx: Context): Tree => Unit = { case vd: ValDef => val rhs = vd.rhs if (!vd.symbol.is(Mutable) && !rhs.isEmpty) { @@ -75,8 +78,7 @@ import scala.collection.mutable } - def transformer(localCtx: Context): Tree => Tree = { - implicit val ctx: Context = localCtx + def transformer(implicit localCtx: Context): Tree => Tree = { def isNullLiteral(tree: Tree) = tree match { case literal: Literal => literal.const.tag == NullTag diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala index 6ba2c8155f2f..8467d60ddd04 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala @@ -32,7 +32,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { * The order of optimizations is tuned to converge faster. * Reordering them may require quadratically more rounds to finish. */ - private def beforeErasure(implicit ctx: Context): List[Optimisation] = + private val beforeErasure: List[Optimisation] = new InlineCaseIntrinsics :: new RemoveUnnecessaryNullChecks :: new InlineOptions :: @@ -49,7 +49,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { Nil /** see comment on beforeErasure */ - private def afterErasure(implicit ctx: Context): List[Optimisation] = + private val afterErasure: List[Optimisation] = new Valify(this) :: new Devalify :: new Jumpjump :: diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala index 510a1d2eaadb..089cc95ac953 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala @@ -8,8 +8,11 @@ import core.Flags._ import ast.Trees._ import scala.collection.mutable -/** Rewrite vars with exactly one assignment as vals. */ -class Valify(val simplifyPhase: Simplify)(implicit val ctx: Context) extends Optimisation { +/** Rewrite vars with exactly one assignment as vals. + * + * @author DarkDimius, OlivierBlanvillain + * */ +class Valify(val simplifyPhase: Simplify) extends Optimisation { import ast.tpd._ // Either a duplicate or a read through series of immutable fields. @@ -21,7 +24,7 @@ class Valify(val simplifyPhase: Simplify)(implicit val ctx: Context) extends Opt val secondWrite: mutable.Map[Symbol, Assign] = mutable.Map() - val visitor: Tree => Unit = { + def visitor(implicit localCtx: Context): Tree => Unit = { case t: ValDef if t.symbol.is(Mutable, Lazy) && !t.symbol.is(Method) && !t.symbol.owner.isClass => if (isPureExpr(t.rhs)) defined(t.symbol) = t @@ -44,7 +47,7 @@ class Valify(val simplifyPhase: Simplify)(implicit val ctx: Context) extends Opt case _ => } - def transformer(localCtx: Context): Tree => Tree = { + def transformer(implicit ctx: Context): Tree => Tree = { case t: Block => // Drop non-side-effecting stats val valdefs = t.stats.collect { case t: ValDef if defined.contains(t.symbol) => t diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala index bef9cb5428f7..c584f7bac100 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala @@ -24,14 +24,14 @@ import scala.collection.mutable * // code not using l * } */ - class Varify(implicit val ctx: Context) extends Optimisation { + class Varify extends Optimisation { import ast.tpd._ val paramsTimesUsed = mutable.HashMap[Symbol, Int]() val possibleRenames = mutable.HashMap[Symbol, Set[Symbol]]() - val visitor: Tree => Unit = { + def visitor(implicit ctx: Context): Tree => Unit = { case t: ValDef if t.symbol.is(Param) => paramsTimesUsed += (t.symbol -> 0) @@ -52,7 +52,7 @@ import scala.collection.mutable case _ => } - def transformer(localCtx: Context): Tree => Tree = { + def transformer(implicit ctx: Context): Tree => Tree = { val paramCandidates = paramsTimesUsed.filter(kv => kv._2 == 1).keySet val renames: Map[Symbol, Symbol] = possibleRenames.iterator .map(kv => (kv._1, kv._2.intersect(paramCandidates))) From a386f63b5210c200ae32608716b8aa16f987683a Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Mon, 12 Jun 2017 11:11:58 +0200 Subject: [PATCH 11/16] Guard fuel with a private final val --- .../dotc/transform/localopt/Simplify.scala | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala index 8467d60ddd04..a3fbf79f8400 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala @@ -67,10 +67,26 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { */ var fuel: Int = -1 + + /** using fuel stops any inlining and prevents optimizations from triggering. + * on my tests it gives 20% slowdown, so it is going to be disabled in public builds. + */ + private final val useFuel = false + + private var optimisations: List[Optimisation] = _ + override def prepareForUnit(tree: Tree)(implicit ctx: Context) = { val maxFuel = ctx.settings.YoptFuel.value + if (!useFuel && maxFuel != ctx.settings.YoptFuel.default) + ctx.error("Optimization fuel-debugging requires enabling in source, see Simplify.scala::useFuel") 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)) + } this } @@ -79,17 +95,12 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { val ctx0 = ctx if (ctx.settings.optimise.value && !tree.symbol.is(Label)) { implicit val ctx: Context = ctx0.withOwner(tree.symbol(ctx0)) - val 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)) - } - + var rhs0 = tree.rhs var rhs1: Tree = null while (rhs1 ne rhs0) { rhs1 = rhs0 - val context = ctx.withOwner(tree.symbol) +// val context = ctx.withOwner(tree.symbol) optimisations.foreach { optimisation => // TODO: fuse for performance // Visit rhs0.foreachSubTree(optimisation.visitor) @@ -100,12 +111,12 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { val innerCtx = if (tree.isDef && tree.symbol.exists) ctx.withOwner(tree.symbol) else ctx val childOptimizedTree = super.transform(tree)(innerCtx) - if (fuel == 0) + if (useFuel && fuel == 0) childOptimizedTree else { val fullyOptimizedTree = optimisation.transformer(ctx)(childOptimizedTree) - if (tree ne fullyOptimizedTree) { + if (useFuel && (tree ne fullyOptimizedTree)) { if (fuel > 0) fuel -= 1 if (fuel != -1 && fuel < 10) { println(s"${tree.symbol} was simplified by ${optimisation.name} (fuel=$fuel): ${tree.show}") From ff91037257df94d72235b0dacdef12fb61723d40 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Fri, 9 Jun 2017 17:01:58 +0200 Subject: [PATCH 12/16] InlineCaseIntrinsics: add .simplified to fix new-unfriendy types --- .../localopt/InlineCaseIntrinsics.scala | 2 +- tests/pos/inline-case-intrinsics.scala | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/pos/inline-case-intrinsics.scala diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala index 1e4d8e10b67a..912a390a3422 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala @@ -47,7 +47,7 @@ class InlineCaseIntrinsics extends Optimisation { case _ => fun } val constructor = a.symbol.owner.companionClass.primaryConstructor.asTerm - evalreceiver(a, rollInArgs(argss.tail, New(a.tpe.widenDealias, constructor, argss.head))) + evalreceiver(a, rollInArgs(argss.tail, New(a.tpe.widenDealias.simplified, constructor, argss.head))) // For synthetic dotty unapplies on case classes: // - CC.unapply(arg): CC → arg diff --git a/tests/pos/inline-case-intrinsics.scala b/tests/pos/inline-case-intrinsics.scala new file mode 100644 index 000000000000..ef16d7d7f11c --- /dev/null +++ b/tests/pos/inline-case-intrinsics.scala @@ -0,0 +1,16 @@ +trait Trait { self => + private case class CC(name: String) + + def m = { + // The type of this Apply after .widenDealias is an AndType: + // TypeRef(AndType( + // TypeRef(ThisType(TypeRef(NoPrefix,)),Trait), + // ThisType(TypeRef(ThisType(TypeRef(NoPrefix,)),Trait)) + // ), CC) + // Which cannot be used as is in a New.... + + val a = CC("") + println(a) + () + } +} From 195d5e613e66ceac23a873f732822da89474cd16 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Mon, 12 Jun 2017 13:35:03 +0200 Subject: [PATCH 13/16] Clean up --- .../transform/localopt/BubbleUpNothing.scala | 17 ++++++----------- .../dotc/transform/localopt/ConstantFold.scala | 12 +++++++++--- .../dotc/transform/localopt/Devalify.scala | 11 ++++++----- .../dotc/transform/localopt/DropNoEffects.scala | 4 ++-- .../localopt/InlineCaseIntrinsics.scala | 4 ++-- .../localopt/InlineLabelsCalledOnce.scala | 5 ++--- .../dotc/transform/localopt/Jumpjump.scala | 4 ++-- .../dotc/transform/localopt/Optimisation.scala | 4 +--- .../localopt/RemoveUnnecessaryNullChecks.scala | 3 +-- .../dotc/transform/localopt/Simplify.scala | 14 +++++++------- .../tools/dotc/transform/localopt/Valify.scala | 6 +++--- 11 files changed, 41 insertions(+), 43 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala b/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala index e379d01b77d4..ccf3d2fb2d64 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala @@ -6,13 +6,12 @@ import core.Symbols._ import ast.Trees._ import dotty.tools.dotc.ast.tpd -/** - * If a block has a statement that evaluates to Nothing: - * - Every pure statement dirrectly preceding an expression that returns Nothing can be removed, - * - as every statement after an expression that returns Nothing can be removed +/** If a block has a statement that evaluates to Nothing: + * - Every pure statement dirrectly preceding an expression that returns Nothing can be removed, + * - as every statement after an expression that returns Nothing can be removed * - * If an If condition evalutates to Nothing, the entire If can be replaced by condition - * If an argument evaluates to Nothing, the entire call can be replaced by evaluation of arguments. + * If an If condition evalutates to Nothing, the entire If can be replaced by condition + * If an argument evaluates to Nothing, the entire call can be replaced by evaluation of arguments. * * This optimisation makes it rather tricky to write meaningful examples * since the compiler will often be able to reduce them to a single main @@ -25,11 +24,7 @@ class BubbleUpNothing extends Optimisation { def visitor(implicit ctx: Context) = NoVisitor - - /** Does the actual Tree => Tree transformation, possibly using a different - * context from the one used in Optimisation. - */ - def transformer(implicit ctx: Context): (tpd.Tree) => tpd.Tree = { + def transformer(implicit ctx: Context): Tree => Tree = { case t @ Apply(Select(Notathing(qual), _), args) => Typed(qual, TypeTree(t.tpe)) // This case leads to complications with multiple argument lists, diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala b/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala index 3b8f8c6a5be1..64e372d52a1e 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala @@ -11,10 +11,16 @@ import Simplify.desugarIdent /** Various constant folding. * * - Starts/ends with the constant folding implemented in typer (ConstFold). + * * - Join branches if they are "similar" - * - regularize arithmetic and boolean expressions to have constants on the left, ie. 6 * 2 * a * 5 => 60 * a + * + * - regularize arithmetic and boolean expressions to have constants on the + * left, ie. 6 * 2 * a * 5 => 60 * a + * * - (if) specific optimisation that propagate booleans, negation, and factor - * out (nested) if with equivalent branches wrt to isSimilar (using &&,||). Dark: ping @OlivierBlanvillain, I didn't understand this + * out (nested) if with equivalent branches wrt to isSimilar. For example: + * - if (b) exp else exp → b; exp + * - if (b1) e1 else if (b2) e1 else e2 → if (b1 || b2) e1 else e2 * * - Constant propagation over pattern matching. * @@ -209,7 +215,7 @@ import Simplify.desugarIdent case _ => false } - def isBool(tpe: Type)(implicit ctx: Context): Boolean = tpe.derivesFrom(defn.BooleanClass) + def isBool(tpe: Type)(implicit ctx: Context): Boolean = tpe.derivesFrom(defn.BooleanClass) def isConst(tpe: Type)(implicit ctx: Context): Boolean = tpe.isInstanceOf[ConstantType] def asConst(tpe: Type)(implicit ctx: Context): ConstantType = tpe.asInstanceOf[ConstantType] } diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala index d94779e7ed5b..91fdaa51d35f 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala @@ -15,11 +15,12 @@ import transform.SymUtils._ /** Inline vals and remove vals that are aliases to other vals * - * Notion of alias is a by-value notion, so "good" casts are ignored. + * Notion of alias is a by-value notion, so "good" casts are ignored. * - * This phase has to be careful not to eliminate vals that are parts of other types - * @author DarkDimius, OlivierBlanvillain - * */ + * This phase has to be careful not to eliminate vals that are parts of other types + * + * @author DarkDimius, OlivierBlanvillain + */ class Devalify extends Optimisation { import ast.tpd._ @@ -98,7 +99,7 @@ class Devalify extends Optimisation { doVisit(tree, timesUsed) } - override def transformer(implicit ctx: Context): Tree => Tree = { + def transformer(implicit ctx: Context): Tree => Tree = { val valsToDrop = defined -- timesUsed.keySet -- timesUsedAsType.keySet val copiesToReplaceAsDuplicates = copies.filter { x => val rhs = dropCasts(x._2) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala index cf2965e05833..8593d91ade66 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala @@ -11,7 +11,7 @@ import ast.Trees._ import Simplify.desugarIdent /** Removes side effect free statements in blocks and Defdef. - * Flattens blocks(except Closure-blocks) + * Flattens blocks (except Closure-blocks) * Note: BoxedUnit currently messes up this phase when run after erasure * * @author DarkDimius, OlivierBlanvillain @@ -59,7 +59,7 @@ class DropNoEffects(val simplifyPhase: Simplify) extends Optimisation { case t => t } - def keepOnlySideEffects(t: Tree)(implicit ctx: Context): Tree = t match { + def keepOnlySideEffects(t: Tree)(implicit ctx: Context): Tree = t match { case l: Literal => EmptyTree diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala index 912a390a3422..a1d7264ee88d 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala @@ -23,9 +23,9 @@ import dotty.tools.dotc.ast.tpd class InlineCaseIntrinsics extends Optimisation { import ast.tpd._ - def visitor(implicit ctx: Context): (tpd.Tree) => Unit = NoVisitor + def visitor(implicit ctx: Context): Tree => Unit = NoVisitor - def transformer(implicit localCtx: Context): Tree => Tree = { + def transformer(implicit ctx: Context): Tree => Tree = { // For synthetic applies on case classes (both dotty/scalac) // - CC.apply(args) → new CC(args) case a: Apply diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala index 9d12f7983e29..67c0abd7b4ce 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala @@ -10,9 +10,8 @@ import config.Printers.simplify /** Inlines LabelDef which are used exactly once. * - * @author DarkDimius, OlivierBlanvillain - * - * */ + * @author DarkDimius, OlivierBlanvillain + */ class InlineLabelsCalledOnce extends Optimisation { import ast.tpd._ diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala index d127cd8a73a1..131a0631c4ab 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala @@ -13,8 +13,8 @@ import core.Flags._ /** Rewrites pairs of consecutive LabelDef jumps by jumping directly to the target. * - * @author DarkDimius, OlivierBlanvillain - * */ + * @author DarkDimius, OlivierBlanvillain + */ class Jumpjump extends Optimisation { import ast.tpd._ diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala index d7b76417c456..f56a17368082 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala @@ -9,9 +9,7 @@ trait Optimisation { /** Run first to gather information on Trees (using mutation) */ def visitor(implicit ctx: Context): Tree => Unit - /** Does the actual Tree => Tree transformation, possibly using a different - * context from the one used in Optimisation. - */ + /** Does the actual Tree => Tree transformation. */ def transformer(implicit ctx: Context): Tree => Tree def name: String = this.getClass.getSimpleName diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala b/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala index 1b64b90fbe0c..89811f8edb74 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala @@ -17,7 +17,6 @@ import scala.collection.mutable * - fallsback to `tpe.isNotNull`, which will eventually be true for non nullable types. * - in (a.call; a == null), the first call throws a NPE if a is null; the test can be removed. * - * * @author DarkDimius, Jvican, OlivierBlanvillain */ class RemoveUnnecessaryNullChecks extends Optimisation { @@ -78,7 +77,7 @@ import scala.collection.mutable } - def transformer(implicit localCtx: Context): Tree => Tree = { + def transformer(implicit ctx: Context): Tree => Tree = { def isNullLiteral(tree: Tree) = tree match { case literal: Literal => literal.const.tag == NullTag diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala index a3fbf79f8400..0160119acb79 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala @@ -27,10 +27,10 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { override val cpy = tpd.cpy /** The original intention is to run most optimizations both before and after erasure. - * Erasure creates new inefficiencies as well as new optimization opportunities. + * 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. + * The order of optimizations is tuned to converge faster. + * Reordering them may require quadratically more rounds to finish. */ private val beforeErasure: List[Optimisation] = new InlineCaseIntrinsics :: @@ -48,7 +48,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { new ConstantFold :: Nil - /** see comment on beforeErasure */ + /** See comment on beforeErasure */ private val afterErasure: List[Optimisation] = new Valify(this) :: new Devalify :: @@ -68,8 +68,8 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { var fuel: Int = -1 - /** using fuel stops any inlining and prevents optimizations from triggering. - * on my tests it gives 20% slowdown, so it is going to be disabled in public builds. + /** Using fuel stops any inlining and prevents optimizations from triggering. + * on my tests it gives 20% slowdown, so it is going to be disabled in public builds. */ private final val useFuel = false @@ -95,7 +95,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { 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) { diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala index 089cc95ac953..8b6d31e5bc9f 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala @@ -10,8 +10,8 @@ import scala.collection.mutable /** Rewrite vars with exactly one assignment as vals. * - * @author DarkDimius, OlivierBlanvillain - * */ + * @author DarkDimius, OlivierBlanvillain + */ class Valify(val simplifyPhase: Simplify) extends Optimisation { import ast.tpd._ @@ -24,7 +24,7 @@ class Valify(val simplifyPhase: Simplify) extends Optimisation { val secondWrite: mutable.Map[Symbol, Assign] = mutable.Map() - def visitor(implicit localCtx: Context): Tree => Unit = { + def visitor(implicit ctx: Context): Tree => Unit = { case t: ValDef if t.symbol.is(Mutable, Lazy) && !t.symbol.is(Method) && !t.symbol.owner.isClass => if (isPureExpr(t.rhs)) defined(t.symbol) = t From 2a666d8baa2a0a00bc0ff3a86913e4db5e7f9679 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Tue, 13 Jun 2017 09:19:36 +0200 Subject: [PATCH 14/16] Revert parts of the refactoring @DarkDimius I think you have mesured the wrong with this 20% slowdown. By not sharing instances Optimistion instead of allocating new ones you where also sharing the mutable.Hashmap instances. The failure on compileStdLib is actaully a proper timeout: keeping these Hashmaps around when compiling all of stdlib prevented GC from doing it job, and (I'm guessing) spent a lot of time reshufuling the Hashmap. I also removed your `private val useFuel = false` back to a simple runtime setting check. I moved the debuging code in a `printIfDifferent` which only adds 3 extra bytecode instructions to the body of the TreeMap.transform (going from 70 to 73). --- .../dotc/transform/localopt/Simplify.scala | 60 ++++++++----------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala index 0160119acb79..cde29a739d91 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala @@ -32,7 +32,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { * The order of optimizations is tuned to converge faster. * Reordering them may require quadratically more rounds to finish. */ - private val beforeErasure: List[Optimisation] = + private def beforeErasure: List[Optimisation] = new InlineCaseIntrinsics :: new RemoveUnnecessaryNullChecks :: new InlineOptions :: @@ -49,7 +49,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { Nil /** See comment on beforeErasure */ - private val afterErasure: List[Optimisation] = + private def afterErasure: List[Optimisation] = new Valify(this) :: new Devalify :: new Jumpjump :: @@ -67,26 +67,10 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { */ var fuel: Int = -1 - - /** Using fuel stops any inlining and prevents optimizations from triggering. - * on my tests it gives 20% slowdown, so it is going to be disabled in public builds. - */ - private final val useFuel = false - - private var optimisations: List[Optimisation] = _ - override def prepareForUnit(tree: Tree)(implicit ctx: Context) = { val maxFuel = ctx.settings.YoptFuel.value - if (!useFuel && maxFuel != ctx.settings.YoptFuel.default) - ctx.error("Optimization fuel-debugging requires enabling in source, see Simplify.scala::useFuel") 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)) - } this } @@ -95,12 +79,17 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { val ctx0 = ctx if (ctx.settings.optimise.value && !tree.symbol.is(Label)) { implicit val ctx: Context = ctx0.withOwner(tree.symbol(ctx0)) + val 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)) + } var rhs0 = tree.rhs var rhs1: Tree = null while (rhs1 ne rhs0) { rhs1 = rhs0 -// val context = ctx.withOwner(tree.symbol) + val context = ctx.withOwner(tree.symbol) optimisations.foreach { optimisation => // TODO: fuse for performance // Visit rhs0.foreachSubTree(optimisation.visitor) @@ -110,21 +99,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { 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) - - if (useFuel && fuel == 0) - childOptimizedTree - else { - val fullyOptimizedTree = optimisation.transformer(ctx)(childOptimizedTree) - - if (useFuel && (tree ne fullyOptimizedTree)) { - if (fuel > 0) fuel -= 1 - if (fuel != -1 && fuel < 10) { - println(s"${tree.symbol} was simplified by ${optimisation.name} (fuel=$fuel): ${tree.show}") - println(s"became after ${optimisation.name}: (fuel=$fuel) ${fullyOptimizedTree.show}") - } - } - fullyOptimizedTree - } + printIfDifferent(childOptimizedTree, optimisation.transformer(ctx)(childOptimizedTree), optimisation) } }.transform(rhs0) } @@ -133,6 +108,23 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { 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. + if (tree1 ne tree2) { + if (fuel > 0) fuel -= 1 + if (fuel != -1) { + println(s"${tree1.symbol} was simplified by ${opt.name} (fuel=$fuel): ${tree1.show}") + println(s"became after ${opt.name}: (fuel=$fuel) ${tree2.show}") + } + } + tree2 + } + } } object Simplify { From 45232ba0d97215cadc81c75a6adcedf128d394b1 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Tue, 13 Jun 2017 10:23:24 +0200 Subject: [PATCH 15/16] Bootstrap with -optimise --- project/Build.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/project/Build.scala b/project/Build.scala index 2dc8ea68b101..c40d184ded6e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -167,6 +167,8 @@ object Build { // otherwise sbt 0.13 incremental compilation breaks (https://github.com/sbt/sbt/issues/3142) scalacOptions ++= Seq("-bootclasspath", sys.props("sun.boot.class.path")), + scalacOptions += "-optimise", + // sbt gets very unhappy if two projects use the same target target := baseDirectory.value / ".." / "out" / "bootstrap" / name.value, From 4be324ebbea4b934ac440171985eb21f9c7f2860 Mon Sep 17 00:00:00 2001 From: Olivier Blanvillain Date: Wed, 14 Jun 2017 11:24:14 +0200 Subject: [PATCH 16/16] Revert moving definitions to Definitions --- .../src/dotty/tools/dotc/core/Definitions.scala | 4 ---- .../dotc/transform/localopt/ConstantFold.scala | 4 ++-- .../transform/localopt/InlineCaseIntrinsics.scala | 4 ++-- .../tools/dotc/transform/localopt/Simplify.scala | 15 +++++++++++---- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 9786ccd2991f..4252fd89719c 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -372,8 +372,6 @@ class Definitions { def Seq_apply(implicit ctx: Context) = Seq_applyR.symbol lazy val Seq_headR = SeqClass.requiredMethodRef(nme.head) def Seq_head(implicit ctx: Context) = Seq_headR.symbol - lazy val SeqFactoryType: TypeRef = ctx.requiredClassRef("scala.collection.generic.SeqFactory") - def SeqFactoryClass(implicit ctx: Context) = SeqFactoryType.symbol.asClass lazy val ArrayType: TypeRef = ctx.requiredClassRef("scala.Array") def ArrayClass(implicit ctx: Context) = ArrayType.symbol.asClass @@ -448,8 +446,6 @@ class Definitions { def Long_* = Long_mulR.symbol lazy val Long_divR = LongClass.requiredMethodRef(nme.DIV, List(LongType)) def Long_/ = Long_divR.symbol - val CommutativePrimitiveOperations = new PerRun[collection.Set[Symbol]](implicit ctx => - Set(defn.Boolean_&&, defn.Boolean_||, defn.Int_+, defn.Int_*, defn.Long_+, defn.Long_*)) lazy val FloatType: TypeRef = valueTypeRef("scala.Float", BoxedFloatType, java.lang.Float.TYPE, FloatEnc) def FloatClass(implicit ctx: Context) = FloatType.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala b/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala index 64e372d52a1e..8bbb25ba84dc 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala @@ -26,7 +26,7 @@ import Simplify.desugarIdent * * @author DarkDimius, OlivierBlanvillain */ - class ConstantFold extends Optimisation { + class ConstantFold(val simplifyPhase: Simplify) extends Optimisation { import ast.tpd._ def visitor(implicit ctx: Context) = NoVisitor @@ -94,7 +94,7 @@ import Simplify.desugarIdent case t @ Apply(Select(lhs, _), List(rhs)) => val sym = t.symbol (lhs, rhs) match { - case (lhs, Literal(_)) if !lhs.isInstanceOf[Literal] && defn.CommutativePrimitiveOperations().contains(sym) => + case (lhs, Literal(_)) if !lhs.isInstanceOf[Literal] && simplifyPhase.CommutativePrimitiveOperations.contains(sym) => rhs.select(sym).appliedTo(lhs) case (l, _) if (sym == defn.Boolean_&&) && isConst(l.tpe) => diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala index a1d7264ee88d..41ba182d8f95 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala @@ -20,7 +20,7 @@ import dotty.tools.dotc.ast.tpd * * @author DarkDimius, OlivierBlanvillain */ -class InlineCaseIntrinsics extends Optimisation { +class InlineCaseIntrinsics(val simplifyPhase: Simplify) extends Optimisation { import ast.tpd._ def visitor(implicit ctx: Context): Tree => Unit = NoVisitor @@ -100,7 +100,7 @@ class InlineCaseIntrinsics extends Optimisation { // Where Seq is any companion of type <: SeqFactoryClass case a: Apply if a.symbol.name == nme.unapplySeq && - a.symbol.owner.derivesFrom(defn.SeqFactoryClass) && + a.symbol.owner.derivesFrom(simplifyPhase.SeqFactoryClass) && a.symbol.extendedOverriddenSymbols.isEmpty && (isPureExpr(a.fun) || a.fun.symbol.is(Synthetic)) => diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala index cde29a739d91..fcd3a6872b49 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala @@ -3,10 +3,12 @@ package transform.localopt import core.Contexts.Context import core.DenotTransformers.IdentityDenotTransformer +import core.Symbols._ import core.Types._ +import core.Flags._ +import core.Decorators._ import transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo} import config.Printers.simplify -import core.Flags._ import ast.tpd /** This phase consists of a series of small, simple, local optimisations @@ -26,6 +28,9 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { 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. * @@ -33,7 +38,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { * Reordering them may require quadratically more rounds to finish. */ private def beforeErasure: List[Optimisation] = - new InlineCaseIntrinsics :: + new InlineCaseIntrinsics(this) :: new RemoveUnnecessaryNullChecks :: new InlineOptions :: new InlineLabelsCalledOnce :: @@ -45,7 +50,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { // new InlineLocalObjects :: // followCases needs to be fixed, see ./tests/pos/rbtree.scala // new Varify :: // varify could stop other transformations from being applied. postponed. // new BubbleUpNothing :: - new ConstantFold :: + new ConstantFold(this) :: Nil /** See comment on beforeErasure */ @@ -54,7 +59,7 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { new Devalify :: new Jumpjump :: new DropGoodCasts :: - new ConstantFold :: + new ConstantFold(this) :: Nil /** Optimisation fuel, for debugging. Decremented every time Simplify @@ -68,6 +73,8 @@ class Simplify extends MiniPhaseTransform with IdentityDenotTransformer { var fuel: Int = -1 override def prepareForUnit(tree: Tree)(implicit ctx: Context) = { + 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