diff --git a/.drone.yml b/.drone.yml index a31c04c43d52..5e06fefb030b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -36,13 +36,6 @@ pipeline: - cp -R . /tmp/2/ && cd /tmp/2/ - ./project/scripts/sbt ";dotty-bootstrapped/compile ;dotty-bootstrapped/test" - test_optimised: - group: test - image: lampepfl/dotty:2018-06-29 - commands: - - cp -R . /tmp/3/ && cd /tmp/3/ - - ./project/scripts/sbt dotty-optimised/test - test_sbt: group: test image: lampepfl/dotty:2018-06-29 diff --git a/build.sbt b/build.sbt index d045d37f2710..d0758ebb5625 100644 --- a/build.sbt +++ b/build.sbt @@ -1,29 +1,23 @@ val dotty = Build.dotty val `dotty-bootstrapped` = Build.`dotty-bootstrapped` -val `dotty-optimised` = Build.`dotty-optimised` val `dotty-interfaces` = Build.`dotty-interfaces` val `dotty-doc` = Build.`dotty-doc` val `dotty-doc-bootstrapped` = Build.`dotty-doc-bootstrapped` -val `dotty-doc-optimised` = Build.`dotty-doc-optimised` val `dotty-compiler` = Build.`dotty-compiler` val `dotty-compiler-bootstrapped` = Build.`dotty-compiler-bootstrapped` -val `dotty-compiler-optimised` = Build.`dotty-compiler-optimised` val `dotty-library` = Build.`dotty-library` val `dotty-library-bootstrapped` = Build.`dotty-library-bootstrapped` -val `dotty-library-optimised` = Build.`dotty-library-optimised` val `dotty-sbt-bridge` = Build.`dotty-sbt-bridge` val `dotty-sbt-bridge-bootstrapped` = Build.`dotty-sbt-bridge-bootstrapped` val `dotty-language-server` = Build.`dotty-language-server` val `dotty-bench` = Build.`dotty-bench` val `dotty-bench-bootstrapped` = Build.`dotty-bench-bootstrapped` -val `dotty-bench-optimised` = Build.`dotty-bench-optimised` val `scala-library` = Build.`scala-library` val `scala-compiler` = Build.`scala-compiler` val `scala-reflect` = Build.`scala-reflect` val scalap = Build.scalap val dist = Build.dist val `dist-bootstrapped` = Build.`dist-bootstrapped` -val `dist-optimised` = Build.`dist-optimised` val `sbt-dotty` = Build.`sbt-dotty` val `vscode-dotty` = Build.`vscode-dotty` diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 0d22c55a2356..b4512be18708 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -15,7 +15,7 @@ import util.FreshNameCreator import core.DenotTransformers.DenotTransformer import core.Denotations.SingleDenotation import dotty.tools.backend.jvm.{CollectSuperCalls, GenBCode, LabelDefs} -import dotty.tools.dotc.transform.localopt.{Simplify, StringInterpolatorOpt} +import dotty.tools.dotc.transform.localopt.StringInterpolatorOpt /** The central class of the dotc compiler. The job of a compiler is to create * runs, which process given `phases` in a given `rootContext`. @@ -92,7 +92,6 @@ class Compiler { new ElimOuterSelect, // Expand outer selections new AugmentScala2Traits, // Expand traits defined in Scala 2.x to simulate old-style rewritings new ResolveSuper, // Implement super accessors and add forwarders to trait methods - new Simplify, // Perform local optimizations, simplified versions of what linker does. new PrimitiveForwarders, // Add forwarders to trait methods that have a mismatch between generic and primitives new FunctionXXLForwarders, // Add forwarders for FunctionXXL apply method new ArrayConstructors) :: // Intercept creation of (non-generic) arrays and intrinsify. @@ -107,8 +106,7 @@ class Compiler { List(new Constructors, // Collect initialization code in primary constructors // Note: constructors changes decls in transformTemplate, no InfoTransformers should be added after it new FunctionalInterfaces, // Rewrites closures to implement @specialized types of Functions. - new GetClass, // Rewrites getClass calls on primitive types. - new Simplify) :: // Perform local optimizations, simplified versions of what linker does. + new GetClass) :: // Rewrites getClass calls on primitive types. List(new LinkScala2Impls, // Redirect calls to trait methods defined by Scala 2.x, so that they now go to their implementations new LambdaLift, // Lifts out nested functions to class scope, storing free variables in environments // Note: in this mini-phase block scopes are incorrect. No phases that rely on scopes should be here diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index dfda54f034e7..94d1b689c9e0 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -143,10 +143,7 @@ class ScalaSettings extends Settings.SettingGroup { val YshowNoInline = BooleanSetting("-Yshow-no-inline", "Show inlined code without the 'inlined from' info") /** Linker specific flags */ - val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying local optimisations to the .program") withAbbreviation "-optimize" val Xlink = BooleanSetting("-Xlink", "Recompile library code with the application.") - 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 YnoDecodeStacktraces = BooleanSetting("-Yno-decode-stacktraces", "Show raw StackOverflow stacktraces, instead of decoding them into triggering operations.") /** Dottydoc specific settings */ diff --git a/compiler/src/dotty/tools/dotc/quoted/ToolboxSettings.scala b/compiler/src/dotty/tools/dotc/quoted/ToolboxSettings.scala index 98027f4c694e..45f4db2db7cd 100644 --- a/compiler/src/dotty/tools/dotc/quoted/ToolboxSettings.scala +++ b/compiler/src/dotty/tools/dotc/quoted/ToolboxSettings.scala @@ -7,22 +7,16 @@ object ToolboxSettings { implicit def default: ToolboxSettings = make() /** Make toolbox settings - * @param optimise Enable optimisation when compiling the quoted code * @param outDir Output directory for the compiled quote. If set to None the output will be in memory * @param color Print output with colors * @param rawTree Do not remove quote tree artifacts * @param compilerArgs Compiler arguments. Use only if you know what you are doing. */ def make( - optimise: Boolean = false, color: Boolean = false, rawTree: Boolean = false, outDir: Option[String] = None, compilerArgs: List[String] = Nil - ): ToolboxSettings = { - var compilerArgs1 = compilerArgs - if (optimise) compilerArgs1 = "-optimise" :: compilerArgs1 - new ToolboxSettings(outDir, rawTree, compilerArgs1) - } - + ): ToolboxSettings = + new ToolboxSettings(outDir, rawTree, compilerArgs) } diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala b/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala deleted file mode 100644 index 936c79f02bce..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/BubbleUpNothing.scala +++ /dev/null @@ -1,67 +0,0 @@ -package dotty.tools.dotc -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: - * - 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 extends Optimisation { - import ast.tpd._ - - def visitor(implicit ctx: Context) = NoVisitor - def clear(): Unit = () - - 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, - // 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)(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) - case _ => null - } - } - - 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 deleted file mode 100644 index 7d3416737bb9..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/ConstantFold.scala +++ /dev/null @@ -1,198 +0,0 @@ -package dotty.tools.dotc -package transform.localopt - -import core.Contexts.Context -import core.Symbols._ -import core.Types._ -import typer.ConstFold -import ast.Trees._ - -/** 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. For example: - * - if (b) exp else exp → b; exp - * - if (b1) e1 else if (b2) e1 else e2 → if (b1 || b2) e1 else e2 - * - if(!b) e1 else e2 → if(b) e2 else e1 - * - * - Constant propagation over pattern matching. - * - * @author DarkDimius, OlivierBlanvillain, gan74 - */ - class ConstantFold(val simplifyPhase: Simplify) extends Optimisation { - import ast.tpd._ - - def visitor(implicit ctx: Context) = NoVisitor - def clear(): Unit = () - - def transformer(implicit ctx: Context): Tree => Tree = { - // 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] && simplifyPhase.CommutativePrimitiveOperations.contains(sym) => - rhs.select(sym).appliedTo(lhs) - - 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 (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 (l: Literal, r: Literal) => - (l.tpe.widenTermRefExpr, r.tpe.widenTermRefExpr) match { - case (ConstantType(_), ConstantType(_)) => - val s = ConstFold.apply(t) - if ((s ne null) && s.tpe.isInstanceOf[ConstantType]) Literal(s.tpe.asInstanceOf[ConstantType].value) - else t - case _ => t - } - - 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 => t - } - - def isSimilar(t1: Tree, t2: Tree)(implicit ctx: Context): 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 => - t2 match { - case t2: Ident => - t1.symbol eq t2.symbol - case _ => // Select - isSimilar(t2, t1) - } - 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 t2: Select => isSimilar(t1, t2) - case _ => 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)(implicit ctx: Context): Boolean = tpe.derivesFrom(defn.BooleanClass) -} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala deleted file mode 100644 index 419c94765b76..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Devalify.scala +++ /dev/null @@ -1,214 +0,0 @@ -package dotty.tools.dotc -package transform.localopt - -import core.Constants.Constant -import core.Contexts.Context -import core.Flags._ -import core.Symbols._ -import core.Types._ -import ast.Trees._ -import scala.collection.mutable -import config.Printers.simplify -import Simplify._ -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. - * - * 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._ - - val timesUsed = newMutableSymbolMap[Int] - val timesUsedAsType = newMutableSymbolMap[Int] - - val defined = mutable.HashSet[Symbol]() - val usedInInnerClass = newMutableSymbolMap[Int] - // Either a duplicate or a read through series of immutable fields - val copies = newMutableSymbolMap[Tree] - - def clear(): Unit = { - timesUsed.clear() - timesUsedAsType.clear() - defined.clear() - usedInInnerClass.clear() - copies.clear() - } - - def visitType(tp: Type)(implicit ctx: Context): Unit = { - tp.foreachPart(x => x match { - case TermRef(NoPrefix, _) => - val b4 = timesUsedAsType.getOrElseUpdate(x.termSymbol, 0) - timesUsedAsType.update(x.termSymbol, b4 + 1) - case _ => - }) - } - - def doVisit(tree: Tree, used: MutableSymbolMap[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 - - dropCasts(valdef.rhs) match { - case t: Tree if readingOnlyVals(t) => - copies.update(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(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.update(t.symbol, b4 + 1) - case _ => - } - - def visitor(implicit ctx: Context): 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(implicit ctx: Context): Tree => Tree = { - val valsToDrop = defined -- timesUsed.keysIterator -- timesUsedAsType.keysIterator - val copiesToReplaceAsDuplicates = copies.filter { x => - val rhs = dropCasts(x._2) - rhs.isInstanceOf[Literal] || (!rhs.symbol.owner.isClass && !rhs.symbol.is(Method | Mutable)) - } -- timesUsedAsType.keysIterator - // 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.keysIterator - - val replacements = copiesToReplaceAsDuplicates ++ copiesToReplaceAsUsedOnce -- usedInInnerClass.keysIterator - - 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 t: DefDef if !t.symbol.owner.isClass => - if (timesUsed.getOrElse(t.symbol, 0) + timesUsedAsType.getOrElse(t.symbol, 0) != 0) t - else { - simplify.println(s"Dropping definition of ${t.symbol.showFullName} as not used") - EmptyTree - } - 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)) => - val isAsInstanceOf = fun.symbol == defn.Any_asInstanceOf && rec.tpe.derivesFrom(tp.tpe.classSymbol) - isAsInstanceOf && readingOnlyVals(rec) - - case t @ Apply(Select(rec, _), Nil) => - isImmutableAccessor(t) && readingOnlyVals(rec) - - case t @ Select(rec, _) if t.symbol.is(Method) => - isImmutableAccessor(t) && readingOnlyVals(rec) - - case t @ Select(qual, _) if !isEffectivelyMutable(t) => - readingOnlyVals(qual) - - case t: Ident if !t.symbol.is(Mutable | Method) && !t.symbol.info.dealias.isInstanceOf[ExprType] => - desugarIdent(t) match { case s: Select => readingOnlyVals(s); case _ => 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 deleted file mode 100644 index 1bbbd10ae4bc..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/DropGoodCasts.scala +++ /dev/null @@ -1,112 +0,0 @@ -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._ -import Simplify.isEffectivelyMutable - -/** 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 - * - * @author DarkDimius, OlivierBlanvillain - */ - class DropGoodCasts extends Optimisation { - import ast.tpd._ - - def visitor(implicit ctx: Context) = NoVisitor - def clear(): Unit = () - - def transformer(implicit ctx: 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_||) => - 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) && - !isEffectivelyMutable(x) && - !x.symbol.is(Method) && - x.symbol.exists && !x.symbol.owner.isClass => - (x.symbol, tp.tpe) :: Nil - - case _ => Nil - } - 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_||) => - 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) && - !isEffectivelyMutable(x) && - !x.symbol.is(Method) && - 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 deleted file mode 100644 index f84ec732e25c..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/DropNoEffects.scala +++ /dev/null @@ -1,203 +0,0 @@ -package dotty.tools.dotc -package transform.localopt - -import core.TypeErasure -import core.Contexts.Context -import core.Symbols._ -import core.Types._ -import core.Flags._ -import ast.Trees._ -import Simplify._ - -/** 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) extends Optimisation { - import ast.tpd._ - - def visitor(implicit ctx: Context) = NoVisitor - def clear(): Unit = () - - def transformer(implicit ctx: 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 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 - - // 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)(implicit ctx: Context): Tree = t match { - case l: Literal => - EmptyTree - - 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 isImmutableAccessor(t) => - keepOnlySideEffects(rec) - - // !name.eq(nme.TYPE_) && // Keep the .TYPE added by ClassOf, would be needed for AfterErasure - // Without is(JavaStatic), { System.out } becomes { System }, but "Java class can't be used as value" - case s @ Select(qual, name) if !s.symbol.is(Mutable | Lazy | Method | JavaStatic) => - 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) => - val prefix = desugarIdentPrefix(t) - if (!prefix.isEmpty && !prefix.symbol.is(allOf(JavaDefined, Package))) t - else 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) - } - seq(prefix, unitLiteral) - - case t => 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" - ) - - /** Does this tree has side effects? This is an approximation awaiting real purity analysis... */ - 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) => - 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 deleted file mode 100644 index cdb4d3e74066..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineCaseIntrinsics.scala +++ /dev/null @@ -1,141 +0,0 @@ -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 core.TypeApplications.noBounds -import ast.Trees._ -import transform.SymUtils._ -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(val simplifyPhase: Simplify) extends Optimisation { - import ast.tpd._ - - def visitor(implicit ctx: Context): Tree => Unit = NoVisitor - def clear(): Unit = () - - 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 - 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 - 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 - // - 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 - evalreceiver(a, Literal(Constant(true))) - else - // CC.unapply(arg): CC → arg - evalreceiver(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 = noBounds(a.tpe.baseType(defn.OptionClass).argInfos.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)) - evalreceiver(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(simplifyPhase.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) - case t: TypeApply => receiver(t.fun) - case t: Ident => - val prefix = desugarIdentPrefix(t) - prefix.tpe.widenDealias - case t: Select => t.qualifier.tpe.widenDealias - } - - val recv = receiver(a) - if (recv.typeSymbol.is(Module)) { - val someTpe = a.tpe.translateParameterized(defn.OptionClass, defn.SomeClass) - evalreceiver(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 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) - case Select(qual, _) => qual - case x => x - } - val recv = receiver(a) - if (recv.isEmpty || isPureRef(recv)) - res - else - Block(recv :: Nil, res) - } -} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala deleted file mode 100644 index d993ac983288..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLabelsCalledOnce.scala +++ /dev/null @@ -1,75 +0,0 @@ -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. - * - * @author DarkDimius, OlivierBlanvillain - */ -class InlineLabelsCalledOnce extends Optimisation { - import ast.tpd._ - - val timesUsed = newMutableSymbolMap[Int] - val defined = newMutableSymbolMap[DefDef] - - def clear(): Unit = { - timesUsed.clear() - defined.clear() - } - - def visitor(implicit ctx: Context): Tree => Unit = { - case d: DefDef if d.symbol.is(Label) => - var isRecursive = false - d.rhs.foreachSubTree { x => - if (x.symbol == d.symbol) - isRecursive = true - } - if (!isRecursive) - defined.update(d.symbol, d) - - case t: Apply if t.symbol.is(Label) => - val b4 = timesUsed.getOrElseUpdate(t.symbol, 0) - timesUsed.update(t.symbol, b4 + 1) - - case _ => - } - - 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, ctx.owner) - - case Some(defDef) if defDef.rhs.isInstanceOf[Literal] => - defDef.rhs - - case _ => a - } - - case d: DefDef if usedOnce(d) => - simplify.println(s"Dropping labeldef (used once) ${d.name} ${timesUsed.get(d.symbol)}") - defined.update(d.symbol, d) - EmptyTree - - 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)(implicit ctx: Context): Boolean = - t.symbol.is(Label) && - timesUsed.getOrElse(t.symbol, 0) == n && - defined.contains(t.symbol) - - 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 deleted file mode 100644 index 64bff5521381..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineLocalObjects.scala +++ /dev/null @@ -1,112 +0,0 @@ -package dotty.tools.dotc -package transform.localopt - -import core.Constants.Constant -import core.Contexts.Context -import core.Decorators._ -import core.Names.Name -import core.NameKinds.LocalOptInlineLocalObj -import core.Types.Type -import core.StdNames._ -import core.Symbols._ -import core.Flags._ -import ast.Trees._ -import scala.collection.mutable -import scala.collection.mutable.LinkedHashMap -import transform.SymUtils._ -import config.Printers.simplify -import Simplify._ - -/** Rewrite fields of local instances as vals. - * - * If a local instance does not escape the local scope, it will be removed - * later by DropNoEffects, thus implementing the equivalent of (local) multi - * parameter value classes. The main motivation for this transformation is to - * get ride of the intermediate tuples object somes created when pattern - * matching on Scala2 case classes. - */ -class InlineLocalObjects(val simplifyPhase: Simplify) extends Optimisation { - import ast.tpd._ - - // ValDefs whose rhs is a case class instantiation: potential candidates. - val candidates = mutable.HashSet[Symbol]() - - // ValDefs whose lhs is used with `._1` (or any getter call). - val gettersCalled = mutable.HashSet[Symbol]() - - // Immutable sorted map from class to new fields, initialized between visitor and transformer. - var newFieldsMapping: Map[Symbol, LinkedHashMap[Symbol, Symbol]] = null - // | | | - // | | New fields, replacements these getters - // | Usages of getters of these classes - // ValDefs of the classes that are being torn apart; = candidates.intersect(gettersCalled) - - def clear(): Unit = { - candidates.clear() - gettersCalled.clear() - newFieldsMapping = null - } - - def initNewFieldsMapping()(implicit ctx: Context): Unit = - if (newFieldsMapping == null) { - newFieldsMapping = candidates.intersect(gettersCalled).map { refVal => - val accessors = refVal.info.classSymbol.caseAccessors.filter(_.isGetter) - val newLocals = accessors.map { x => - val owner: Symbol = refVal.owner - val name: Name = LocalOptInlineLocalObj.fresh() - val flags: FlagSet = Synthetic - val info: Type = x.asSeenFrom(refVal.info).info.finalResultType.widenDealias - ctx.newSymbol(owner, name, flags, info) - } - (refVal, LinkedHashMap[Symbol, Symbol](accessors.zip(newLocals): _*)) - }.toMap - } - - // Pattern for candidates to this optimisation: ValDefs where the rhs is an - // immutable case class instantiation. - object NewCaseClassValDef { - def unapply(t: ValDef)(implicit ctx: Context): Option[(Tree, List[Tree])] = - t.rhs match { - case Apply(fun, args) - if t.symbol.info.classSymbol.is(CaseClass) && // is rhs a case class? - !t.symbol.is(Lazy | Mutable) && // is lhs a val? - !t.symbol.info.classSymbol.caseAccessors.exists(_.is(Mutable)) && // is the case class immutable? - fun.symbol.isConstructor && // is rhs a new? - t.tpe.widenDealias == t.symbol.info.finalResultType.widenDealias => // no case class inheritance or enums - Some((fun, args)) - case _ => None - } - } - - def visitor(implicit ctx: Context): Tree => Unit = { - case t @ NewCaseClassValDef(fun, args) => - candidates += t.symbol - case t @ Select(qual, _) if isImmutableAccessor(t) => - gettersCalled += qual.symbol - case _ => - } - - def transformer(implicit ctx: Context): Tree => Tree = { - initNewFieldsMapping(); - { - case t @ NewCaseClassValDef(fun, args) if newFieldsMapping.contains(t.symbol) => - val newFields = newFieldsMapping(t.symbol).values.toList - val newFieldsDefs = newFields.zip(args).map { case (nf, arg) => - val rhs = arg.changeOwnerAfter(t.symbol, nf, simplifyPhase) - ValDef(nf.asTerm, rhs) - } - val recreate = cpy.ValDef(t)(rhs = fun.appliedToArgs(newFields.map(x => ref(x)))) - simplify.println(s"Replacing ${t.symbol.fullName} with stack-allocated fields ($newFields)") - Thicket(newFieldsDefs :+ recreate) - - case t @ Select(rec, _) if isImmutableAccessor(t) => - newFieldsMapping.getOrElse(rec.symbol, Map.empty[Symbol, Symbol]).get(t.symbol) match { - case None => t - case Some(newSym) => ref(newSym) - } - - case t => t - } - } -} - diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/InlineOptions.scala b/compiler/src/dotty/tools/dotc/transform/localopt/InlineOptions.scala deleted file mode 100644 index 3edaa96d5f2f..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/InlineOptions.scala +++ /dev/null @@ -1,60 +0,0 @@ -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. - * - * - * @author DarkDimius, OlivierBlanvillain - * */ -class InlineOptions extends Optimisation { - import ast.tpd._ - - val somes = newMutableSymbolMap[Tree] - val nones = mutable.HashSet[Symbol]() - - def clear(): Unit = { - somes.clear() - nones.clear() - } - - 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 => - 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(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)) - 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 deleted file mode 100644 index 9f4f9a8fc82c..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Jumpjump.scala +++ /dev/null @@ -1,56 +0,0 @@ -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. - * - * @author DarkDimius, OlivierBlanvillain - */ -class Jumpjump extends Optimisation { - import ast.tpd._ - - val defined = newMutableSymbolMap[Symbol] - - def clear(): Unit = defined.clear() - - def visitor(implicit ctx: Context): 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 != t.symbol => - - defined(defdef.symbol) = t.symbol - case _ => - } - case _ => - } - - def transformer(implicit ctx: 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 deleted file mode 100644 index f52d4cf705bf..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Optimisation.scala +++ /dev/null @@ -1,21 +0,0 @@ -package dotty.tools.dotc -package transform.localopt - -import core.Contexts.Context -import ast.tpd.Tree - -trait Optimisation { - - /** Gathers information on trees (using mutation), to be run first. */ - def visitor(implicit ctx: Context): Tree => Unit - - /** Does the actual Tree => Tree transformation. */ - def transformer(implicit ctx: Context): Tree => Tree - - /** Clears all the state of this optimisation, to be run last. */ - def clear(): Unit - - def name: String = this.getClass.getSimpleName - - 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 deleted file mode 100644 index 72144b031a5b..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/RemoveUnnecessaryNullChecks.scala +++ /dev/null @@ -1,109 +0,0 @@ -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. - * - * @author DarkDimius, Jvican, OlivierBlanvillain - */ - class RemoveUnnecessaryNullChecks extends Optimisation { - import ast.tpd._ - - val initializedVals = mutable.HashSet[Symbol]() - - val checkGood = newMutableSymbolMap[Set[Symbol]] - - def clear(): Unit = { - initializedVals.clear() - checkGood.clear() - } - - def isGood(t: Symbol)(implicit ctx: Context): Boolean = { - 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)) - } - } - - def visitor(implicit ctx: Context): Tree => Unit = { - case vd: ValDef => - val rhs = vd.rhs - 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 _: New | _: This => true - - case t: Apply if t.symbol.isPrimaryConstructor => true - - case t: Literal => t.const.value != null - - case t: Ident if !t.symbol.owner.isClass => - checkGood.update(target, checkGood.getOrElse(target, Set.empty) + t.symbol) - true - - case t: Apply if !t.symbol.owner.isClass => - checkGood.update(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(implicit ctx: Context): Tree => Tree = { - 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 - 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/Simplify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala deleted file mode 100644 index 8f0ca36c1a01..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala +++ /dev/null @@ -1,176 +0,0 @@ -package dotty.tools.dotc -package transform.localopt - -import core.Contexts.Context -import core.DenotTransformers.IdentityDenotTransformer -import core.Symbols._ -import core.Types._ -import core.Flags._ -import core.Decorators._ -import core.NameOps._ -import transform.MegaPhase.MiniPhase -import config.Printers.simplify -import ast.tpd - -import scala.annotation.tailrec - -/** This phase consists of a series of small, simple, local optimisations - * applied as a fix point transformation over Dotty Trees. - * - * The termination condition uses referential equality on Trees. Furthermore, - * termination relies of every optimisation to be shrinking transformations. - * - * This phase is intended to be run multiple times in the compilation pipeline. - * This is due to several reasons: - * - running this phase early allows to reduce size of compilation unit, speeding up subsequent transformations. - * - running this phase late allows to eliminate inefficiencies created by previous phase - * - different patters are easier to optimize at different moments of pipeline - */ -class Simplify extends MiniPhase with IdentityDenotTransformer { - import tpd._ - override def phaseName: String = "simplify" - override val cpy = tpd.cpy - - private[localopt] var SeqFactoryClass: Symbol = null - private[localopt] var CommutativePrimitiveOperations: Set[Symbol] = null - - /** The original intention is to run most optimizations both before and after erasure. - * Erasure creates new inefficiencies as well as new optimization opportunities. - * - * The order of optimizations is tuned to converge faster. - * Reordering them may require quadratically more rounds to finish. - */ - private def beforeErasure: List[Optimisation] = - new InlineCaseIntrinsics(this) :: - new RemoveUnnecessaryNullChecks :: - new InlineOptions :: - //new InlineLabelsCalledOnce :: // not needed: new pattern matcher does this already - new Valify(this) :: - new Devalify :: - new Jumpjump :: - new DropGoodCasts :: - new DropNoEffects(this) :: - new InlineLocalObjects(this) :: - // new Varify :: // varify could stop other transformations from being applied. postponed. - // new BubbleUpNothing :: - new ConstantFold(this) :: - Nil - - /** See comment on beforeErasure */ - private def afterErasure: List[Optimisation] = - new Valify(this) :: - new Devalify :: - new Jumpjump :: - new DropGoodCasts :: - new DropNoEffects(this) :: - new ConstantFold(this) :: - Nil - - var optimisations: List[Optimisation] = Nil - - /** Optimisation fuel, for debugging. Decremented every time Simplify - * applies an optimisation until fuel == 0. Original idea from Automatic - * Isolation of Compiler Errors by David Whalley. Unable with -Yopt-fuel. - * - * The fuel can be used to do a bisection on large test cases that fail - * -optimise. See compiler/test/bisect.sh for a shell script to automates - * the bisection search. - */ - var fuel: Int = -1 - - override def prepareForUnit(tree: Tree)(implicit ctx: Context) = { - if (ctx.settings.optimise.value) { - SeqFactoryClass = ctx.requiredClass("scala.collection.generic.SeqFactory") - CommutativePrimitiveOperations = Set(defn.Boolean_&&, defn.Boolean_||, defn.Int_+, defn.Int_*, defn.Long_+, defn.Long_*) - - val maxFuel = ctx.settings.YoptFuel.value - if (fuel < 0 && maxFuel > 0) // Both defaults are at -1 - fuel = maxFuel - - optimisations = { - val o = if (ctx.erasedTypes) afterErasure else beforeErasure - val p = ctx.settings.YoptPhases.value - if (p.isEmpty) o else o.filter(x => p.contains(x.name)) - } - } - - ctx - } - - // The entry point of local optimisation: DefDefs - override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = { - val ctx0 = ctx - if (ctx.settings.optimise.value && !tree.symbol.is(Label)) { - implicit val ctx: Context = ctx0.withOwner(tree.symbol(ctx0)) - var rhs0 = tree.rhs - var rhs1: Tree = null - while (rhs1 ne rhs0) { - rhs1 = rhs0 - optimisations.foreach { optimisation => - // Visit - rhs0.foreachSubTree(optimisation.visitor) - - // Transform - rhs0 = new TreeMap() { - override def transform(tree: Tree)(implicit ctx: Context): Tree = { - val innerCtx = if (tree.isDef && tree.symbol.exists) ctx.withOwner(tree.symbol) else ctx - val childOptimizedTree = super.transform(tree)(innerCtx) - printIfDifferent(childOptimizedTree, optimisation.transformer(ctx)(childOptimizedTree), optimisation) - } - }.transform(rhs0) - - // Clean - optimisation.clear() - } - } - if (rhs0 ne tree.rhs) tpd.cpy.DefDef(tree)(rhs = rhs0) - else tree - } else tree - } - - private def printIfDifferent(tree1: Tree, tree2: => Tree, opt: Optimisation)(implicit ctx: Context): Tree = { - if (fuel == -1) - tree2 // Does nothing when fuel is disabled. - else if (fuel == 0) - tree1 // No more fuel? No more transformations for you! - else { // Print the trees if different and consume fuel accordingly. - val t2 = tree2 - if (tree1 ne t2) { - if (fuel > 0) - fuel -= 1 - if (fuel != -1 && fuel < 5) { - println(s"${tree1.symbol} was simplified by ${opt.name} (fuel=$fuel): ${tree1.show}") - println(s"became after ${opt.name}: (fuel=$fuel) ${t2.show}") - } - } - t2 - } - } -} - -object Simplify { - import tpd._ - - /** Is this tree mutable, or java.lang.System.{in, out, err}? These three - * System members are the only static final fields that are mutable. - * See https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5.4 - */ - def isEffectivelyMutable(t: Tree)(implicit ctx: Context): Boolean = t match { - case _ if t.symbol.is(Mutable) => true - case _: Select | _: Ident => - t.symbol.owner == defn.SystemModule - case _ => false - } - - def isImmutableAccessor(t: Tree)(implicit ctx: Context): Boolean = { - val sym = t.symbol - val isImmutableGetter = sym.isGetter && !sym.is(Mutable | Lazy) - val isCaseAccessor = sym.is(CaseAccessor) && !sym.is(Mutable | Lazy) - val isProductAccessor = sym.exists && - sym.owner.derivesFrom(defn.ProductClass) && - sym.owner.is(CaseClass) && - sym.name.isSelectorName && - !sym.info.decls.exists(_.is(Mutable | Lazy)) // Conservatively covers case class A(var x: Int) - isImmutableGetter || isCaseAccessor || isProductAccessor - } -} diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala b/compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala deleted file mode 100644 index a3b18f72169e..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Valify.scala +++ /dev/null @@ -1,93 +0,0 @@ -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. - * - * @author DarkDimius, OlivierBlanvillain - */ -class Valify(val simplifyPhase: Simplify) extends Optimisation { - import ast.tpd._ - - // Either a duplicate or a read through series of immutable fields. - val defined: MutableSymbolMap[ValDef] = newMutableSymbolMap - - val firstRead: MutableSymbolMap[RefTree] = newMutableSymbolMap - - val firstWrite: MutableSymbolMap[Assign] = newMutableSymbolMap - - val secondWrite: MutableSymbolMap[Assign] = newMutableSymbolMap - - def clear(): Unit = { - defined.clear() - firstRead.clear() - firstWrite.clear() - secondWrite.clear() - } - - 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 - - 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(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 - } - - 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 deleted file mode 100644 index a97e117ae9fb..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/localopt/Varify.scala +++ /dev/null @@ -1,85 +0,0 @@ -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 = - * // code that may use l - * var r = l - * // code not using l - * } - * - * becomes: - * - * { - * var r = - * // code that may use l - * // code not using l - * } - */ - class Varify extends Optimisation { - import ast.tpd._ - - val paramsTimesUsed = newMutableSymbolMap[Int] - - val possibleRenames = newMutableSymbolMap[Set[Symbol]] - - def clear(): Unit = { - paramsTimesUsed.clear() - possibleRenames.clear() - } - - def visitor(implicit ctx: Context): Tree => Unit = { - case t: ValDef if t.symbol.is(Param) => - paramsTimesUsed.update(t.symbol, 0) - - case t: ValDef if t.symbol.is(Mutable) => - t.rhs.foreachSubTree { subtree => - if (paramsTimesUsed.contains(subtree.symbol) && - t.symbol.info.widenDealias <:< subtree.symbol.info.widenDealias) { - val newSet = possibleRenames.getOrElse(t.symbol, Set.empty) + subtree.symbol - possibleRenames.update(t.symbol, newSet) - } - } - - case t: RefTree if paramsTimesUsed.contains(t.symbol) => - val param = t.symbol - val current = paramsTimesUsed.get(param) - current foreach { c => paramsTimesUsed.update(param, c + 1) } - - case _ => - } - - 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))) - .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 - } -} diff --git a/compiler/test/bisect.sh b/compiler/test/bisect.sh deleted file mode 100644 index 309a9717eaf7..000000000000 --- a/compiler/test/bisect.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/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 < - def instructions(clazz: String): List[Instruction] = { - val clsIn = dir.lookupName(s"$clazz.class", directory = false).input - val clsNode = loadClassNode(clsIn) - instructionsFromMethod(getMethod(clsNode, "main")) - } - val A = instructions("A") - val B = instructions("B") - val diff = diffInstructions(A, B) - if (checkEqual) - assert(A == B, s"Bytecode doesn't match: (lhs = source, rhs = expected) \n$diff") - else - assert(A != B, s"Same Bytecodes without -optimise: you are testing the wrong thing!") - } - } - - @Test def inlineVals = - check("println(1)", - """ - |val one = 1 - |val anotherone = one - |println(anotherone) - """) - - @Test def inlineCaseIntrinsicsDottyApply = - check( - source = "CC.apply(1, 2)", - expected = "new CC(1, 2)", - shared = "case class CC(i: Int, j: Int)") - - @Test def inlineCaseIntrinsicsScalacApply = - check("::.apply(1, Nil)", "new ::(1, Nil)") - - @Test def inlineCaseIntrinsicsScalacUnapply = - check( - """ - |val t = Tuple2(1, "s") - |print(Tuple2.unapply(t)) - """, - """ - |print(new Some(new Tuple2(1, "s"))) - """) - - @Test def dropNoEffects = - check( - """ - |val a = "wow" - |print(1) - """, - """ - |print(1) - """) - - @Test def dropNoEffectsTuple = - check("new Tuple2(1, 3)", "") - - @Test def inlineLocalObjects = - check( - """ - |val t = new Tuple2(1, 3) - |print(t._1 + t._2) - """, - """ - |val i = 3 - |print(1 + i) // Prevents typer from constant folding 1 + 3 to 4 - """) - - @Test def inlineOptions = - check( - """ - |val sum = Some("s") - |println(sum.isDefined) - """, - """ - |println(true) - """) - - - /* - * Constant folding tests - */ - - @Test def basicConstantFold = - check( - """ - |val i = 3 - |val j = i + 4 - |print(j) - """, - """ - |print(7) - """) - - @Test def branchConstantFold = - check( - """ - |val t = true // val needed, or typer takes care of this - |if (t) print(1) - |else print(2) - """, - """ - |print(1) - """) - - @Test def arithmeticConstantFold = - check( - """ - |val i = 3 - |val j = i + 4 - |if(j - i >= (i + 1) / 2) - | print(i + 1) - """, - """ - |print(4) - """) - - @Test def twoValConstantFold = - check( - """ - |val i = 3 - |val j = 4 - |val k = i * j - |print(k - j) - """, - """ - |print(8) - """) - - @Test def localDefinitionElimination = - check( - """ - |lazy val foo = 1 - |def bar = 2 - |val baz = 3 - """, - """ - """) - - @Test def localDefinitionNoElimination = - check( - """ - |val j = 0 // dummy - |class Foo { - | lazy val foo = 1 - | def bar = 2 - | val baz = 3 - |} - """, - """ - |class Foo { - | lazy val foo = 1 - | def bar = 2 - | val baz = 3 - |} - """) - - @Test def test4784 = - check( - """{ println(1); false } || true""", - """true""", checkEqual = false) - - // @Test def listPatmapExample = - // check( - // """ - // |val l = 1 :: 2 :: Nil - // |l match { - // | case Nil => print("nil") - // | case x :: xs => print(x) - // |} - // """, - // """TODO - // """) - - // @Test def fooCCExample = - // check( - // source = - // """ - // |val x: Any = new Object {} - // |val (a, b) = x match { - // | case CC(s @ 1, CC(t, _)) => - // | (s , 2) - // | case _ => (42, 43) - // |} - // |a + b - // """, - // expected = - // """TODO - // """, - // shared = "case class CC(a: Int, b: Object)") - - // @Test def booleansFunctionExample = - // check( - // """ - // |val a: Any = new Object {} - // |val (b1, b2) = (a.isInstanceOf[String], a.isInstanceOf[List[Int]]) - // |(b1, b2) match { - // | case (true, true) => true - // | case (false, false) => true - // | case _ => false - // |} - // """, - // """ - // |val a: Any = new Object {} - // |val bl = a.isInstanceOf[List[_]] - // |val bl2 = a.isInstanceOf[String] - // |if (true == bl2 && true == bl) - // | true - // |else - // | false == bl2 && false == bl - // """) -} diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 486d968a24bb..3d389597a226 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -48,13 +48,11 @@ object TestConfiguration { val yCheckOptions = Array("-Ycheck:all") val basicDefaultOptions = checkOptions ++ noCheckOptions ++ yCheckOptions - val defaultUnoptimised = TestFlags(classPath, runClassPath, basicDefaultOptions) - val defaultOptimised = defaultUnoptimised and "-optimise" - val defaultOptions = defaultUnoptimised + val defaultOptions = TestFlags(classPath, runClassPath, basicDefaultOptions) val defaultRunWithCompilerOptions = defaultOptions.withRunClasspath(Jars.dottyRunWithCompiler.mkString(":")) val allowDeepSubtypes = defaultOptions without "-Yno-deep-subtypes" val allowDoubleBindings = defaultOptions without "-Yno-double-bindings" - val picklingOptions = defaultUnoptimised and ( + val picklingOptions = defaultOptions and ( "-Xprint-types", "-Ytest-pickler", "-Yprint-pos", diff --git a/docs/docs/reference/optimisations.md b/docs/docs/reference/optimisations.md deleted file mode 100644 index 2e847989a7c4..000000000000 --- a/docs/docs/reference/optimisations.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: doc-page -title: "Local Optimisations" ---- - -The Dotty compiler implements several local optimisations to generate faster bytecode. These optimisations can be enabled by compiling with the `-optimise` flag. The current best source of documentation about what tree transformations are performed under `-optimise` is the Scaladoc classes in the [localopt package](https://github.com/lampepfl/dotty/tree/master/compiler/src/dotty/tools/dotc/transform/localopt). - -Local optimisations assumes no use of Java Reflection to mutate final fields. diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 1b46769168c9..8b448fb7ac01 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -57,8 +57,6 @@ sidebar: url: docs/reference/erased-terms.html - title: Kind Polymorphism url: docs/reference/kind-polymorphism.html - - title: Local Optimisations - url: docs/reference/optimisations.html - title: Changed Features subsection: - title: Volatile Lazy Vals diff --git a/project/Build.scala b/project/Build.scala index 1273e5ddc7ce..779020d0b607 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -293,18 +293,6 @@ object Build { } ) - - // Bootstrap with -optimise - lazy val commonOptimisedSettings = commonBootstrappedSettings ++ Seq( - scalacOptions ++= Seq("-optimise"), - - // The *-bootstrapped and *-optimised projects contain the same sources, so - // we only need to import one set in the IDE. We prefer to import the - // non-optimized projects because optimize is slower to compile and we do - // not trust its output yet. - excludeFromIDE := true - ) - lazy val commonBenchmarkSettings = Seq( outputStrategy := Some(StdoutOutput), mainClass in (Jmh, run) := Some("dotty.tools.benchmarks.Bench"), // custom main for jmh:run @@ -343,7 +331,6 @@ object Build { // this is only necessary for compatibility with sbt which currently hardcodes the "dotty" artifact name lazy val dotty = project.in(file(".")).asDottyRoot(NonBootstrapped) lazy val `dotty-bootstrapped` = project.asDottyRoot(Bootstrapped) - lazy val `dotty-optimised` = project.asDottyRoot(BootstrappedOptimised) lazy val `dotty-interfaces` = project.in(file("interfaces")). disablePlugins(ScriptedPlugin). @@ -425,12 +412,10 @@ object Build { lazy val `dotty-doc` = project.in(file("doc-tool")).asDottyDoc(NonBootstrapped) lazy val `dotty-doc-bootstrapped` = project.in(file("doc-tool")).asDottyDoc(Bootstrapped) - lazy val `dotty-doc-optimised` = project.in(file("doc-tool")).asDottyDoc(BootstrappedOptimised) def dottyDoc(implicit mode: Mode): Project = mode match { case NonBootstrapped => `dotty-doc` case Bootstrapped => `dotty-doc-bootstrapped` - case BootstrappedOptimised => `dotty-doc-optimised` } def testOnlyFiltered(test: String, options: String) = Def.inputTaskDyn { @@ -718,12 +703,10 @@ object Build { lazy val `dotty-compiler` = project.in(file("compiler")).asDottyCompiler(NonBootstrapped) lazy val `dotty-compiler-bootstrapped` = project.in(file("compiler")).asDottyCompiler(Bootstrapped) - lazy val `dotty-compiler-optimised` = project.in(file("compiler")).asDottyCompiler(BootstrappedOptimised) def dottyCompiler(implicit mode: Mode): Project = mode match { case NonBootstrapped => `dotty-compiler` case Bootstrapped => `dotty-compiler-bootstrapped` - case BootstrappedOptimised => `dotty-compiler-optimised` } // Settings shared between dotty-library and dotty-library-bootstrapped @@ -733,12 +716,10 @@ object Build { lazy val `dotty-library` = project.in(file("library")).asDottyLibrary(NonBootstrapped) lazy val `dotty-library-bootstrapped`: Project = project.in(file("library")).asDottyLibrary(Bootstrapped) - lazy val `dotty-library-optimised`: Project = project.in(file("library")).asDottyLibrary(BootstrappedOptimised) def dottyLibrary(implicit mode: Mode): Project = mode match { case NonBootstrapped => `dotty-library` case Bootstrapped => `dotty-library-bootstrapped` - case BootstrappedOptimised => `dotty-library-optimised` } // until sbt/sbt#2402 is fixed (https://github.com/sbt/sbt/issues/2402) @@ -840,7 +821,6 @@ object Build { lazy val `dotty-bench` = project.in(file("bench")).asDottyBench(NonBootstrapped) lazy val `dotty-bench-bootstrapped` = project.in(file("bench")).asDottyBench(Bootstrapped) - lazy val `dotty-bench-optimised` = project.in(file("bench")).asDottyBench(BootstrappedOptimised) // Depend on dotty-library so that sbt projects using dotty automatically // depend on the dotty-library @@ -1149,10 +1129,6 @@ object Build { .settings( packResourceDir += ((baseDirectory in dist).value / "bin" -> "bin"), ) - lazy val `dist-optimised` = project.asDist(BootstrappedOptimised) - .settings( - packResourceDir += ((baseDirectory in dist).value / "bin" -> "bin"), - ) // /** A sandbox to play with the Scala.js back-end of dotty. // * @@ -1246,13 +1222,12 @@ object Build { dependsOn(`dotty-interfaces`, dottyCompiler, dottyLibrary, dottyDoc). settings(commonDistSettings). bootstrappedSettings( - target := baseDirectory.value / "target" // override setting in commonBootstrappedSettings + target := baseDirectory.value / "target" // override setting in commonBootstrappedSettings ) def withCommonSettings(implicit mode: Mode): Project = project.settings(mode match { case NonBootstrapped => commonNonBootstrappedSettings case Bootstrapped => commonBootstrappedSettings - case BootstrappedOptimised => commonOptimisedSettings }) } } diff --git a/project/Modes.scala b/project/Modes.scala index 4a408a70e954..86059d67299f 100644 --- a/project/Modes.scala +++ b/project/Modes.scala @@ -6,7 +6,6 @@ object Modes { object NonBootstrapped extends Mode object Bootstrapped extends Mode - object BootstrappedOptimised extends Mode implicit class ProjectModesOps(val project: Project) extends AnyVal { diff --git a/tests/neg-no-optimise/patmat1.scala b/tests/neg/patmat1.scala similarity index 100% rename from tests/neg-no-optimise/patmat1.scala rename to tests/neg/patmat1.scala diff --git a/tests/neg-no-optimise/patmat2.scala b/tests/neg/patmat2.scala similarity index 100% rename from tests/neg-no-optimise/patmat2.scala rename to tests/neg/patmat2.scala diff --git a/tests/neg-no-optimise/t7868.scala b/tests/neg/t7868.scala similarity index 100% rename from tests/neg-no-optimise/t7868.scala rename to tests/neg/t7868.scala diff --git a/tests/pos/bubbleUpNothing.scala b/tests/pos/bubbleUpNothing.scala index 1c4da164866d..7b43414e340a 100644 --- a/tests/pos/bubbleUpNothing.scala +++ b/tests/pos/bubbleUpNothing.scala @@ -43,17 +43,6 @@ object Test { { "a"; "b"; ??? } .toString.subSequence(0, 1).toString.subSequence(0, 1).toString - // run -optimise -Xprint:simplify - // def test1(): String = ???(): String - // def test2(): String = ???(): String - // def test3(): String = ???(): String - // def test4(): String = ???(): String - // def test5(): String = ???(): String - // def test6(): String = ???(): String - // def test7(): String = ???(): String - // def test8(): String = ???(): String - // def test9(): String = ???(): String - def test10: Unit = { def fail = throw new IllegalArgumentException("") } diff --git a/tests/pos-no-optimise/escapingRefs.scala b/tests/pos/escapingRefs.scala similarity index 100% rename from tests/pos-no-optimise/escapingRefs.scala rename to tests/pos/escapingRefs.scala diff --git a/tests/run-with-compiler/quote-run-with-settings.scala b/tests/run-with-compiler/quote-run-with-settings.scala index 8d2c677735ca..1efe50ec45c7 100644 --- a/tests/run-with-compiler/quote-run-with-settings.scala +++ b/tests/run-with-compiler/quote-run-with-settings.scala @@ -24,7 +24,7 @@ object Test { Files.deleteIfExists(classFile) { - implicit val settings = ToolboxSettings.make(optimise = true, outDir = Some(outDir.toString)) + implicit val settings = ToolboxSettings.make(outDir = Some(outDir.toString)) implicit val toolbox2: scala.quoted.Toolbox = dotty.tools.dotc.quoted.Toolbox.make println(expr.run) assert(Files.exists(classFile)) diff --git a/tests/run-no-optimise/2772.scala b/tests/run/2772.scala similarity index 100% rename from tests/run-no-optimise/2772.scala rename to tests/run/2772.scala diff --git a/tests/run-no-optimise/t10594.scala b/tests/run/t10594.scala similarity index 100% rename from tests/run-no-optimise/t10594.scala rename to tests/run/t10594.scala