From 63f072fe9be341f2acac5609d7657bf2b444ceb6 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Fri, 19 Aug 2011 21:23:21 +0000 Subject: [recommit] Backend optimization: Inline excepti... [recommit] Backend optimization: Inline exception handlers. Review by dragos. --- src/compiler/scala/tools/nsc/Global.scala | 12 +- .../tools/nsc/backend/icode/ICodeCheckers.scala | 10 +- .../backend/icode/analysis/TypeFlowAnalysis.scala | 4 +- .../scala/tools/nsc/backend/jvm/GenJVM.scala | 44 +- .../nsc/backend/opt/DeadCodeElimination.scala | 10 +- .../nsc/backend/opt/InlineExceptionHandlers.scala | 479 +++++++++++++++++++++ .../scala/tools/nsc/settings/ScalaSettings.scala | 3 +- test/files/run/programmatic-main.check | 55 +-- 8 files changed, 563 insertions(+), 54 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/backend/opt/InlineExceptionHandlers.scala diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 57b5ed45ea..dd067750c8 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -29,7 +29,7 @@ import transform._ import backend.icode.{ ICodes, GenICode, ICodeCheckers } import backend.{ ScalaPrimitives, Platform, MSILPlatform, JavaPlatform } import backend.jvm.GenJVM -import backend.opt.{ Inliners, ClosureElimination, DeadCodeElimination } +import backend.opt.{ Inliners, InlineExceptionHandlers, ClosureElimination, DeadCodeElimination } import backend.icode.analysis._ class Global(var currentSettings: Settings, var reporter: Reporter) extends SymbolTable @@ -500,10 +500,17 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb val runsRightAfter = None } with Inliners + // phaseName = "inlineExceptionHandlers" + object inlineExceptionHandlers extends { + val global: Global.this.type = Global.this + val runsAfter = List[String]("inliner") + val runsRightAfter = None + } with InlineExceptionHandlers + // phaseName = "closelim" object closureElimination extends { val global: Global.this.type = Global.this - val runsAfter = List[String]("inliner") + val runsAfter = List[String]("inlineExceptionHandlers") val runsRightAfter = None } with ClosureElimination @@ -601,6 +608,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb cleanup -> "platform-specific cleanups, generate reflective calls", genicode -> "generate portable intermediate code", inliner -> "optimization: do inlining", + inlineExceptionHandlers -> "optimization: inline exception handlers", closureElimination -> "optimization: eliminate uncalled closures", deadCode -> "optimization: eliminate dead code", terminal -> "The last phase in the compiler chain" diff --git a/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala b/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala index 7c24c6338b..8f7fdab64c 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/ICodeCheckers.scala @@ -217,7 +217,13 @@ abstract class ICodeCheckers { throw new CheckerException(incompatibleString) } else { - val newStack = new TypeStack((s1.types, s2.types).zipped map lub) + val newStack: TypeStack = try { + new TypeStack((s1.types, s2.types).zipped map lub) + } catch { + case t: Exception => + checkerDebug(t.toString + ": " + s1.types.toString + " vs " + s2.types.toString) + new TypeStack(s1.types) + } if (newStack.isEmpty || s1.types == s2.types) () // not interesting to report else checkerDebug("Checker created new stack:\n (%s, %s) => %s".format(s1, s2, newStack)) @@ -705,7 +711,7 @@ abstract class ICodeCheckers { //////////////// Error reporting ///////////////////////// def icodeError(msg: String) { - ICodeCheckers.this.global.globalError( + ICodeCheckers.this.global.warning( "!! ICode checker fatality in " + method + "\n at: " + basicBlock.fullString + "\n error message: " + msg diff --git a/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala b/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala index c2994c66a5..a34d269cc9 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/analysis/TypeFlowAnalysis.scala @@ -535,9 +535,9 @@ abstract class TypeFlowAnalysis { case SCOPE_ENTER(_) | SCOPE_EXIT(_) => () - case LOAD_EXCEPTION(_) => + case LOAD_EXCEPTION(clasz) => stack.pop(stack.length) - stack.push(typeLattice.top) + stack.push(toTypeKind(clasz.tpe)) case _ => dumpClassesAndAbort("Unknown instruction: " + i) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index ab42f4176c..fad3a0f132 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -11,6 +11,7 @@ import java.io.{ DataOutputStream, OutputStream } import java.nio.ByteBuffer import scala.collection.{ mutable, immutable } import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer } +import scala.tools.reflect.SigParser import scala.tools.nsc.util.ScalaClassLoader import scala.tools.nsc.symtab._ import scala.reflect.internal.ClassfileConstants._ @@ -421,15 +422,11 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with c.cunit.source.toString) var fieldList = List[String]() - for { - f <- clasz.fields - if f.symbol.hasGetter - g = f.symbol.getter(c.symbol) - s = f.symbol.setter(c.symbol) - if g.isPublic && !(f.symbol.name startsWith "$") // inserting $outer breaks the bean - } { + for (f <- clasz.fields if f.symbol.hasGetter; + val g = f.symbol.getter(c.symbol); + val s = f.symbol.setter(c.symbol); + if g.isPublic && !(f.symbol.name startsWith "$")) // inserting $outer breaks the bean fieldList = javaName(f.symbol) :: javaName(g) :: (if (s != NoSymbol) javaName(s) else null) :: fieldList - } val methodList = for (m <- clasz.methods if !m.symbol.isConstructor && @@ -606,6 +603,14 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with nannots } + /** Run the signature parser to catch bogus signatures. + */ + def isValidSignature(sym: Symbol, sig: String) = ( + if (sym.isMethod) SigParser verifyMethod sig + else if (sym.isTerm) SigParser verifyType sig + else SigParser verifyClass sig + ) + // @M don't generate java generics sigs for (members of) implementation // classes, as they are monomorphic (TODO: ok?) private def needsGenericSignature(sym: Symbol) = !( @@ -627,8 +632,12 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with // println("addGenericSignature: "+ (sym.ownerChain map (x => (x.name, x.isImplClass)))) erasure.javaSig(sym, memberTpe) foreach { sig => debuglog("sig(" + jmember.getName + ", " + sym + ", " + owner + ") " + sig) - - if (settings.Xverify.value && !erasure.isValidSignature(sym, sig)) { + /** Since we're using a sun internal class for signature validation, + * we have to allow for it not existing or otherwise malfunctioning: + * in which case we treat every signature as valid. Medium term we + * should certainly write independent signature validation. + */ + if (settings.Xverify.value && SigParser.isParserAvailable && !isValidSignature(sym, sig)) { clasz.cunit.warning(sym.pos, """|compiler bug: created invalid generic signature for %s in %s |signature: %s @@ -652,10 +661,9 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with } } val index = jmember.getConstantPool.addUtf8(sig).toShort - if (opt.verboseDebug || erasure.traceSignatures) + if (opt.verboseDebug) atPhase(currentRun.erasurePhase) { - log("new signature for " + sym+":"+sym.info) - log(" " + sig) + println("add generic sig "+sym+":"+sym.info+" ==> "+sig+" @ "+index) } val buf = ByteBuffer.allocate(2) buf putShort index @@ -1818,14 +1826,20 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with /** * Compute the indexes of each local variable of the given - * method. Assumes parameters come first in the list of locals. + * method. *Does not assume the parameters come first!* */ def computeLocalVarsIndex(m: IMethod) { var idx = 1 if (m.symbol.isStaticMember) idx = 0; - for (l <- m.locals) { + for (l <- m.params) { + debuglog("Index value for " + l + "{" + l.## + "}: " + idx) + l.index = idx + idx += sizeOf(l.kind) + } + + for (l <- m.locals if !(m.params contains l)) { debuglog("Index value for " + l + "{" + l.## + "}: " + idx) l.index = idx idx += sizeOf(l.kind) diff --git a/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala b/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala index 68e0a76a78..c7939e09c7 100644 --- a/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala +++ b/src/compiler/scala/tools/nsc/backend/opt/DeadCodeElimination.scala @@ -68,7 +68,7 @@ abstract class DeadCodeElimination extends SubComponent { var method: IMethod = _ /** Map instructions who have a drop on some control path, to that DROP instruction. */ - val dropOf: mutable.Map[(BasicBlock, Int), (BasicBlock, Int)] = perRunCaches.newMap() + val dropOf: mutable.Map[(BasicBlock, Int), List[(BasicBlock, Int)]] = perRunCaches.newMap() def dieCodeDie(m: IMethod) { if (m.code ne null) { @@ -115,7 +115,7 @@ abstract class DeadCodeElimination extends SubComponent { case CALL_METHOD(m1, _) if isSideEffecting(m1) => true case LOAD_EXCEPTION(_) | DUP(_) | LOAD_MODULE(_) => true case _ => - dropOf((bb1, idx1)) = (bb, idx) + dropOf((bb1, idx1)) = (bb,idx) :: dropOf.getOrElse((bb1, idx1), Nil) // println("DROP is innessential: " + i + " because of: " + bb1(idx1) + " at " + bb1 + ":" + idx1) false } @@ -141,9 +141,9 @@ abstract class DeadCodeElimination extends SubComponent { val instr = bb(idx) if (!useful(bb)(idx)) { useful(bb) += idx - dropOf.get(bb, idx) match { - case Some((bb1, idx1)) => useful(bb1) += idx1 - case None => () + dropOf.get(bb, idx) foreach { + for ((bb1, idx1) <- _) + useful(bb1) += idx1 } instr match { case LOAD_LOCAL(l1) => diff --git a/src/compiler/scala/tools/nsc/backend/opt/InlineExceptionHandlers.scala b/src/compiler/scala/tools/nsc/backend/opt/InlineExceptionHandlers.scala new file mode 100644 index 0000000000..28d8b5d650 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/opt/InlineExceptionHandlers.scala @@ -0,0 +1,479 @@ +/* NSC -- new scala compiler + * Copyright 2005-2011 LAMP/EPFL + */ + +package scala.tools.nsc +package backend.opt +import scala.util.control.Breaks._ + +/** + * This optimization phase inlines the exception handlers so that further phases can optimize the code better + * + * {{{ + * try { + * ... + * if (condition) + * throw IllegalArgumentException("sth") + * } catch { + * case e: IllegalArgumentException => + * case e: ... => ... + * } + * }}} + * + * will inline the exception handler code to: + * + * {{{ + * try { + * ... + * if (condition) + * // + jump to the end of the catch statement + * } catch { + * case e: IllegalArgumentException => + * case e: ... => ... + * } + * }}} + * + * Q: How does the inlining work, ICode level? + * A: if a block contains a THROW(A) instruction AND there is a handler that takes A or a superclass of A we do: + * 1. We duplicate the handler code such that we can transform THROW into a JUMP + * 2. We analyze the handler to see what local it expects the exception to be placed in + * 3. We place the exception that is thrown in the correct "local variable" slot and clean up the stack + * 4. We finally JUMP to the duplicate handler + * All the above logic is implemented in InlineExceptionHandlersPhase.apply(bblock: BasicBlock) + * + * Q: Why do we need to duplicate the handler? + * A: An exception might be thrown in a method that we invoke in the function and we cannot see that THROW command + * directly. In order to catch such exceptions, we keep the exception handler in place and duplicate it in order + * to inline its code. + * + * @author Vlad Ureche + */ +abstract class InlineExceptionHandlers extends SubComponent { + import global._ + import icodes._ + import icodes.opcodes._ + + val phaseName = "inlineExceptionHandlers" + + /** Create a new phase */ + override def newPhase(p: Phase) = new InlineExceptionHandlersPhase(p) + + /** + * Inlining Exception Handlers + */ + class InlineExceptionHandlersPhase(prev: Phase) extends ICodePhase(prev) { + + def name = phaseName + + /* This map is used to keep track of duplicated exception handlers + * explanation: for each exception handler basic block, there is a copy of it + * -some exception handler basic blocks might not be duplicated because they have an unknown format => Option[(...)] + * -some exception handler duplicates expect the exception on the stack while others expect it in a local + * => Option[Local] + */ + var handlerCopies: Map[BasicBlock, Option[(Option[Local], BasicBlock)]] = Map.empty + /* This map is the inverse of handlerCopies, used to compute the stack of duplicate blocks */ + var handlerCopiesInverted: Map[BasicBlock, (BasicBlock, TypeKind)] = Map.empty + + /* Type Flow Analysis */ + val tfa: analysis.MethodTFA = new analysis.MethodTFA() + var tfaCache: Map[Int, tfa.lattice.Elem] = Map.empty + var analyzedMethod: IMethod = null + + /* Blocks that need to be analyzed */ + var todoBlocks: List[BasicBlock] = Nil + + /* Used only for warnings */ + var currentClass: IClass = null + + /** Apply exception handler inlining to a class */ + override def apply(c: IClass): Unit = + if (settings.inlineHandlers.value) { + val startTime = System.currentTimeMillis + currentClass = c + + log("Starting " + c.toString) + for (method <- c.methods) + apply(method) + + log("Finished " + c.toString + "... " + (System.currentTimeMillis - startTime) + "ms") + currentClass = null + } + + /** + * Apply exception handler inlining to a method + * + * Note: for each exception handling block, we (might) create duplicates. Therefore we iterate until we get to a + * fixed point where all the possible handlers have been inlined. + * + * TODO: Should we have an inlining depth limit? A nested sequence of n try-catch blocks can lead to at most 2n + * inlined blocks, so worst case scenario we double the size of the code + */ + def apply(method: IMethod): Unit = { + + if (method.code ne null) { + // create the list of starting blocks + todoBlocks = global.icodes.linearizer.linearize(method) + + while (todoBlocks.length > 0) { + val levelBlocks = todoBlocks + todoBlocks = Nil + for (bblock <- levelBlocks) + apply(bblock) // new blocks will be added to todoBlocks + } + } + + // Cleanup the references after we finished the file + handlerCopies = Map.empty + handlerCopiesInverted = Map.empty + todoBlocks = Nil + + // Type flow analysis cleanup + analyzedMethod = null + tfaCache = Map.empty + //TODO: Need a way to clear tfa structures + } + + /** Apply exception handler inlining to a basic block */ + def apply(bblock: BasicBlock): Unit = { + /* + * The logic of this entire method: + * - for each basic block, we look at each instruction until we find a THROW instruction + * - once we found a THROW instruction, we decide if it is DECIDABLE which of handler will catch the exception + * (see method findExceptionHandler for more details) + * - if we decided there is a handler that will catch the exception, we need to replace the THROW instruction by + * a set of equivalent instructions: + * * we need to compute the static types of the stack slots + * * we need to clear the stack, everything but the exception instance on top (or in a local variable slot) + * * we need to JUMP to the duplicate exception handler + * - we compute the static types of the stack slots in function getTypesAtInstruction + * - we duplicate the exception handler (and we get back the information of whether the duplicate expects the + * exception instance on top of the stack or in a local variable slot) + * - we compute the necessary code to put the exception in its place, clear the stack and JUMP + * - we change the THROW exception to the new Clear stack + JUMP code + */ + for ((instr, index) <- bblock.zipWithIndex) + if (instr.isInstanceOf[THROW]) { + + // Decide if any handler fits this exception + val THROW(clazz) = instr + findExceptionHandler(toTypeKind(clazz.tpe), bblock.exceptionSuccessors) match { + + case None => + // nothing to do, we cannot determine statically which handler will catch the exception + + case Some((handler, caughtException)) => + var onStackException: TypeKind = null + var thrownException: TypeKind = null + log(" Replacing " + instr.toString + " in " + bblock.toString + " to new handler") + + // Solve the stack and drop the element that we already stored, which should be the exception + // needs to be done here to be the first thing before code becomes altered + var typeInfo = getTypesAtInstruction(bblock, index) + + // Duplicate exception handler + duplicateExceptionHandlerWithCaching(handler) match { + + case None => + log(" Could not duplicate handler for " + instr.toString + " in " + bblock.toString) + + case Some((exceptionLocalOpt, newHandler)) => + + var canReplaceHandler = true + onStackException = typeInfo.head + thrownException = toTypeKind(clazz.tpe) + + // A couple of sanity checks, to make sure we don't touch code we can't safely handle + if (typeInfo.length < 1) canReplaceHandler = false + if (index != bblock.length - 1) canReplaceHandler = false + if (!(onStackException <:< thrownException)) canReplaceHandler = false + // in other words: what's on the stack MUST conform to what's in the THROW(..)! + + if (!canReplaceHandler) { + + assert(currentClass ne null) + currentClass.cunit.warning(NoPosition, "Unable to inline the exception handler inside incorrect" + + " block:\n" + bblock.mkString("\n") + "\nwith stack: " + typeInfo.toString + " just " + + "before instruction index " + index) + + } else { + + var newCode: List[Instruction] = Nil + var replaceType = -1 + + // Prepare the new code to replace the THROW instruction + exceptionLocalOpt match { + + // the handler duplicate expects the exception in a local: easy one :) + case Some(exceptionLocal) => + replaceType = 1 + val exceptionType = typeInfo.head + + newCode ::= STORE_LOCAL(exceptionLocal) + while (typeInfo.length > 1) { + typeInfo = typeInfo.tail // in the first cycle we remove the exception Type + newCode ::= DROP(typeInfo.head) + } + newCode ::= JUMP(newHandler) + + // we already have the exception on top of the stack, only need to JUMP + case None if (typeInfo.length == 1) => + replaceType = 2 + newCode = JUMP(newHandler) :: Nil + + // we have the exception on top of the stack but we have other stuff on the stack + // create a local, load exception, clear the stack and finally store the exception on the stack + case None => + replaceType = 3 + val exceptionType = typeInfo.head + + assert(currentClass ne null) + assert(currentClass.cunit ne null) + + // Here we could create a single local for all exceptions of a certain type. TODO: try that. + val localName = currentClass.cunit.freshTermName("exception$") + val localType = exceptionType + val localSymbol = bblock.method.symbol.newValue(NoPosition, localName).setInfo(localType.toType) + val local = new Local(localSymbol, localType, false) + bblock.method.addLocal(local) + + // Save the exception, drop the stack and place back the exception + newCode ::= STORE_LOCAL(local) + while (typeInfo.length > 1) { + typeInfo = typeInfo.tail // in the first cycle we remove the exception Type + newCode ::= DROP(typeInfo.head) + } + newCode ::= LOAD_LOCAL(local) + newCode ::= JUMP(newHandler) + } + // replace THROW by the new code + bblock.replaceInstruction(instr, newCode.reverse) + + // notify the successors changed for the current block + // notify the predecessors changed for the inlined handler block + bblock.touched = true + newHandler.touched = true + + log(" Replaced " + instr.toString + " in " + bblock.toString + " to new handler") + log("OPTIMIZED[" + replaceType + "] class " + currentClass.toString + " method " + + bblock.method.toString + " block " + bblock.toString + " newhandler " + + newHandler.toString + ":\n\t\t" + onStackException.toString + " <:< " + + thrownException.toString + " <:< " + caughtException.toString) + } + + } + } + } + } + + /** + * Gets the types on the stack at a certain point in the program. Note that we want to analyze the method lazily + * and therefore use the analyzedMethod variable + */ + def getTypesAtInstruction(bblock: BasicBlock, index: Int): List[TypeKind] = { + + // get the stack at the block entry + var typeInfo = getTypesAtBlockEntry(bblock) + + // perform tfa to the current instruction + log(" stack at the beginning of block " + bblock.toString + " in function " + + bblock.method.toString + ": " + typeInfo.stack.toString) + for (i <- 0 to (index - 1)) { + typeInfo = tfa.interpret(typeInfo, bblock(i)) + log(" stack after interpret: " + typeInfo.stack.toString + " after instruction " + + bblock(i).toString) + } + log(" stack before instruction " + index + " of block " + bblock.toString + " in function " + + bblock.method.toString + ": " + typeInfo.stack.toString) + + // return the result + typeInfo.stack.types + } + + + /** + * Gets the stack at the block entry. Normally the typeFlowAnalysis should be run again, but we know how to compute + * the stack for handler duplicates. For the locals, it's safe to assume the info from the original handler is + * still valid (a more precise analysis can be done, but it's not necessary) + */ + def getTypesAtBlockEntry(bblock: BasicBlock): tfa.lattice.Elem = { + + // lazily perform tfa, because it's expensive + // cache results by block label, as rewriting the code messes up the block's hashCode + if (analyzedMethod eq null) { + analyzedMethod = bblock.method + tfa.init(bblock.method) + tfa.run + log(" performed tfa on method: " + bblock.method.toString) + + for (block <- bblock.method.code.blocks.sortWith(_.label < _.label)) + tfaCache += block.label -> tfa.in(block) + } + + log(" getting typeinfo at the beginning of block " + bblock.toString) + + if (tfaCache contains bblock.label) + tfaCache(bblock.label) + else { + // this block was not analyzed, but it's a copy of some other block so its stack should be the same + log(" getting typeinfo at the beginning of block " + bblock.toString + " as a copy of " + + handlerCopiesInverted(bblock).toString) + val (origBlock, exception) = handlerCopiesInverted(bblock) + val typeInfo = getTypesAtBlockEntry(origBlock) + val stack = handlerCopies(origBlock).get._1 match { + case Some(_) => Nil // empty stack, the handler copy expects an empty stack + case None => List(exception) // one slot on the stack for the exception + } + + // If we use the mutability property, it crashes the analysis + tfa.lattice.IState(new analysis.VarBinding(typeInfo.vars), new icodes.TypeStack(stack)) + } + } + + /** + * Finds the first exception handler that matches the current exception + * + * Note the following code: + * {{{ + * try { + * throw new IllegalArgumentException("...") + * } catch { + * case e: RuntimeException => log("RuntimeException") + * case i: IllegalArgumentException => log("IllegalArgumentException") + * } + * }}} + * + * will print "RuntimeException" => we need the *first* valid handler + * + * There's a hidden catch here: say we have the following code: + * {{{ + * try { + * val exception: Throwable = + * if (scala.util.Random.nextInt % 2 == 0) + * new IllegalArgumentException("even") + * else + * new StackOverflowError("odd") + * throw exception + * } catch { + * case e: IllegalArgumentException => + * println("Correct, IllegalArgumentException") + * case e: StackOverflowError => + * println("Correct, StackOverflowException") + * case t: Throwable => + * println("WROOOONG, not Throwable!") + * } + * }}} + * + * We don't want to select a handler if there's at least one that's more specific! + */ + def findExceptionHandler(thrownException: TypeKind, handlers: List[BasicBlock]): Option[(BasicBlock, TypeKind)] = { + + // function to extract exeption type + def extractException(bb: BasicBlock): Option[TypeKind] = + if (bb.length >= 1) { + bb.head match { + case LOAD_EXCEPTION(clazz) => Some(toTypeKind(clazz.tpe)) + case _ => None + } + } else + None + + var finalHandlerData: Option[(BasicBlock, TypeKind)] = None + + breakable { + for (handler <- handlers) + extractException(handler) match { + case Some(caughtException) if (thrownException <:< caughtException) => + // we'll do inlining here: createdException <:< thrownException <:< caughtException, good! + finalHandlerData = Some((handler, caughtException)) + break + case Some(caughtException) if (caughtException <:< thrownException) => + // we can't do inlining here, the handling mechanism is more precise than we can reason about + finalHandlerData = None + break + case _ => + // no result yet, look deeper in the handler stack :) + } + } + + finalHandlerData + } + + + /** + * This function takes care of duplicating the basic block code for inlining the handler + * + * Note: This function does not duplicate the same basic block twice. It wil contain a map of the duplicated + * basic blocks + */ + def duplicateExceptionHandlerWithCaching(handler: BasicBlock): Option[(Option[Local], BasicBlock)] = { + + if (!(handlerCopies contains handler)) + handlerCopies = handlerCopies + (handler -> duplicateExceptionHandler(handler)) + + handlerCopies(handler) + } + + /** This function takes care of actual duplication */ + def duplicateExceptionHandler(handler: BasicBlock): Option[(Option[Local], BasicBlock)] = { + + log(" duplicating handler block " + handler.toString) + + // Sanitiy checks + var canDuplicate = true + if (handler.length < 2) canDuplicate = false + if (!(handler(0).isInstanceOf[LOAD_EXCEPTION])) canDuplicate = false + + canDuplicate match { + case true => + + val LOAD_EXCEPTION(caughtClass) = handler(0) + val caughtException = toTypeKind(caughtClass.tpe) + + val exceptionLocal: Option[Local] = + if (handler(1).isInstanceOf[STORE_LOCAL]) + STORE_LOCAL.unapply(handler(1).asInstanceOf[STORE_LOCAL]) + else + None + + val dropIntstructions = + if (exceptionLocal == None) + 1 // we only drop the LOAD_EXCEPTION and expect the exception on the stack + else + 2 // we drop both LOAD_EXCEPTION and STORE_LOCAL + + // copy the exception handler code once again, dropping the LOAD_EXCEPTION + val copy = handler.code.newBlock + for (instr <- handler.drop(dropIntstructions)) + copy.emit(instr, instr.pos) + copy.close + + // extend the handlers of the handler to the copy + for (parentHandler <-handler.method.exh) + if (parentHandler covers handler) { + parentHandler.addCoveredBlock(copy) + // notify the parent handler that the successors changed + parentHandler.startBlock.touched = true + } + + // notify the successors of the inlined handler might have changed + copy.touched = true + handler.touched = true + + log(" duplicated handler block " + handler.toString + " to " + copy.toString) + + // announce the duplicate handler + handlerCopiesInverted = handlerCopiesInverted + (copy -> ((handler, caughtException))) + todoBlocks = copy :: todoBlocks + + Some((exceptionLocal, copy)) + + case false => + assert(currentClass ne null) + currentClass.cunit.warning(NoPosition, "Unable to inline the exception handler due to incorrect format:\n" + + handler.mkString("\n")) + None + } + } + } +} diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index e0366f1e3d..09ad877d25 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -48,7 +48,7 @@ trait ScalaSettings extends AbsScalaSettings val d = OutputSetting (outputDirs, ".") val optimise = BooleanSetting ("-optimise", "Generates faster bytecode by applying optimisations to the program") . withAbbreviation("-optimize") . - withPostSetHook(set => List(inline, Xcloselim, Xdce) foreach (_.value = set.value)) + withPostSetHook(set => List(inline, inlineHandlers, Xcloselim, Xdce) foreach (_.value = set.value)) val nospecialization = BooleanSetting ("-no-specialization", "Ignore @specialize annotations.") /** @@ -120,6 +120,7 @@ trait ScalaSettings extends AbsScalaSettings val termConflict = ChoiceSetting ("-Yresolve-term-conflict", "strategy", "Resolve term conflicts", List("package", "object", "error"), "error") val inline = BooleanSetting ("-Yinline", "Perform inlining when possible.") + val inlineHandlers= BooleanSetting ("-Yinline-handlers", "Perform exception handler inlining when possible.") val Xlinearizer = ChoiceSetting ("-Ylinearizer", "which", "Linearizer to use", List("normal", "dfs", "rpo", "dump"), "rpo") val log = PhasesSetting ("-Ylog", "Log operations during") val Ylogcp = BooleanSetting ("-Ylog-classpath", "Output information about what classpath is being applied.") diff --git a/test/files/run/programmatic-main.check b/test/files/run/programmatic-main.check index be446b58cf..4aeb3ab60c 100644 --- a/test/files/run/programmatic-main.check +++ b/test/files/run/programmatic-main.check @@ -1,28 +1,29 @@ - phase name id description - ---------- -- ----------- - parser 1 parse source into ASTs, perform simple desugaring - namer 2 resolve names, attach symbols to named trees -packageobjects 3 load package objects - typer 4 the meat and potatoes: type the trees -superaccessors 5 add super accessors in traits and nested classes - pickler 6 serialize symbol tables - refchecks 7 reference/override checking, translate nested objects - liftcode 8 reify trees - uncurry 9 uncurry, translate function values to anonymous classes - tailcalls 10 replace tail calls by jumps - specialize 11 @specialized-driven class and method specialization - explicitouter 12 this refs to outer pointers, translate patterns - erasure 13 erase types, add interfaces for traits - lazyvals 14 allocate bitmaps, translate lazy vals into lazified defs - lambdalift 15 move nested functions to top level - constructors 16 move field definitions into constructors - flatten 17 eliminate inner classes - mixin 18 mixin composition - cleanup 19 platform-specific cleanups, generate reflective calls - icode 20 generate portable intermediate code - inliner 21 optimization: do inlining - closelim 22 optimization: eliminate uncalled closures - dce 23 optimization: eliminate dead code - jvm 24 generate JVM bytecode - terminal 25 The last phase in the compiler chain + phase name id description + ---------- -- ----------- + parser 1 parse source into ASTs, perform simple desugaring + namer 2 resolve names, attach symbols to named trees + packageobjects 3 load package objects + typer 4 the meat and potatoes: type the trees + superaccessors 5 add super accessors in traits and nested classes + pickler 6 serialize symbol tables + refchecks 7 reference/override checking, translate nested objects + liftcode 8 reify trees + uncurry 9 uncurry, translate function values to anonymous classes + tailcalls 10 replace tail calls by jumps + specialize 11 @specialized-driven class and method specialization + explicitouter 12 this refs to outer pointers, translate patterns + erasure 13 erase types, add interfaces for traits + lazyvals 14 allocate bitmaps, translate lazy vals into lazified defs + lambdalift 15 move nested functions to top level + constructors 16 move field definitions into constructors + flatten 17 eliminate inner classes + mixin 18 mixin composition + cleanup 19 platform-specific cleanups, generate reflective calls + icode 20 generate portable intermediate code + inliner 21 optimization: do inlining +inlineExceptionHandlers 22 optimization: inline exception handlers + closelim 23 optimization: eliminate uncalled closures + dce 24 optimization: eliminate dead code + jvm 25 generate JVM bytecode + terminal 26 The last phase in the compiler chain -- cgit v1.2.3