53
64
val rdef = new reachingDefinitions.ReachingDefinitionsAnalysis;
55
66
/** Use-def chain: give the reaching definitions at the beginning of given instruction. */
56
var defs: immutable.Map[(BasicBlock, Int), immutable.Set[rdef.lattice.Definition]] = immutable.HashMap.empty
67
var defs: immutable.Map[InstrLoc, immutable.Set[rdef.lattice.Definition]] = immutable.HashMap.empty
58
69
/** Useful instructions which have not been scanned yet. */
59
val worklist: mutable.Set[(BasicBlock, Int)] = new mutable.LinkedHashSet
70
val worklist: mutable.Set[InstrLoc] = new mutable.LinkedHashSet
61
72
/** what instructions have been marked as useful? */
62
val useful: mutable.Map[BasicBlock, mutable.BitSet] = new mutable.HashMap
73
val useful: mutable.Map[BasicBlock, mutable.BitSet] = perRunCaches.newMap()
64
75
/** what local variables have been accessed at least once? */
65
76
var accessedLocals: List[Local] = Nil
78
/** Map from a local and a basic block to the instructions that store to that local in that basic block */
79
val localStores = mutable.Map[(Local, BasicBlock), mutable.BitSet]() withDefault {_ => mutable.BitSet()}
81
/** Stores that clobber previous stores to array or ref locals. See SI-5313 */
82
val clobbers = mutable.Set[InstrLoc]()
67
84
/** the current method. */
68
85
var method: IMethod = _
70
87
/** Map instructions who have a drop on some control path, to that DROP instruction. */
71
val dropOf: mutable.Map[(BasicBlock, Int), (BasicBlock, Int)] = new mutable.HashMap()
88
val dropOf: mutable.Map[InstrLoc, List[InstrLoc]] = perRunCaches.newMap()
73
90
def dieCodeDie(m: IMethod) {
75
log("dead code elimination on " + m);
92
debuglog("dead code elimination on " + m);
78
97
accessedLocals = m.params.reverse
79
98
m.code.blocks ++= linearizer.linearize(m)
83
102
accessedLocals = accessedLocals.distinct
84
if (m.locals diff accessedLocals nonEmpty) {
85
log("Removed dead locals: " + (m.locals diff accessedLocals))
103
val diff = m.locals diff accessedLocals
105
val msg = diff.map(_.sym.name)mkString(", ")
106
log(s"Removed ${diff.size} dead locals: $msg")
86
107
m.locals = accessedLocals.reverse
91
112
/** collect reaching definitions and initial useful instructions for this method. */
92
def collectRDef(m: IMethod): Unit = if (m.code ne null) {
93
defs = immutable.HashMap.empty; worklist.clear; useful.clear;
113
def collectRDef(m: IMethod): Unit = if (m.hasCode) {
114
defs = immutable.HashMap.empty; worklist.clear(); useful.clear();
97
for (bb <- m.code.blocks.toList) {
118
m foreachBlock { bb =>
98
119
useful(bb) = new mutable.BitSet(bb.size)
99
120
var rd = rdef.in(bb);
100
121
for (Pair(i, idx) <- bb.toList.zipWithIndex) {
123
// utility for adding to worklist
124
def moveToWorkList() = moveToWorkListIf(true)
126
// utility for (conditionally) adding to worklist
127
def moveToWorkListIf(cond: Boolean) =
129
debuglog("in worklist: " + i)
130
worklist += ((bb, idx))
132
debuglog("not in worklist: " + i)
135
// instruction-specific logic
102
case LOAD_LOCAL(l) =>
138
case LOAD_LOCAL(_) =>
103
139
defs = defs + Pair(((bb, idx)), rd.vars)
104
// Console.println(i + ": " + (bb, idx) + " rd: " + rd + " and having: " + defs)
140
moveToWorkListIf(false)
142
case STORE_LOCAL(l) =>
143
/* SI-4935 Check whether a module is stack top, if so mark the instruction that loaded it
144
* (otherwise any side-effects of the module's constructor go lost).
145
* (a) The other two cases where a module's value is stored (STORE_FIELD and STORE_ARRAY_ITEM)
146
* are already marked (case clause below).
147
* (b) A CALL_METHOD targeting a method `m1` where the receiver is potentially a module (case clause below)
148
* will have the module's load marked provided `isSideEffecting(m1)`.
149
* TODO check for purity (the ICode?) of the module's constructor (besides m1's purity).
150
* See also https://github.com/paulp/scala/blob/topic/purity-analysis/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala
152
val necessary = rdef.findDefs(bb, idx, 1) exists { p =>
155
case LOAD_MODULE(module) => isLoadNeeded(module)
159
moveToWorkListIf(necessary)
161
// add it to the localStores map
163
val set = localStores(key)
165
localStores(key) = set
105
167
case RETURN(_) | JUMP(_) | CJUMP(_, _, _, _) | CZJUMP(_, _, _, _) | STORE_FIELD(_, _) |
106
THROW(_) | STORE_ARRAY_ITEM(_) | SCOPE_ENTER(_) | SCOPE_EXIT(_) | STORE_THIS(_) |
107
LOAD_EXCEPTION(_) | SWITCH(_, _) | MONITOR_ENTER() | MONITOR_EXIT() => worklist += ((bb, idx))
108
case CALL_METHOD(m1, _) if isSideEffecting(m1) => worklist += ((bb, idx)); log("marking " + m1)
168
THROW(_) | LOAD_ARRAY_ITEM(_) | STORE_ARRAY_ITEM(_) | SCOPE_ENTER(_) | SCOPE_EXIT(_) | STORE_THIS(_) |
169
LOAD_EXCEPTION(_) | SWITCH(_, _) | MONITOR_ENTER() | MONITOR_EXIT() =>
172
case CALL_METHOD(m1, _) if isSideEffecting(m1) =>
109
175
case CALL_METHOD(m1, SuperCall(_)) =>
110
worklist += ((bb, idx)) // super calls to constructor
176
moveToWorkList() // super calls to constructor
112
179
val necessary = rdef.findDefs(bb, idx, 1) exists { p =>
113
180
val (bb1, idx1) = p
115
182
case CALL_METHOD(m1, _) if isSideEffecting(m1) => true
116
183
case LOAD_EXCEPTION(_) | DUP(_) | LOAD_MODULE(_) => true
118
dropOf((bb1, idx1)) = (bb, idx)
119
// println("DROP is innessential: " + i + " because of: " + bb1(idx1) + " at " + bb1 + ":" + idx1)
185
dropOf((bb1, idx1)) = (bb,idx) :: dropOf.getOrElse((bb1, idx1), Nil)
186
debuglog("DROP is innessential: " + i + " because of: " + bb1(idx1) + " at " + bb1 + ":" + idx1)
123
if (necessary) worklist += ((bb, idx))
190
moveToWorkListIf(necessary)
192
moveToWorkListIf(false)
126
194
rd = rdef.interpret(bb, idx, rd)
199
private def isLoadNeeded(module: Symbol): Boolean = {
200
module.info.member(nme.CONSTRUCTOR).filter(isSideEffecting) != NoSymbol
131
203
/** Mark useful instructions. Instructions in the worklist are each inspected and their
132
204
* dependencies are marked useful too, and added to the worklist.
135
207
// log("Starting with worklist: " + worklist)
136
208
while (!worklist.isEmpty) {
137
val (bb, idx) = worklist.iterator.next
209
val (bb, idx) = worklist.head
138
210
worklist -= ((bb, idx))
139
if (settings.debug.value)
140
log("Marking instr: \tBB_" + bb + ": " + idx + " " + bb(idx))
211
debuglog("Marking instr: \tBB_" + bb + ": " + idx + " " + bb(idx))
142
213
val instr = bb(idx)
214
// adds the instrutions that define the stack values about to be consumed to the work list to
216
def addDefs() = for ((bb1, idx1) <- rdef.findDefs(bb, idx, instr.consumed) if !useful(bb1)(idx1)) {
217
debuglog(s"\t${bb1(idx1)} is consumed by $instr")
218
worklist += ((bb1, idx1))
221
// DROP logic -- if an instruction is useful, its drops are also useful
222
// and we don't mark the DROPs as useful directly but add them to the
223
// worklist so we also mark their reaching defs as useful - see SI-7060
143
224
if (!useful(bb)(idx)) {
144
225
useful(bb) += idx
145
dropOf.get(bb, idx) match {
146
case Some((bb1, idx1)) => useful(bb1) += idx1
226
dropOf.get(bb, idx) foreach {
227
for ((bb1, idx1) <- _) {
229
* SI-7060: A drop that we now mark as useful can be reached via several paths,
230
* so we should follow by marking all its reaching definition as useful too:
232
debuglog("\tAdding: " + bb1(idx1) + " to the worklist, as a useful DROP.")
233
worklist += ((bb1, idx1))
237
// per-instruction logic
150
239
case LOAD_LOCAL(l1) =>
151
240
for ((l2, bb1, idx1) <- defs((bb, idx)) if l1 == l2; if !useful(bb1)(idx1)) {
152
log("\tAdding " + bb1(idx1))
241
debuglog("\tAdding " + bb1(idx1))
153
242
worklist += ((bb1, idx1))
245
case STORE_LOCAL(l1) if l1.kind.isRefOrArrayType =>
248
// search for clobbers of this store if we aren't doing l1 = null
249
// this doesn't catch the second store in x=null;l1=x; but in practice this catches
250
// a lot of null stores very cheaply
251
if (idx == 0 || bb(idx - 1) != CONSTANT(Constant(null)))
252
findClobbers(l1, bb, idx + 1)
156
254
case nw @ NEW(REFERENCE(sym)) =>
157
255
assert(nw.init ne null, "null new.init at: " + bb + ": " + idx + "(" + instr + ")")
158
256
worklist += findInstruction(bb, nw.init)
175
for ((bb1, idx1) <- rdef.findDefs(bb, idx, instr.consumed) if !useful(bb1)(idx1)) {
176
log("\tAdding " + bb1(idx1))
177
worklist += ((bb1, idx1))
280
* Finds and marks all clobbers of the given local starting in the given
281
* basic block at the given index
283
* Storing to local variables of reference or array type may be indirectly
284
* observable because it may remove a reference to an object which may allow the object
285
* to be gc'd. See SI-5313. In this code I call the LOCAL_STORE(s) that immediately follow a
286
* LOCAL_STORE and that store to the same local "clobbers." If a LOCAL_STORE is marked
287
* useful then its clobbers must go into the set of clobbers, which will be
288
* compensated for later
290
def findClobbers(l: Local, bb: BasicBlock, idx: Int) {
291
// previously visited blocks tracked to prevent searching forever in a cycle
292
val inspected = mutable.Set[BasicBlock]()
293
// our worklist of blocks that still need to be checked
294
val blocksToBeInspected = mutable.Set[BasicBlock]()
296
// Tries to find the next clobber of l1 in bb1 starting at idx1.
297
// if it finds one it adds the clobber to clobbers set for later
298
// handling. If not it adds the direct successor blocks to
299
// the uninspectedBlocks to try to find clobbers there. Either way
300
// it adds the exception successor blocks for further search
301
def findClobberInBlock(idx1: Int, bb1: BasicBlock) {
303
val foundClobber = (localStores contains key) && {
304
def minIdx(s : mutable.BitSet) = if(s.isEmpty) -1 else s.min
306
// find the smallest index greater than or equal to idx1
307
val clobberIdx = minIdx(localStores(key) dropWhile (_ < idx1))
308
if (clobberIdx == -1)
311
debuglog(s"\t${bb1(clobberIdx)} is a clobber of ${bb(idx)}")
312
clobbers += ((bb1, clobberIdx))
317
// always need to look into the exception successors for additional clobbers
318
// because we don't know when flow might enter an exception handler
319
blocksToBeInspected ++= (bb1.exceptionSuccessors filterNot inspected)
320
// If we didn't find a clobber here then we need to look at successor blocks.
321
// if we found a clobber then we don't need to search in the direct successors
323
blocksToBeInspected ++= (bb1.directSuccessors filterNot inspected)
327
// first search starting at the current index
328
// note we don't put bb in the inspected list yet because a loop may later force
329
// us back around to search from the beginning of bb
330
findClobberInBlock(idx, bb)
331
// then loop until we've exhausted the set of uninspected blocks
332
while(!blocksToBeInspected.isEmpty) {
333
val bb1 = blocksToBeInspected.head
334
blocksToBeInspected -= bb1
336
findClobberInBlock(0, bb1)
184
340
def sweep(m: IMethod) {
185
341
val compensations = computeCompensations(m)
187
for (bb <- m.code.blocks.toList) {
188
// Console.println("** Sweeping block " + bb + " **")
343
debuglog("Sweeping: " + m)
345
m foreachBlock { bb =>
189
347
val oldInstr = bb.toList
192
350
for (Pair(i, idx) <- oldInstr.zipWithIndex) {
193
351
if (useful(bb)(idx)) {
194
// log(" " + i + " is useful")
352
debuglog(" * " + i + " is useful")
195
353
bb.emit(i, i.pos)
196
354
compensations.get(bb, idx) match {
197
355
case Some(is) => is foreach bb.emit
210
368
case NEW(REFERENCE(sym)) =>
211
log("skipped object creation: " + sym + "inside " + m)
369
log(s"Eliminated instantation of $sym inside $m")
370
case STORE_LOCAL(l) if clobbers contains ((bb, idx)) =>
371
// if an unused instruction was a clobber of a used store to a reference or array type
372
// then we'll replace it with the store of a null to make sure the reference is
373
// eliminated. See SI-5313
374
bb emit CONSTANT(Constant(null))
375
bb emit STORE_LOCAL(l)
214
if (settings.debug.value) log("Skipped: bb_" + bb + ": " + idx + "( " + i + ")")
378
debuglog(" " + i + " [swept]")
218
382
if (bb.nonEmpty) bb.close
219
else log("empty block encountered")
383
else log(s"empty block encountered in $m")
223
private def computeCompensations(m: IMethod): mutable.Map[(BasicBlock, Int), List[Instruction]] = {
224
val compensations: mutable.Map[(BasicBlock, Int), List[Instruction]] = new mutable.HashMap
387
private def computeCompensations(m: IMethod): mutable.Map[InstrLoc, List[Instruction]] = {
388
val compensations: mutable.Map[InstrLoc, List[Instruction]] = new mutable.HashMap
226
for (bb <- m.code.blocks) {
390
m foreachBlock { bb =>
227
391
assert(bb.closed, "Open block in computeCompensations")
228
for ((i, idx) <- bb.toList.zipWithIndex) {
392
foreachWithIndex(bb.toList) { (i, idx) =>
229
393
if (!useful(bb)(idx)) {
230
for ((consumedType, depth) <- i.consumedTypes.reverse.zipWithIndex) {
231
log("Finding definitions of: " + i + "\n\t" + consumedType + " at depth: " + depth)
394
foreachWithIndex(i.consumedTypes.reverse) { (consumedType, depth) =>
395
debuglog("Finding definitions of: " + i + "\n\t" + consumedType + " at depth: " + depth)
232
396
val defs = rdef.findDefs(bb, idx, 1, depth)
233
397
for (d <- defs) {
234
398
val (bb, idx) = d
399
debuglog("rdef: "+ bb(idx))
236
401
case DUP(_) if idx > 0 =>
237
402
bb(idx - 1) match {