
dotty.tools.dotc.transform.localopt.ConstantFold.scala Maven / Gradle / Ivy
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)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy