Skip to content

Commit

Permalink
bringing down -Yinline time by 33%
Browse files Browse the repository at this point in the history
After inlining one ore more callsites, Inliner runs anew a type-flow analysis for the same method, ie. the previous solution is discarded although it's a subset of that needed after the inlining(s). Background in: http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2011Q4/Inliner.pdf

Instead, we invalidate only the fragment of the CFG affected by inlining (the basic block where inlining occurred and newly added basic blocks) and let the iterative dataflow analysis start from there. It reaches the fixpoint faster.

The inlining decisions thus made are exactly the same as with the slower version, see -Ylog:inliner, with a focus on lines starting with "[log inliner] Inlining"

review by @VladUreche @dragos @paulp
  • Loading branch information
magarciaEPFL committed Jan 16, 2012
1 parent ac11155 commit ba00a5b
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 26 deletions.
Expand Up @@ -619,6 +619,50 @@ abstract class TypeFlowAnalysis {
} }
} }


class MTFAGrowable extends MethodTFA {

import icodes._

/** discards what must be discarded, blanks what needs to be blanked out, and keeps the rest. */
def reinit(m: icodes.IMethod, staleOut: List[BasicBlock], inlined: collection.Set[BasicBlock], staleIn: collection.Set[BasicBlock]) {
if (this.method == null || this.method.symbol != m.symbol) {
init(m)
return
} else if(staleOut.isEmpty && inlined.isEmpty && staleIn.isEmpty) {
// this promotes invoking reinit if in doubt, no performance degradation will ensue!
return;
}

reinit {
// asserts conveying an idea what CFG shapes arrive here.
// staleIn foreach (p => assert( !in.isDefinedAt(p), p))
// staleIn foreach (p => assert(!out.isDefinedAt(p), p))
// inlined foreach (p => assert( !in.isDefinedAt(p), p))
// inlined foreach (p => assert(!out.isDefinedAt(p), p))
// inlined foreach (p => assert(!p.successors.isEmpty || p.lastInstruction.isInstanceOf[icodes.opcodes.THROW], p))
// staleOut foreach (p => assert( in.isDefinedAt(p), p))

// never rewrite in(m.startBlock)
staleOut foreach { b =>
if(!inlined.contains(b)) { worklist += b }
out(b) = typeFlowLattice.bottom
}
// nothing else is added to the worklist, bb's reachable via succs will be tfa'ed
blankOut(inlined)
blankOut(staleIn)
// no need to add startBlocks from m.exh
}
}

private def blankOut(blocks: collection.Set[BasicBlock]) {
blocks foreach { b =>
in(b) = typeFlowLattice.bottom
out(b) = typeFlowLattice.bottom
}
}

}

class Timer { class Timer {
var millis = 0L var millis = 0L


Expand Down
70 changes: 44 additions & 26 deletions src/compiler/scala/tools/nsc/backend/opt/Inliners.scala
Expand Up @@ -102,11 +102,21 @@ abstract class Inliners extends SubComponent {
debuglog("Analyzing " + cls) debuglog("Analyzing " + cls)


this.currentIClazz = cls this.currentIClazz = cls
cls.methods filterNot (_.symbol.isConstructor) foreach analyzeMethod val ms = cls.methods filterNot { _.symbol.isConstructor }
ms foreach { im =>
if(hasInline(im.symbol)) {
log("Not inlining into " + im.symbol.originalName.decode + " because it is marked @inline.")
} else if(im.hasCode) {
analyzeMethod(im)
}
}
} }


val tfa = new analysis.MethodTFA() val tfa = new analysis.MTFAGrowable()
tfa.stat = global.opt.printStats tfa.stat = global.opt.printStats
val staleOut = new mutable.ListBuffer[BasicBlock]
val splicedBlocks = mutable.Set.empty[BasicBlock]
val staleIn = mutable.Set.empty[BasicBlock]


// how many times have we already inlined this method here? // how many times have we already inlined this method here?
private val inlinedMethodCount = perRunCaches.newMap[Symbol, Int]() withDefaultValue 0 private val inlinedMethodCount = perRunCaches.newMap[Symbol, Int]() withDefaultValue 0
Expand Down Expand Up @@ -208,34 +218,35 @@ abstract class Inliners extends SubComponent {
import scala.util.control.Breaks._ import scala.util.control.Breaks._
do { do {
retry = false retry = false
if (caller.inline) { log("Analyzing " + m + " count " + count + " with " + caller.length + " blocks")
log("Not inlining into " + caller.sym.originalName.decode + " because it is marked @inline.") tfa.reinit(m, staleOut.toList, splicedBlocks, staleIn)
} tfa.run
else if (caller.m.hasCode) { staleOut.clear()
log("Analyzing " + m + " count " + count + " with " + caller.length + " blocks") splicedBlocks.clear()
tfa init m staleIn.clear()
tfa.run
caller.m.linearizedBlocks() foreach { bb => caller.m.linearizedBlocks() foreach { bb =>
info = tfa in bb info = tfa in bb


breakable { breakable {
for (i <- bb) { for (i <- bb) {
i match { i match {
// Dynamic == normal invocations // Dynamic == normal invocations
// Static(true) == calls to private members // Static(true) == calls to private members
case CALL_METHOD(msym, Dynamic | Static(true)) if !msym.isConstructor => case CALL_METHOD(msym, Dynamic | Static(true)) if !msym.isConstructor =>
if (analyzeInc(msym, i, bb)) if (analyzeInc(msym, i, bb)) {
break break
case _ => () }
} case _ => ()
info = tfa.interpret(info, i)
} }
info = tfa.interpret(info, i)
} }
} }


if (tfa.stat)
log(m.symbol.fullName + " iterations: " + tfa.iterations + " (size: " + caller.length + ")")
} }

if (tfa.stat)
log(m.symbol.fullName + " iterations: " + tfa.iterations + " (size: " + caller.length + ")")
} }
while (retry && count < MAX_INLINE_RETRY) while (retry && count < MAX_INLINE_RETRY)


Expand Down Expand Up @@ -343,6 +354,9 @@ abstract class Inliners extends SubComponent {
* The instruction must be a CALL_METHOD. * The instruction must be a CALL_METHOD.
*/ */
def doInline(block: BasicBlock, instr: Instruction) { def doInline(block: BasicBlock, instr: Instruction) {

staleOut += block

val targetPos = instr.pos val targetPos = instr.pos
log("Inlining " + inc.m + " in " + caller.m + " at pos: " + posToStr(targetPos)) log("Inlining " + inc.m + " in " + caller.m + " at pos: " + posToStr(targetPos))


Expand Down Expand Up @@ -478,7 +492,8 @@ abstract class Inliners extends SubComponent {
block.close block.close


// duplicate the other blocks in the callee // duplicate the other blocks in the callee
inc.m.linearizedBlocks() foreach { bb => val calleeLin = inc.m.linearizedBlocks()
calleeLin foreach { bb =>
var info = a in bb var info = a in bb
def emitInlined(i: Instruction) = inlinedBlock(bb).emit(i, targetPos) def emitInlined(i: Instruction) = inlinedBlock(bb).emit(i, targetPos)
def emitDrops(toDrop: Int) = info.stack.types drop toDrop foreach (t => emitInlined(DROP(t))) def emitDrops(toDrop: Int) = info.stack.types drop toDrop foreach (t => emitInlined(DROP(t)))
Expand All @@ -503,6 +518,9 @@ abstract class Inliners extends SubComponent {
afterBlock emit instrAfter afterBlock emit instrAfter
afterBlock.close afterBlock.close


staleIn += afterBlock
splicedBlocks ++= (calleeLin map inlinedBlock)

// add exception handlers of the callee // add exception handlers of the callee
caller addHandlers (inc.handlers map translateExh) caller addHandlers (inc.handlers map translateExh)
assert(pending.isEmpty, "Pending NEW elements: " + pending) assert(pending.isEmpty, "Pending NEW elements: " + pending)
Expand Down

0 comments on commit ba00a5b

Please sign in to comment.