summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala28
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala497
-rw-r--r--test/files/run/virtpatmat_opt_sharing.check1
-rw-r--r--test/files/run/virtpatmat_opt_sharing.flags1
-rw-r--r--test/files/run/virtpatmat_opt_sharing.scala10
5 files changed, 456 insertions, 81 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index 769ef79546..2921607c24 100644
--- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala
+++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -290,7 +290,26 @@ abstract class UnCurry extends InfoTransform
val idparam = m.paramss.head.head
val substParam = new TreeSymSubstituter(List(vparam), List(idparam))
def substTree[T <: Tree](t: T): T = substParam(resetLocalAttrs(t))
-
+ object VirtPatmatOpt {
+ object Last {
+ def unapply[T](xs: List[T]) = xs.lastOption
+ }
+ // keep this in synch by what's generated by combineCases/runOrElse
+ object MatcherBlock {
+ def unapply(matcher: Tree): Option[(ValDef, ValDef, ValDef, ValDef, List[Tree])] = matcher match { // TODO: BUG the unapplySeq version of the case below does not seem to work in virtpatmat??
+ case Block((zero: ValDef) :: (x: ValDef) :: (matchRes: ValDef) :: (keepGoing: ValDef) :: stats, _) => Some(zero, x, matchRes, keepGoing, stats)
+ case _ => None
+ }
+ }
+ // TODO: virtpatmat use case: would be nice if could abstract over the repeated pattern more easily
+ // case Block(Last(P)) =>
+ // case P =>
+ def unapply(matcher: Tree): Option[(ValDef, ValDef, ValDef, ValDef, List[Tree], Tree => Tree)] = matcher match {
+ case MatcherBlock(zero, x, matchRes, keepGoing, stats) => Some(zero, x, matchRes, keepGoing, stats, identity[Tree])
+ case Block(outerStats, MatcherBlock(zero, x, matchRes, keepGoing, stats)) => Some(zero, x, matchRes, keepGoing, stats, inner => Block(outerStats, inner))
+ case b => treeBrowser browse b; None
+ }
+ }
DefDef(m, (fun.body: @unchecked) match {
case Match(selector, cases) =>
def transformCase(cdef: CaseDef): CaseDef =
@@ -301,6 +320,7 @@ abstract class UnCurry extends InfoTransform
if (cases exists treeInfo.isDefaultCase) Literal(Constant(true))
else Match(substTree(selector.duplicate), (cases map transformCase) :+ defaultCase)
)
+ // TODO: find a better way to keep this in synch with the code generard by patmatvirtualizer
// TODO: check tgt.tpe.typeSymbol isNonBottomSubclass MatchingStrategyClass
case Apply(Apply(TypeApply(Select(tgt, nme.runOrElse), targs), args_scrut), args_pm) if opt.virtPatmat =>
object noOne extends Transformer {
@@ -317,7 +337,7 @@ abstract class UnCurry extends InfoTransform
}
substTree(Apply(Apply(TypeApply(Select(tgt.duplicate, tgt.tpe.member("isSuccess".toTermName)), targs map (_.duplicate)), args_scrut map (_.duplicate)), args_pm map (noOne.transform)))
// for no-option version of virtpatmat
- case Block(List(zero: ValDef, x: ValDef, matchRes: ValDef, keepGoing: ValDef, stats@_*), _) if opt.virtPatmat => import CODE._
+ case VirtPatmatOpt(zero, x, matchRes, keepGoing, stats, addOuter) if opt.virtPatmat => import CODE._
object dropMatchResAssign extends Transformer {
// override val treeCopy = newStrictTreeCopier // will duplicate below
override def transform(tree: Tree): Tree = tree match {
@@ -329,14 +349,14 @@ abstract class UnCurry extends InfoTransform
}
}
val statsNoMatchRes: List[Tree] = stats map (dropMatchResAssign.transform) toList
- val idaBlock = Block(
+ val idaBlock = addOuter(Block(
zero ::
x ::
/* drop matchRes def */
keepGoing ::
statsNoMatchRes,
NOT(REF(keepGoing.symbol)) // replace `if (keepGoing) throw new MatchError(...) else matchRes` by `!keepGoing`
- )
+ ))
substTree(idaBlock.duplicate) // duplicate on block as a whole to ensure valdefs are properly cloned and substed
})
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala b/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala
index 05a85e97fe..f563f8bca2 100644
--- a/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala
@@ -245,7 +245,7 @@ trait PatMatVirtualiser extends ast.TreeDSL { self: Analyzer =>
// `one(x) : T` where x is the binder before this pattern, which will be replaced by the binder for the alternative by TreeMaker.singleBinder below
// T is the widened type of the previous binder -- this ascription is necessary to infer a clean type for `or` -- the alternative combinator -- in the presence of existential types
// see pos/virtpatmat_exist1.scala
- combineExtractors(translatePattern(patBinder, alt), pmgen.one(CODE.REF(patBinder), patBinder.info.widen)) // only RHS of actual case should use caseResult, else the optimized codegen breaks
+ combineExtractors(propagateSubstitution(translatePattern(patBinder, alt)), pmgen.one(CODE.REF(patBinder), patBinder.info.widen)) // only RHS of actual case should use caseResult, else the optimized codegen breaks
}
noFurtherSubPats(AlternativesTreeMaker(patBinder, altTrees : _*))
@@ -664,51 +664,97 @@ defined class Foo */
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
trait TreeMakers {
- trait TreeMaker {
- def substitution: Substitution ={
- if (currSub eq null) currSub = initialSubstitution
- currSub
- }
+ abstract class TreeMaker {
+ def substitution: Substitution =
+ if (currSub eq null) localSubstitution
+ else currSub
- protected def initialSubstitution: Substitution
+ protected def localSubstitution: Substitution
- private[TreeMakers] def addOuterSubstitution(outerSubst: Substitution): TreeMaker = {
- currSub = outerSubst >> substitution
+ private[TreeMakers] def incorporateOuterSubstitution(outerSubst: Substitution): TreeMaker = {
+ if (currSub ne null) {
+ println("BUG: incorporateOuterSubstitution called more than once for "+ (this, currSub, outerSubst))
+ Thread.dumpStack()
+ }
+ else currSub = outerSubst >> substitution
this
}
private[this] var currSub: Substitution = null
def chainBefore(next: Tree): Tree
+ def treesToHoist: List[Tree] = Nil
}
- case class SubstOnlyTreeMaker(initialSubstitution: Substitution) extends TreeMaker {
+ case class SubstOnlyTreeMaker(localSubstitution: Substitution) extends TreeMaker {
def chainBefore(next: Tree): Tree = substitution(next)
}
- trait FunTreeMaker extends TreeMaker {
+ abstract class FunTreeMaker extends TreeMaker {
val nextBinder: Symbol
// wrap a Fun (with binder nextBinder) around the next tree (unless nextBinder == NoSymbol) and perform our substitution
- protected def wrapFunSubst(next: Tree): Tree = pmgen.fun(nextBinder, substitution(next))
+ protected def wrapFunSubst(next: Tree): Tree
+ = pmgen.fun(nextBinder, substitution(next))
+
+ var reused: Boolean = false
+ def reusedBinders: List[Symbol] = Nil
+ override def treesToHoist: List[Tree] = { import CODE._
+ reusedBinders map { b => VAL(b) === pmgen.mkZero(b.info) }
+ }
}
- trait FreshFunTreeMaker extends FunTreeMaker {
+ abstract class FreshFunTreeMaker extends FunTreeMaker {
val pos: Position
+ val prevBinder: Symbol
val nextBinderTp: Type
lazy val nextBinder = freshSym(pos, nextBinderTp)
+ lazy val localSubstitution = Substitution(List(prevBinder), List(CODE.REF(nextBinder)))
}
- trait SingleExtractorTreeMaker extends FunTreeMaker {
+ abstract class SingleExtractorTreeMaker extends FunTreeMaker {
val extractor: Tree
// build Tree that chains `next` after the current extractor
- def chainBefore(next: Tree): Tree = pmgen.flatMap(extractor, wrapFunSubst(next)) setPos extractor.pos
+ def chainBefore(next: Tree): Tree =
+ pmgen.flatMap(extractor, wrapFunSubst(next)) setPos extractor.pos
+
}
- trait SingleBinderTreeMaker extends FunTreeMaker {
- val prevBinder: Symbol
- lazy val initialSubstitution = Substitution(List(prevBinder), List(CODE.REF(nextBinder)))
+ // TODO: in the process of shifting optimized code gen into the treemakers: complete and make it conditional in the same way as is happening in pmgen
+ abstract class CondTreeMaker extends FreshFunTreeMaker { import CODE._
+ val cond: Tree
+ val res: Tree
+
+ // must set reused before!
+ override lazy val reusedBinders = if(reused) List(freshSym(pos, BooleanClass.tpe, "rc") setFlag MUTABLE, nextBinder setFlag MUTABLE) else Nil
+ def storedCond = reusedBinders(0)
+ def storedRes = reusedBinders(1)
+
+ def chainBefore(next: Tree): Tree =
+ if (!reused)
+ atPos(pos)(pmgen.flatMapCond(cond, res, nextBinder, nextBinderTp, substitution(next)))
+ else {
+ IF (cond) THEN BLOCK(
+ storedCond === TRUE,
+ storedRes === res,
+ substitution(next).duplicate // TODO: finer-grained dup'ing
+ ) ELSE pmgen.zero
+ }
}
- abstract class SimpleTreeMaker extends SingleExtractorTreeMaker with SingleBinderTreeMaker with FreshFunTreeMaker
+ case class ReusingCondTreeMaker(dropped_priors: List[(TreeMaker, Option[TreeMaker])]) extends TreeMaker { import CODE._
+ lazy val localSubstitution = {
+ val (from, to) = dropped_priors.collect {case (dropped: CondTreeMaker, Some(prior: CondTreeMaker)) => (dropped.nextBinder, REF(prior.storedRes))}.unzip
+ val oldSubs = dropped_priors.collect {case (dropped: TreeMaker, _) => dropped.substitution}
+ oldSubs.foldLeft(Substitution(from, to))(_ >> _)
+ }
+
+ def chainBefore(next: Tree): Tree = {
+ val cond = REF(dropped_priors.reverse.collectFirst{case (_, Some(ctm: CondTreeMaker)) => ctm}.get.storedCond)
+
+ IF (cond) THEN BLOCK(
+ substitution(next).duplicate // TODO: finer-grained duplication -- MUST duplicate though, or we'll get VerifyErrors since sharing trees confuses lambdalift, and its confusion it emits illegal casts (diagnosed by Grzegorz: checkcast T ; invokevirtual S.m, where T not a subtype of S)
+ ) ELSE pmgen.zero
+ }
+ }
/**
* Make a TreeMaker that will result in an extractor call specified by `extractor`
@@ -717,9 +763,12 @@ defined class Foo */
* the function's body is determined by the next TreeMaker
* in this function's body, and all the subsequent ones, references to the symbols in `from` will be replaced by the corresponding tree in `to`
*/
- case class ExtractorTreeMaker(extractor: Tree, nextBinder: Symbol, initialSubstitution: Substitution) extends SingleExtractorTreeMaker
+ case class ExtractorTreeMaker(extractor: Tree, nextBinder: Symbol, localSubstitution: Substitution) extends SingleExtractorTreeMaker {
+ override def toString = "X"+(extractor, nextBinder)
+ }
- case class ProductExtractorTreeMaker(prevBinder: Symbol, extraCond: Option[Tree], initialSubstitution: Substitution) extends TreeMaker { import CODE._
+ // TODO: allow user-defined unapplyProduct
+ case class ProductExtractorTreeMaker(prevBinder: Symbol, extraCond: Option[Tree], localSubstitution: Substitution) extends TreeMaker { import CODE._
def chainBefore(next: Tree): Tree = {
val nullCheck = REF(prevBinder) OBJ_NE NULL
val cond = extraCond match {
@@ -728,34 +777,44 @@ defined class Foo */
}
pmgen.condOptimized(cond, substitution(next))
}
+
+ override def toString = "P"+(prevBinder, extraCond getOrElse "", localSubstitution)
}
- case class FilteredExtractorTreeMaker(extractor: Tree, guard: Tree, nextBinder: Symbol, initialSubstitution: Substitution) extends FunTreeMaker {
+ case class FilteredExtractorTreeMaker(extractor: Tree, guard: Tree, nextBinder: Symbol, localSubstitution: Substitution) extends FunTreeMaker {
def chainBefore(next: Tree): Tree =
pmgen.flatMap(extractor, wrapFunSubst(pmgen.condOptimized(guard, next))) setPos extractor.pos
+ override def toString = "FX"+(extractor, guard, nextBinder)
}
// need to substitute since binder may be used outside of the next extractor call (say, in the body of the case)
- case class TypeTestTreeMaker(prevBinder: Symbol, nextBinderTp: Type, pos: Position) extends SimpleTreeMaker {
- val extractor = pmgen.condCast(typeTest(prevBinder, nextBinderTp), prevBinder, nextBinderTp)
+ case class TypeTestTreeMaker(prevBinder: Symbol, nextBinderTp: Type, pos: Position) extends CondTreeMaker {
+ val cond = typeTest(prevBinder, nextBinderTp)
+ val res = pmgen._asInstanceOf(prevBinder, nextBinderTp)
+ override def toString = "TT"+(prevBinder, nextBinderTp)
}
// implements the run-time aspects of (ยง8.2) (typedPattern has already done the necessary type transformations)
- case class TypeAndEqualityTestTreeMaker(prevBinder: Symbol, patBinder: Symbol, pt: Type, pos: Position) extends SimpleTreeMaker {
+ case class TypeAndEqualityTestTreeMaker(prevBinder: Symbol, patBinder: Symbol, pt: Type, pos: Position) extends CondTreeMaker {
val nextBinderTp = glb(List(patBinder.info.widen, pt))
- val extractor = pmgen.condCast(typeAndEqualityTest(patBinder, pt), patBinder, nextBinderTp)
+
+ val cond = typeAndEqualityTest(patBinder, pt)
+ val res = pmgen._asInstanceOf(patBinder, nextBinderTp)
+ override def toString = "TET"+(patBinder, pt)
}
// need to substitute to deal with existential types -- TODO: deal with existentials better, don't substitute (see RichClass during quick.comp)
- case class EqualityTestTreeMaker(prevBinder: Symbol, patTree: Tree, pos: Position) extends SimpleTreeMaker {
- val nextBinderTp: Type = prevBinder.info.widen
+ case class EqualityTestTreeMaker(prevBinder: Symbol, patTree: Tree, pos: Position) extends CondTreeMaker {
+ val nextBinderTp = prevBinder.info.widen
// NOTE: generate `patTree == patBinder`, since the extractor must be in control of the equals method (also, patBinder may be null)
// equals need not be well-behaved, so don't intersect with pattern's (stabilized) type (unlike MaybeBoundTyped's accumType, where it's required)
- val extractor = atPos(pos)(pmgen.cond(pmgen._equals(patTree, prevBinder), CODE.REF(prevBinder), nextBinderTp))
+ val cond = pmgen._equals(patTree, prevBinder)
+ val res = CODE.REF(prevBinder)
+ override def toString = "ET"+(prevBinder, patTree)
}
- case class AlternativesTreeMaker(prevBinder: Symbol, alts: Tree*) extends SingleBinderTreeMaker with FreshFunTreeMaker {
+ case class AlternativesTreeMaker(prevBinder: Symbol, alts: Tree*) extends FreshFunTreeMaker {
val nextBinderTp: Type = prevBinder.info.widen
val pos = alts.head.pos
def chainBefore(next: Tree): Tree =
@@ -763,67 +822,296 @@ defined class Foo */
}
case class GuardTreeMaker(guardTree: Tree) extends /*SingleExtractor*/TreeMaker {
- val initialSubstitution: Substitution = EmptySubstitution
+ val localSubstitution: Substitution = EmptySubstitution
// val nextBinder = freshSym(guardTree.pos, UnitClass.tpe)
// val extractor = pmgen.guard(guardTree)
def chainBefore(next: Tree): Tree = { import CODE._
IF (guardTree) THEN next ELSE pmgen.zero
}
-
override def toString = "G("+ guardTree +")"
}
- // combineExtractors changes the current substitution's of the tree makers in `treeMakers`
- def combineExtractors(treeMakers: List[TreeMaker], body: Tree): Tree = {
- // a foldLeft to accumulate the initialSubstitution left-to-right, but written using a map and a var for clarity
- def propagateSubstitution(treeMakers: List[TreeMaker]): List[TreeMaker] = {
- var accumSubst: Substitution = EmptySubstitution
- treeMakers foreach { maker =>
- // could mutate maker instead, but it doesn't seem to shave much time off of quick.comp
- maker addOuterSubstitution accumSubst
- accumSubst = maker.substitution
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// decisions, decisions
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ object Test {
+ var currId = 0
+ }
+ case class Test(cond: Cond, treeMaker: TreeMaker) {
+ // def <:<(other: Test) = cond <:< other.cond
+ // def andThen_: (prev: List[Test]): List[Test] =
+ // prev.filterNot(this <:< _) :+ this
+
+ private val reusedBy = new collection.mutable.HashSet[Test]
+ var reuses: Option[Test] = None
+ def registerReuseBy(later: Test): Unit = {
+ assert(later.reuses.isEmpty)
+ reusedBy += later
+ later.reuses = Some(this)
+ }
+
+ val id = { Test.currId += 1; Test.currId}
+ override def toString =
+ if (cond eq Top) "T"
+ else if(cond eq Havoc) "!?"
+ else "T"+ id + (if(reusedBy nonEmpty) "!["+ treeMaker +"]" else (if(reuses.isEmpty) "["+ treeMaker +"]" else " cf. T"+reuses.get.id))
+ }
+
+ object Cond {
+ // def refines(self: Cond, other: Cond): Boolean = (self, other) match {
+ // case (Bottom, _) => true
+ // case (Havoc , _) => true
+ // case (_ , Top) => true
+ // case (_ , _) => false
+ // }
+ var currId = 0
+ }
+
+ abstract class Cond {
+ // def testedPath: Tree
+ // def <:<(other: Cond) = Cond.refines(this, other)
+
+ val id = { Cond.currId += 1; Cond.currId}
+ }
+
+ // does not contribute any knowledge
+ case object Top extends Cond
+
+ // takes away knowledge. e.g., a user-defined guard
+ case object Havoc extends Cond
+
+ // we know everything! everything!
+ // this either means the case is unreachable,
+ // or that it is statically known to be picked -- at this point in the decision tree --> no point in emitting further alternatives
+ // case object Bottom extends Cond
+
+
+ object EqualityCond {
+ private val uniques = new collection.mutable.HashMap[(Tree, Tree), EqualityCond]
+ def apply(testedPath: Tree, rhs: Tree): EqualityCond = uniques getOrElseUpdate((testedPath, rhs), new EqualityCond(testedPath, rhs))
+ }
+ class EqualityCond(testedPath: Tree, rhs: Tree) extends Cond {
+ // def negation = TopCond // inequality doesn't teach us anything
+ // do simplification when we know enough about the tree statically:
+ // - collapse equal trees
+ // - accumulate tests when (in)equality not known statically
+ // - become bottom when we statically know this can never match
+
+ override def toString = testedPath +" == "+ rhs +"#"+ id
+ }
+
+ object TypeCond {
+ private val uniques = new collection.mutable.HashMap[(Tree, Type), TypeCond]
+ def apply(testedPath: Tree, pt: Type): TypeCond = uniques getOrElseUpdate((testedPath, pt), new TypeCond(testedPath, pt))
+ }
+ class TypeCond(testedPath: Tree, pt: Type) extends Cond {
+ // def negation = TopCond // inequality doesn't teach us anything
+ // do simplification when we know enough about the tree statically:
+ // - collapse equal trees
+ // - accumulate tests when (in)equality not known statically
+ // - become bottom when we statically know this can never match
+ override def toString = testedPath +" <: "+ pt +"#"+ id
+ }
+
+ object TypeAndEqualityCond {
+ private val uniques = new collection.mutable.HashMap[(Tree, Type), TypeAndEqualityCond]
+ def apply(testedPath: Tree, pt: Type): TypeAndEqualityCond = uniques getOrElseUpdate((testedPath, pt), new TypeAndEqualityCond(testedPath, pt))
+ }
+ class TypeAndEqualityCond(testedPath: Tree, pt: Type) extends Cond {
+ // def negation = TopCond // inequality doesn't teach us anything
+ // do simplification when we know enough about the tree statically:
+ // - collapse equal trees
+ // - accumulate tests when (in)equality not known statically
+ // - become bottom when we statically know this can never match
+ override def toString = testedPath +" (<: && ==) "+ pt +"#"+ id
+ }
+
+ /** a flow-sensitive, generalised, common sub-expression elimination
+ * reuse knowledge from performed tests
+ * the only sub-expressions we consider are the conditions and results of the three tests (type, type&equality, equality)
+ * when a sub-expression is share, it is stored in a mutable variable
+ * the variable is floated up so that its scope includes all of the program that shares it
+ * we generalize sharing to implication, where b reuses a if a => b and priors(a) => priors(b) (the priors of a sub expression form the path through the decision tree)
+ *
+ * intended to be generalised to exhaustivity/reachability checking
+ */
+ def doCSE(prevBinder: Symbol, cases: List[(List[TreeMaker], Tree)], pt: Type): List[(List[TreeMaker], Tree)] = {
+ // a variable in this set should never be replaced by a tree that "does not consist of a selection on a variable in this set" (intuitively)
+ val pointsToBound = collection.mutable.HashSet(prevBinder)
+
+ // the substitution that renames variables to variables in pointsToBound
+ var normalize: Substitution = EmptySubstitution
+
+ // replaces a variable (in pointsToBound) by a selection on another variable in pointsToBound
+ // TODO check:
+ // pointsToBound -- accumSubst.from == Set(prevBinder) && (accumSubst.from.toSet -- pointsToBound) isEmpty
+ var accumSubst: Substitution = EmptySubstitution
+
+ val trees = new collection.mutable.HashSet[Tree]
+
+ def approximateTreeMaker(tm: TreeMaker): Test = {
+ val subst = tm.substitution
+
+ // find part of substitution that replaces bound symbols by new symbols, and reverse that part
+ // so that we don't introduce new aliases for existing symbols, thus keeping the set of bound symbols minimal
+ val (boundSubst, unboundSubst) = (subst.from zip subst.to) partition {case (f, t) =>
+ t.isInstanceOf[Ident] && (t.symbol ne NoSymbol) && pointsToBound(f)
+ }
+ val (boundFrom, boundTo) = boundSubst.unzip
+ normalize >>= Substitution(boundTo map (_.symbol), boundFrom map (CODE.REF(_)))
+ // println("normalize: "+ normalize)
+
+ val (unboundFrom, unboundTo) = unboundSubst unzip
+ val okSubst = Substitution(unboundFrom, unboundTo map (normalize(_))) // it's important substitution does not duplicate trees here -- it helps to keep hash consing simple, anyway
+ pointsToBound ++= ((okSubst.from, okSubst.to).zipped filter { (f, t) => pointsToBound exists (sym => t.exists(_.symbol == sym)) })._1
+ // println("pointsToBound: "+ pointsToBound)
+
+ accumSubst >>= okSubst
+ // println("accumSubst: "+ accumSubst)
+
+ // TODO: improve, e.g., for constants
+ def sameValue(a: Tree, b: Tree): Boolean = (a eq b) || ((a, b) match {
+ case (_ : Ident, _ : Ident) => a.symbol eq b.symbol
+ case _ => false
+ })
+
+ // hashconsing trees (modulo value-equality)
+ def unique(t: Tree): Tree =
+ trees find (a => a.equalsStructure0(t)(sameValue)) match {
+ case Some(orig) => orig // println("unique: "+ (t eq orig, orig));
+ case _ => trees += t; t
+ }
+
+ def uniqueTp(tp: Type): Type = tp match {
+ // typerefs etc are already hashconsed
+ case _ : UniqueType => tp
+ case tp@RefinedType(parents, EmptyScope) => tp.memo(tp: Type)(identity) // TODO: does this help?
+ case _ => tp
}
- treeMakers
+
+ def binderToUniqueTree(b: Symbol) = unique(accumSubst(normalize(CODE.REF(b))))
+
+ Test(tm match {
+ case ProductExtractorTreeMaker(pb, None, subst) => Top // TODO: NotNullTest(prevBinder)
+ case tm@TypeTestTreeMaker(prevBinder, nextBinderTp, _) => TypeCond(binderToUniqueTree(prevBinder), uniqueTp(nextBinderTp))
+ case tm@TypeAndEqualityTestTreeMaker(_, patBinder, pt, _) => TypeAndEqualityCond(binderToUniqueTree(patBinder), uniqueTp(pt))
+ case tm@EqualityTestTreeMaker(prevBinder, patTree, _) => EqualityCond(binderToUniqueTree(prevBinder), unique(patTree))
+ case ExtractorTreeMaker(_, _, _)
+ | GuardTreeMaker(_)
+ | ProductExtractorTreeMaker(_, Some(_), _) => Havoc
+ case FilteredExtractorTreeMaker(x, g, nb, subst) => Havoc
+ case AlternativesTreeMaker(_, _*) => Havoc // TODO: can do better here
+ case SubstOnlyTreeMaker(_) => Top
+ }, tm)
}
- propagateSubstitution(treeMakers).foldRight (body) (_ chainBefore _)
- // this optimization doesn't give us much
- // var accumSubst: Substitution = EmptySubstitution
- // var revMakers: List[TreeMaker] = Nil
- // treeMakers foreach { maker =>
- // accumSubst = accumSubst >> maker.substitution
- // maker.substitution = accumSubst
- // revMakers ::= maker
- // }
- //
- // var accumTree = body
- // revMakers foreach { maker =>
- // accumTree = maker chainBefore accumTree
- // }
- //
- // atPos(pos)(accumTree)
+ val testss = cases.map {case (treeMakers, _) => treeMakers map approximateTreeMaker }
+
+ // interpret:
+ val dependencies = new collection.mutable.LinkedHashMap[Test, Set[Cond]]
+ val tested = new collection.mutable.HashSet[Cond]
+ testss foreach { tests =>
+ tested.clear()
+ tests dropWhile { test =>
+ val cond = test.cond
+ if ((cond eq Havoc) || (cond eq Top)) (cond eq Top) // stop when we encounter a havoc, skip top
+ else {
+ tested += cond
+
+ // is there an earlier test that checks our condition and whose dependencies are implied by ours?
+ dependencies find { case (priorTest, deps) =>
+ ((priorTest.cond eq cond) || (deps contains cond)) && (deps subsetOf tested)
+ } foreach { case (priorTest, deps) =>
+ // if so, note the dependency in both tests
+ priorTest registerReuseBy test
+ }
+
+ dependencies(test) = tested.toSet // copies
+ true
+ }
+ }
+ }
+
+ // find longest prefix of tests that reuse a prior test, and whose dependent conditions monotonically increase
+ // then, collapse these contiguous sequences of reusing tests
+ // store the result of the final test and the intermediate results in hoisted mutable variables (TODO: optimize: don't store intermediate results that aren't used)
+ // replace each reference to a variable originally bound by a collapsed test by a reference to the hoisted variable
+ (testss, cases).zipped map { case (tests, (_, caseBody)) =>
+ var currDeps = Set[Cond]()
+ val (sharedPrefix, suffix) = tests span { test =>
+ (test.cond eq Top) || (for(
+ reusedTest <- test.reuses;
+ nextDeps <- dependencies.get(reusedTest);
+ diff <- (nextDeps -- currDeps).headOption;
+ _ <- Some(currDeps = nextDeps))
+ yield diff).nonEmpty
+ }
+
+ val collapsedTreeMakers = if (sharedPrefix.lengthCompare(1) > 0) { // prefix must be longer than one for the optimization to pay off
+ for (test <- sharedPrefix; reusedTest <- test.reuses; if reusedTest.treeMaker.isInstanceOf[FunTreeMaker])
+ reusedTest.treeMaker.asInstanceOf[FunTreeMaker].reused = true
+ // println("sharedPrefix: "+ sharedPrefix)
+ for (lastShared <- sharedPrefix.reverse.dropWhile(_.cond eq Top).headOption;
+ lastReused <- lastShared.reuses)
+ yield ReusingCondTreeMaker(sharedPrefix map (t => (t.treeMaker, t.reuses map (_.treeMaker)))) :: suffix.map(_.treeMaker)
+ } else None
+
+ (collapsedTreeMakers getOrElse tests.map(_.treeMaker), // sharedPrefix need not be empty (but it only contains Top-tests, which are dropped above)
+ caseBody)
+ }
}
- def combineCases(scrut: Tree, scrutSym: Symbol, cases: List[(List[TreeMaker], Tree)], pt: Type): Tree = {
+
+ // a foldLeft to accumulate the localSubstitution left-to-right, mutating the treemakers in-place for performance
+ def propagateSubstitution(treeMakers: List[TreeMaker]): List[TreeMaker] = {
+ var accumSubst: Substitution = EmptySubstitution
+ treeMakers foreach { maker =>
+ maker incorporateOuterSubstitution accumSubst
+ accumSubst = maker.substitution
+ }
+ treeMakers
+ }
+
+ // calls propagateSubstitution on the treemakers
+ def analyzeCases(prevBinder: Symbol, cases: List[(List[TreeMaker], Tree)], pt: Type): List[(List[TreeMaker], Tree)] = {
+ cases foreach { case (pats, _) => propagateSubstitution(pats) }
+ doCSE(prevBinder, cases, pt)
+ }
+
+ def combineCases(scrut: Tree, scrutSym: Symbol, cases0: List[(List[TreeMaker], Tree)], pt: Type): Tree = {
+ var toHoist = List[Tree]()
val matcher =
- if (cases nonEmpty) {
+ if (cases0 nonEmpty) {
// when specified, need to propagate pt explicitly (type inferencer can't handle it)
val optPt =
if (isFullyDefined(pt)) appliedType(matchingMonadType, List(pt))
else NoType
+ val cases = analyzeCases(scrutSym, cases0, pt)
+
// map + foldLeft
var combinedCases = combineExtractors(cases.head._1, cases.head._2)
cases.tail foreach { case (pats, body) =>
combinedCases = pmgen.typedOrElse(optPt)(combinedCases, combineExtractors(pats, body))
}
+ toHoist = (for ((treeMakers, _) <- cases; tm <- treeMakers; hoisted <- tm.treesToHoist) yield hoisted).toList
+
pmgen.fun(scrutSym, combinedCases)
} else pmgen.zero
- pmgen.runOrElse(scrut, matcher, scrutSym.info, if (isFullyDefined(pt)) pt else NoType)
+
+ val expr = pmgen.runOrElse(scrut, matcher, scrutSym.info, if (isFullyDefined(pt)) pt else NoType)
+ if (toHoist isEmpty) expr
+ else Block(toHoist, expr)
}
+ // combineExtractors changes the current substitution's of the tree makers in `treeMakers`
+ // requires propagateSubstitution(treeMakers) has been called
+ def combineExtractors(treeMakers: List[TreeMaker], body: Tree): Tree =
+ treeMakers.foldRight (body) (_ chainBefore _)
+
+
object Substitution {
def apply(from: Symbol, to: Tree) = new Substitution(List(from), List(to))
// requires sameLength(from, to)
@@ -833,11 +1121,14 @@ defined class Foo */
class Substitution(val from: List[Symbol], val to: List[Tree]) {
def apply(tree: Tree): Tree = typedSubst(tree, from, to)
+
+ // the substitution that chains `other` before `this` substitution
// forall t: Tree. this(other(t)) == (this >> other)(t)
def >>(other: Substitution): Substitution = {
val (fromFiltered, toFiltered) = (from, to).zipped filter { (f, t) => !other.from.contains(f) }
new Substitution(other.from ++ fromFiltered, other.to.map(apply) ++ toFiltered) // a quick benchmarking run indicates the `.map(apply)` is not too costly
}
+ override def toString = (from zip to) mkString("Substitution(", ", ", ")")
}
object EmptySubstitution extends Substitution(Nil, Nil) {
@@ -845,6 +1136,8 @@ defined class Foo */
override def >>(other: Substitution): Substitution = other
}
+
+
def matchingMonadType: Type
def typedSubst(tree: Tree, from: List[Symbol], to: List[Tree]): Tree
def freshSym(pos: Position, tp: Type = NoType, prefix: String = "x"): Symbol
@@ -855,6 +1148,7 @@ defined class Foo */
trait AbsCodeGen { import CODE.UNIT
def runOrElse(scrut: Tree, matcher: Tree, scrutTp: Type, resTp: Type): Tree
def flatMap(a: Tree, b: Tree): Tree
+ def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, nextBinderTp: Type, next: Tree): Tree
def fun(arg: Symbol, body: Tree): Tree
def or(f: Tree, as: List[Tree]): Tree
def typedOrElse(pt: Type)(thisCase: Tree, elseCase: Tree): Tree
@@ -868,6 +1162,8 @@ defined class Foo */
def condOptimized(c: Tree, then: Tree): Tree
def condCast(c: Tree, binder: Symbol, expectedTp: Type): Tree
def _equals(checker: Tree, binder: Symbol): Tree
+ def _asInstanceOf(b: Symbol, tp: Type): Tree
+ def mkZero(tp: Type): Tree
}
def pmgen: AbsCodeGen
@@ -967,6 +1263,22 @@ defined class Foo */
if (typesConform(b.info, tpX)) REF(b) //{ println("warning: emitted redundant asInstanceOf: "+(b, b.info, tp)); REF(b) } //.setType(tpX)
else gen.mkAsInstanceOf(REF(b), tpX, true, false)
}
+
+ // duplicated out of frustration with cast generation
+ def mkZero(tp: Type): Tree = {
+ tp.typeSymbol match {
+ case UnitClass => Literal(Constant())
+ case BooleanClass => Literal(Constant(false))
+ case FloatClass => Literal(Constant(0.0f))
+ case DoubleClass => Literal(Constant(0.0d))
+ case ByteClass => Literal(Constant(0.toByte))
+ case ShortClass => Literal(Constant(0.toShort))
+ case IntClass => Literal(Constant(0))
+ case LongClass => Literal(Constant(0L))
+ case CharClass => Literal(Constant(0.toChar))
+ case _ => gen.mkAsInstanceOf(Literal(Constant(null)), tp, any = true, wrapInApply = false) // the magic incantation is true/false here
+ }
+ }
}
trait MatchingStrategyGen { self: CommonCodeGen with MatchingStrategyGen with MonadInstGen =>
@@ -989,6 +1301,8 @@ defined class Foo */
trait MonadInstGen { self: CommonCodeGen with MatchingStrategyGen with MonadInstGen =>
// methods in the monad instance -- used directly in translation
def flatMap(a: Tree, b: Tree): Tree = (a DOT vpmName.flatMap)(b)
+ def flatMapCond(condi: Tree, res: Tree, nextBinder: Symbol, nextBinderTp: Type, next: Tree): Tree =
+ flatMap(cond(condi, res, nextBinderTp), fun(nextBinder, next))
def typedOrElse(pt: Type)(thisCase: Tree, elseCase: Tree): Tree = (genTypeApply(thisCase DOT vpmName.orElse, pt)) APPLY (elseCase)
}
@@ -1004,22 +1318,6 @@ defined class Foo */
override def zero: Tree = REF(zeroSym)
override def one(res: Tree, tp: Type = NoType, oneName: Name = vpmName.one): Tree = Apply(genTypeApply(REF(SomeModule), tp), List(res))
- // duplicated out of frustration with cast generation
- private def mkZero(tp: Type): Tree = {
- tp.typeSymbol match {
- case UnitClass => Literal(Constant())
- case BooleanClass => Literal(Constant(false))
- case FloatClass => Literal(Constant(0.0f))
- case DoubleClass => Literal(Constant(0.0d))
- case ByteClass => Literal(Constant(0.toByte))
- case ShortClass => Literal(Constant(0.toShort))
- case IntClass => Literal(Constant(0))
- case LongClass => Literal(Constant(0L))
- case CharClass => Literal(Constant(0.toChar))
- case _ => gen.mkAsInstanceOf(Literal(Constant(null)), tp, any = true, wrapInApply = false) // the magic incantation is true/false here
- }
- }
-
/** Inline runOrElse and get rid of Option allocations
*
@@ -1103,6 +1401,12 @@ defined class Foo */
case _ => println("huh?")
(opt DOT vpmName.flatMap)(fun)
}
+
+ override def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, nextBinderTp: Type, next: Tree): Tree =
+ IF (cond) THEN BLOCK(
+ VAL(nextBinder) === res,
+ next
+ ) ELSE zero
}
def genTypeApply(tfun: Tree, args: Type*): Tree = if(args contains NoType) tfun else TypeApply(tfun, args.toList map TypeTree)
@@ -1161,3 +1465,42 @@ defined class Foo */
// var okTree: Tree = null
// }
// private def c(t: Tree): Tree = noShadowedUntyped(t)
+
+ // def approximateTreeMaker(tm: TreeMaker): List[Test] = tm match {
+ // case ExtractorTreeMaker(extractor, _, _) => HavocTest
+ // case FilteredExtractorTreeMaker(extractor, lenGuard, _, _) => HavocTest
+ // case ProductExtractorTreeMaker(testedBinder, lenGuard, _) => TopTest // TODO: (testedBinder ne null) and lenGuard
+ //
+ // // cond = typeTest(prevBinder, nextBinderTp)
+ // // res = pmgen._asInstanceOf(prevBinder, nextBinderTp)
+ // case TypeTestTreeMaker(testedBinder, pt, _) =>
+ //
+ // // cond = typeAndEqualityTest(patBinder, pt)
+ // // res = pmgen._asInstanceOf(patBinder, nextBinderTp)
+ // case TypeAndEqualityTestTreeMaker(_, testedBinder, pt, _) =>
+ //
+ // // cond = pmgen._equals(patTree, prevBinder)
+ // // res = CODE.REF(prevBinder)
+ // case EqualityTestTreeMaker(testedBinder, rhs, _) =>
+ //
+ // case AlternativesTreeMaker(_, alts: *) =>
+ //
+ // case GuardTreeMaker(guardTree) =>
+ // }
+
+ // // TODO: it's not exactly sound to represent an unapply-call by its symbol... also need to consider the prefix, like the outer-test (can this be captured as the path to this test?)
+ // type ExtractorRepr = Symbol
+ //
+ // // TODO: we're undoing tree-construction that we ourselves performed earlier -- how about not-doing so we don't have to undo?
+ // private def findBinderArgOfApply(extractor: Tree, unappSym: Symbol): Symbol = {
+ // class CollectTreeTraverser[T](pf: PartialFunction[Tree => T]) extends Traverser {
+ // val hits = new ListBuffer[T]
+ // override def traverse(t: Tree) {
+ // if (pf.isDefinedAt(t)) hits += pf(t)
+ // super.traverse(t)
+ // }
+ // }
+ // val trav = new CollectTreeTraverser{ case Apply(unapp, List(arg)) if unapp.symbol eq unappSym => arg.symbol}
+ // trav.traverse(extractor)
+ // trav.hits.headOption getOrElse NoSymbol
+ // }
diff --git a/test/files/run/virtpatmat_opt_sharing.check b/test/files/run/virtpatmat_opt_sharing.check
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/test/files/run/virtpatmat_opt_sharing.check
@@ -0,0 +1 @@
+1
diff --git a/test/files/run/virtpatmat_opt_sharing.flags b/test/files/run/virtpatmat_opt_sharing.flags
new file mode 100644
index 0000000000..9769db9257
--- /dev/null
+++ b/test/files/run/virtpatmat_opt_sharing.flags
@@ -0,0 +1 @@
+ -Yvirtpatmat -Xexperimental
diff --git a/test/files/run/virtpatmat_opt_sharing.scala b/test/files/run/virtpatmat_opt_sharing.scala
new file mode 100644
index 0000000000..119e3050ea
--- /dev/null
+++ b/test/files/run/virtpatmat_opt_sharing.scala
@@ -0,0 +1,10 @@
+object Test extends App {
+ virtMatch()
+ def virtMatch() = {
+ List(1, 3, 4, 7) match {
+ case 1 :: 3 :: 4 :: 5 :: x => println("nope")
+ case 1 :: 3 :: 4 :: 6 :: x => println("nope")
+ case 1 :: 3 :: 4 :: 7 :: x => println(1)
+ }
+ }
+} \ No newline at end of file