diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala | 450 |
1 files changed, 0 insertions, 450 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala b/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala deleted file mode 100644 index 8911a3a28c..0000000000 --- a/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala +++ /dev/null @@ -1,450 +0,0 @@ -/* NSC -- new scala compiler - * Copyright 2005-2013 LAMP/EPFL - * @author Iulian Dragos - */ - - -package scala.tools.nsc -package backend.opt - -import scala.collection.{ mutable, immutable } - -/** - */ -abstract class DeadCodeElimination extends SubComponent { - import global._ - import icodes._ - import icodes.opcodes._ - import definitions.RuntimePackage - - /** The block and index where an instruction is located */ - type InstrLoc = (BasicBlock, Int) - - val phaseName = "dce" - - override val enabled: Boolean = settings.Xdce - - /** Create a new phase */ - override def newPhase(p: Phase) = new DeadCodeEliminationPhase(p) - - /** Dead code elimination phase. - */ - class DeadCodeEliminationPhase(prev: Phase) extends ICodePhase(prev) { - - def name = phaseName - val dce = new DeadCode() - - override def apply(c: IClass) { - if (settings.Xdce && (dce ne null)) - dce.analyzeClass(c) - } - } - - /** closures that are instantiated at least once, after dead code elimination */ - val liveClosures = perRunCaches.newSet[Symbol]() - - /** closures that are eliminated, populated by GenASM.AsmPhase.run() - * these class symbols won't have a .class physical file, thus shouldn't be included in InnerClasses JVM attribute, - * otherwise some tools get confused or slow (SI-6546) - * */ - val elidedClosures = perRunCaches.newSet[Symbol]() - - /** Remove dead code. - */ - class DeadCode { - - def analyzeClass(cls: IClass) { - log(s"Analyzing ${cls.methods.size} methods in $cls.") - cls.methods.foreach { m => - this.method = m - dieCodeDie(m) - global.closureElimination.peephole(m) - } - } - - val rdef = new reachingDefinitions.ReachingDefinitionsAnalysis - - /** Use-def chain: give the reaching definitions at the beginning of given instruction. */ - var defs: immutable.Map[InstrLoc, immutable.Set[rdef.lattice.Definition]] = immutable.HashMap.empty - - /** Useful instructions which have not been scanned yet. */ - val worklist: mutable.Set[InstrLoc] = new mutable.LinkedHashSet - - /** what instructions have been marked as useful? */ - val useful: mutable.Map[BasicBlock, mutable.BitSet] = perRunCaches.newMap() - - /** what local variables have been accessed at least once? */ - var accessedLocals: List[Local] = Nil - - /** Map from a local and a basic block to the instructions that store to that local in that basic block */ - val localStores = mutable.Map[(Local, BasicBlock), mutable.BitSet]() withDefault {_ => mutable.BitSet()} - - /** Stores that clobber previous stores to array or ref locals. See SI-5313 */ - val clobbers = mutable.Set[InstrLoc]() - - /** the current method. */ - var method: IMethod = _ - - /** Map instructions who have a drop on some control path, to that DROP instruction. */ - val dropOf: mutable.Map[InstrLoc, List[InstrLoc]] = perRunCaches.newMap() - - def dieCodeDie(m: IMethod) { - if (m.hasCode) { - debuglog("dead code elimination on " + m) - dropOf.clear() - localStores.clear() - clobbers.clear() - m.code.blocks.clear() - m.code.touched = true - accessedLocals = m.params.reverse - m.code.blocks ++= linearizer.linearize(m) - m.code.touched = true - collectRDef(m) - mark() - sweep(m) - accessedLocals = accessedLocals.distinct - val diff = m.locals diff accessedLocals - if (diff.nonEmpty) { - val msg = diff.map(_.sym.name)mkString(", ") - log(s"Removed ${diff.size} dead locals: $msg") - m.locals = accessedLocals.reverse - } - } - } - - /** collect reaching definitions and initial useful instructions for this method. */ - def collectRDef(m: IMethod): Unit = if (m.hasCode) { - defs = immutable.HashMap.empty; worklist.clear(); useful.clear() - rdef.init(m) - rdef.run() - - m foreachBlock { bb => - useful(bb) = new mutable.BitSet(bb.size) - var rd = rdef.in(bb) - for ((i, idx) <- bb.toList.zipWithIndex) { - - // utility for adding to worklist - def moveToWorkList() = moveToWorkListIf(cond = true) - - // utility for (conditionally) adding to worklist - def moveToWorkListIf(cond: Boolean) = - if (cond) { - debuglog("in worklist: " + i) - worklist += ((bb, idx)) - } else { - debuglog("not in worklist: " + i) - } - - // instruction-specific logic - i match { - - case LOAD_LOCAL(_) => - defs = defs + (((bb, idx), rd.vars)) - moveToWorkListIf(cond = false) - - case STORE_LOCAL(l) => - /* SI-4935 Check whether a module is stack top, if so mark the instruction that loaded it - * (otherwise any side-effects of the module's constructor go lost). - * (a) The other two cases where a module's value is stored (STORE_FIELD and STORE_ARRAY_ITEM) - * are already marked (case clause below). - * (b) A CALL_METHOD targeting a method `m1` where the receiver is potentially a module (case clause below) - * will have the module's load marked provided `isSideEffecting(m1)`. - * TODO check for purity (the ICode?) of the module's constructor (besides m1's purity). - * See also https://github.com/paulp/scala/blob/topic/purity-analysis/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala - */ - val necessary = rdef.findDefs(bb, idx, 1) exists { p => - val (bb1, idx1) = p - bb1(idx1) match { - case LOAD_MODULE(module) => isLoadNeeded(module) - case _ => false - } - } - moveToWorkListIf(necessary) - - // add it to the localStores map - val key = (l, bb) - val set = localStores(key) - set += idx - localStores(key) = set - - case RETURN(_) | JUMP(_) | CJUMP(_, _, _, _) | CZJUMP(_, _, _, _) | STORE_FIELD(_, _) | - THROW(_) | LOAD_ARRAY_ITEM(_) | STORE_ARRAY_ITEM(_) | SCOPE_ENTER(_) | SCOPE_EXIT(_) | STORE_THIS(_) | - LOAD_EXCEPTION(_) | SWITCH(_, _) | MONITOR_ENTER() | MONITOR_EXIT() | CHECK_CAST(_) | CREATE_ARRAY(_, _) => - moveToWorkList() - - case LOAD_FIELD(sym, isStatic) if isStatic || !inliner.isClosureClass(sym.owner) => - // static load may trigger static initialization. - // non-static load can throw NPE (but we know closure fields can't be accessed via a - // null reference. - moveToWorkList() - case CALL_METHOD(m1, _) if isSideEffecting(m1) => - moveToWorkList() - - case CALL_METHOD(m1, SuperCall(_)) => - moveToWorkList() // super calls to constructor - - case DROP(_) => - val necessary = rdef.findDefs(bb, idx, 1) exists { p => - val (bb1, idx1) = p - bb1(idx1) match { - case CALL_METHOD(m1, _) if isSideEffecting(m1) => true - case LOAD_EXCEPTION(_) | DUP(_) | LOAD_MODULE(_) => true - case _ => - dropOf((bb1, idx1)) = (bb,idx) :: dropOf.getOrElse((bb1, idx1), Nil) - debuglog("DROP is inessential: " + i + " because of: " + bb1(idx1) + " at " + bb1 + ":" + idx1) - false - } - } - moveToWorkListIf(necessary) - case LOAD_MODULE(sym) if isLoadNeeded(sym) => - moveToWorkList() // SI-4859 Module initialization might side-effect. - case CALL_PRIMITIVE(Arithmetic(DIV | REM, INT | LONG) | ArrayLength(_)) => - moveToWorkList() // SI-8601 Might divide by zero - case _ => () - moveToWorkListIf(cond = false) - } - rd = rdef.interpret(bb, idx, rd) - } - } - } - - private def isLoadNeeded(module: Symbol): Boolean = { - module.info.member(nme.CONSTRUCTOR).filter(isSideEffecting) != NoSymbol - } - - /** Mark useful instructions. Instructions in the worklist are each inspected and their - * dependencies are marked useful too, and added to the worklist. - */ - def mark() { -// log("Starting with worklist: " + worklist) - while (!worklist.isEmpty) { - val (bb, idx) = worklist.head - worklist -= ((bb, idx)) - debuglog("Marking instr: \tBB_" + bb + ": " + idx + " " + bb(idx)) - - val instr = bb(idx) - // adds the instructions that define the stack values about to be consumed to the work list to - // be marked useful - def addDefs() = for ((bb1, idx1) <- rdef.findDefs(bb, idx, instr.consumed) if !useful(bb1)(idx1)) { - debuglog(s"\t${bb1(idx1)} is consumed by $instr") - worklist += ((bb1, idx1)) - } - - // DROP logic -- if an instruction is useful, its drops are also useful - // and we don't mark the DROPs as useful directly but add them to the - // worklist so we also mark their reaching defs as useful - see SI-7060 - if (!useful(bb)(idx)) { - useful(bb) += idx - dropOf.get((bb, idx)) foreach { - for ((bb1, idx1) <- _) { - /* - * SI-7060: A drop that we now mark as useful can be reached via several paths, - * so we should follow by marking all its reaching definition as useful too: - */ - debuglog("\tAdding: " + bb1(idx1) + " to the worklist, as a useful DROP.") - worklist += ((bb1, idx1)) - } - } - - // per-instruction logic - instr match { - case LOAD_LOCAL(l1) => - for ((l2, bb1, idx1) <- defs((bb, idx)) if l1 == l2; if !useful(bb1)(idx1)) { - debuglog("\tAdding " + bb1(idx1)) - worklist += ((bb1, idx1)) - } - - case STORE_LOCAL(l1) if l1.kind.isRefOrArrayType => - addDefs() - // see SI-5313 - // search for clobbers of this store if we aren't doing l1 = null - // this doesn't catch the second store in x=null;l1=x; but in practice this catches - // a lot of null stores very cheaply - if (idx == 0 || bb(idx - 1) != CONSTANT(Constant(null))) - findClobbers(l1, bb, idx + 1) - - case nw @ NEW(REFERENCE(sym)) => - assert(nw.init ne null, "null new.init at: " + bb + ": " + idx + "(" + instr + ")") - worklist += findInstruction(bb, nw.init) - if (inliner.isClosureClass(sym)) { - liveClosures += sym - } - - // it may be better to move static initializers from closures to - // the enclosing class, to allow the optimizer to remove more closures. - // right now, the only static fields in closures are created when caching - // 'symbol literals. - case LOAD_FIELD(sym, true) if inliner.isClosureClass(sym.owner) => - log("added closure class for field " + sym) - liveClosures += sym.owner - - case LOAD_EXCEPTION(_) => - () - - case _ => - addDefs() - } - } - } - } - - /** - * Finds and marks all clobbers of the given local starting in the given - * basic block at the given index - * - * Storing to local variables of reference or array type may be indirectly - * observable because it may remove a reference to an object which may allow the object - * to be gc'd. See SI-5313. In this code I call the LOCAL_STORE(s) that immediately follow a - * LOCAL_STORE and that store to the same local "clobbers." If a LOCAL_STORE is marked - * useful then its clobbers must go into the set of clobbers, which will be - * compensated for later - */ - def findClobbers(l: Local, bb: BasicBlock, idx: Int) { - // previously visited blocks tracked to prevent searching forever in a cycle - val inspected = mutable.Set[BasicBlock]() - // our worklist of blocks that still need to be checked - val blocksToBeInspected = mutable.Set[BasicBlock]() - - // Tries to find the next clobber of l1 in bb1 starting at idx1. - // if it finds one it adds the clobber to clobbers set for later - // handling. If not it adds the direct successor blocks to - // the uninspectedBlocks to try to find clobbers there. Either way - // it adds the exception successor blocks for further search - def findClobberInBlock(idx1: Int, bb1: BasicBlock) { - val key = ((l, bb1)) - val foundClobber = (localStores contains key) && { - def minIdx(s : mutable.BitSet) = if(s.isEmpty) -1 else s.min - - // find the smallest index greater than or equal to idx1 - val clobberIdx = minIdx(localStores(key) dropWhile (_ < idx1)) - if (clobberIdx == -1) - false - else { - debuglog(s"\t${bb1(clobberIdx)} is a clobber of ${bb(idx)}") - clobbers += ((bb1, clobberIdx)) - true - } - } - - // always need to look into the exception successors for additional clobbers - // because we don't know when flow might enter an exception handler - blocksToBeInspected ++= (bb1.exceptionSuccessors filterNot inspected) - // If we didn't find a clobber here then we need to look at successor blocks. - // if we found a clobber then we don't need to search in the direct successors - if (!foundClobber) { - blocksToBeInspected ++= (bb1.directSuccessors filterNot inspected) - } - } - - // first search starting at the current index - // note we don't put bb in the inspected list yet because a loop may later force - // us back around to search from the beginning of bb - findClobberInBlock(idx, bb) - // then loop until we've exhausted the set of uninspected blocks - while(!blocksToBeInspected.isEmpty) { - val bb1 = blocksToBeInspected.head - blocksToBeInspected -= bb1 - inspected += bb1 - findClobberInBlock(0, bb1) - } - } - - def sweep(m: IMethod) { - val compensations = computeCompensations(m) - - debuglog("Sweeping: " + m) - - m foreachBlock { bb => - debuglog(bb + ":") - val oldInstr = bb.toList - bb.open() - bb.clear() - for ((i, idx) <- oldInstr.zipWithIndex) { - if (useful(bb)(idx)) { - debuglog(" * " + i + " is useful") - bb.emit(i, i.pos) - compensations.get((bb, idx)) match { - case Some(is) => is foreach bb.emit - case None => () - } - // check for accessed locals - i match { - case LOAD_LOCAL(l) if !l.arg => - accessedLocals = l :: accessedLocals - case STORE_LOCAL(l) if !l.arg => - accessedLocals = l :: accessedLocals - case _ => () - } - } else { - i match { - case NEW(REFERENCE(sym)) => - log(s"Eliminated instantiation of $sym inside $m") - case STORE_LOCAL(l) if clobbers contains ((bb, idx)) => - // if an unused instruction was a clobber of a used store to a reference or array type - // then we'll replace it with the store of a null to make sure the reference is - // eliminated. See SI-5313 - bb emit CONSTANT(Constant(null)) - bb emit STORE_LOCAL(l) - case _ => () - } - debuglog(" " + i + " [swept]") - } - } - - if (bb.nonEmpty) bb.close() - else log(s"empty block encountered in $m") - } - } - - private def computeCompensations(m: IMethod): mutable.Map[InstrLoc, List[Instruction]] = { - val compensations: mutable.Map[InstrLoc, List[Instruction]] = new mutable.HashMap - - m foreachBlock { bb => - assert(bb.closed, "Open block in computeCompensations") - foreachWithIndex(bb.toList) { (i, idx) => - if (!useful(bb)(idx)) { - foreachWithIndex(i.consumedTypes.reverse) { (consumedType, depth) => - debuglog("Finding definitions of: " + i + "\n\t" + consumedType + " at depth: " + depth) - val defs = rdef.findDefs(bb, idx, 1, depth) - for (d <- defs) { - val (bb, idx) = d - debuglog("rdef: "+ bb(idx)) - bb(idx) match { - case DUP(_) if idx > 0 => - bb(idx - 1) match { - case nw @ NEW(_) => - val init = findInstruction(bb, nw.init) - log("Moving DROP to after <init> call: " + nw.init) - compensations(init) = List(DROP(consumedType)) - case _ => - compensations(d) = List(DROP(consumedType)) - } - case _ => - compensations(d) = List(DROP(consumedType)) - } - } - } - } - } - } - compensations - } - - private def findInstruction(bb: BasicBlock, i: Instruction): InstrLoc = { - for (b <- linearizer.linearizeAt(method, bb)) { - val idx = b.toList indexWhere (_ eq i) - if (idx != -1) - return (b, idx) - } - abort("could not find init in: " + method) - } - - private def isPure(sym: Symbol) = ( - (sym.isGetter && sym.isEffectivelyFinalOrNotOverridden && !sym.isLazy) - || (sym.isPrimaryConstructor && (sym.enclosingPackage == RuntimePackage || inliner.isClosureClass(sym.owner))) - ) - /** Is 'sym' a side-effecting method? TODO: proper analysis. */ - private def isSideEffecting(sym: Symbol) = !isPure(sym) - - } /* DeadCode */ -} |