diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala new file mode 100644 index 0000000000..27bcbb82d4 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala @@ -0,0 +1,399 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + + +package scala +package tools.nsc +package backend +package jvm + +import scala.collection.{ mutable, immutable } +import scala.annotation.switch + +import scala.tools.asm + +/* + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ + * @version 1.0 + * + */ +abstract class BCodeSyncAndTry extends BCodeBodyBuilder { + import global._ + + + /* + * Functionality to lower `synchronized` and `try` expressions. + */ + abstract class SyncAndTryBuilder(cunit: CompilationUnit) extends PlainBodyBuilder(cunit) { + + def genSynchronized(tree: Apply, expectedType: BType): BType = { + val Apply(fun, args) = tree + val monitor = locals.makeLocal(ObjectReference, "monitor") + val monCleanup = new asm.Label + + // if the synchronized block returns a result, store it in a local variable. + // Just leaving it on the stack is not valid in MSIL (stack is cleaned when leaving try-blocks). + val hasResult = (expectedType != UNIT) + val monitorResult: Symbol = if (hasResult) locals.makeLocal(tpeTK(args.head), "monitorResult") else null; + + /* ------ (1) pushing and entering the monitor, also keeping a reference to it in a local var. ------ */ + genLoadQualifier(fun) + bc dup ObjectReference + locals.store(monitor) + emit(asm.Opcodes.MONITORENTER) + + /* ------ (2) Synchronized block. + * Reached by fall-through from (1). + * Protected by: + * (2.a) the EH-version of the monitor-exit, and + * (2.b) whatever protects the whole synchronized expression. + * ------ + */ + val startProtected = currProgramPoint() + registerCleanup(monCleanup) + genLoad(args.head, expectedType /* toTypeKind(tree.tpe.resultType) */) + unregisterCleanup(monCleanup) + if (hasResult) { locals.store(monitorResult) } + nopIfNeeded(startProtected) + val endProtected = currProgramPoint() + + /* ------ (3) monitor-exit after normal, non-early-return, termination of (2). + * Reached by fall-through from (2). + * Protected by whatever protects the whole synchronized expression. + * ------ + */ + locals.load(monitor) + emit(asm.Opcodes.MONITOREXIT) + if (hasResult) { locals.load(monitorResult) } + val postHandler = new asm.Label + bc goTo postHandler + + /* ------ (4) exception-handler version of monitor-exit code. + * Reached upon abrupt termination of (2). + * Protected by whatever protects the whole synchronized expression. + * ------ + */ + protect(startProtected, endProtected, currProgramPoint(), ThrowableReference) + locals.load(monitor) + emit(asm.Opcodes.MONITOREXIT) + emit(asm.Opcodes.ATHROW) + + /* ------ (5) cleanup version of monitor-exit code. + * Reached upon early-return from (2). + * Protected by whatever protects the whole synchronized expression. + * ------ + */ + if (shouldEmitCleanup) { + markProgramPoint(monCleanup) + locals.load(monitor) + emit(asm.Opcodes.MONITOREXIT) + pendingCleanups() + } + + /* ------ (6) normal exit of the synchronized expression. + * Reached after normal, non-early-return, termination of (3). + * Protected by whatever protects the whole synchronized expression. + * ------ + */ + mnode visitLabel postHandler + + lineNumber(tree) + + expectedType + } + + /* + * Detects whether no instructions have been emitted since label `lbl` and if so emits a NOP. + * Useful to avoid emitting an empty try-block being protected by exception handlers, + * which results in "java.lang.ClassFormatError: Illegal exception table range". See SI-6102. + */ + def nopIfNeeded(lbl: asm.Label) { + val noInstructionEmitted = isAtProgramPoint(lbl) + if (noInstructionEmitted) { emit(asm.Opcodes.NOP) } + } + + /* + * Emitting try-catch is easy, emitting try-catch-finally not quite so. + * A finally-block (which always has type Unit, thus leaving the operand stack unchanged) + * affects control-transfer from protected regions, as follows: + * + * (a) `return` statement: + * + * First, the value to return (if any) is evaluated. + * Afterwards, all enclosing finally-blocks are run, from innermost to outermost. + * Only then is the return value (if any) returned. + * + * Some terminology: + * (a.1) Executing a return statement that is protected + * by one or more finally-blocks is called "early return" + * (a.2) the chain of code sections (a code section for each enclosing finally-block) + * to run upon early returns is called "cleanup chain" + * + * As an additional spin, consider a return statement in a finally-block. + * In this case, the value to return depends on how control arrived at that statement: + * in case it arrived via a previous return, the previous return enjoys priority: + * the value to return is given by that statement. + * + * (b) A finally-block protects both the try-clause and the catch-clauses. + * + * Sidenote: + * A try-clause may contain an empty block. On CLR, a finally-block has special semantics + * regarding Abort interruptions; but on the JVM it's safe to elide an exception-handler + * that protects an "empty" range ("empty" as in "containing NOPs only", + * see `asm.optimiz.DanglingExcHandlers` and SI-6720). + * + * This means a finally-block indicates instructions that can be reached: + * (b.1) Upon normal (non-early-returning) completion of the try-clause or a catch-clause + * In this case, the next-program-point is that following the try-catch-finally expression. + * (b.2) Upon early-return initiated in the try-clause or a catch-clause + * In this case, the next-program-point is the enclosing cleanup section (if any), otherwise return. + * (b.3) Upon abrupt termination (due to unhandled exception) of the try-clause or a catch-clause + * In this case, the unhandled exception must be re-thrown after running the finally-block. + * + * (c) finally-blocks are implicit to `synchronized` (a finally-block is added to just release the lock) + * that's why `genSynchronized()` too emits cleanup-sections. + * + * A number of code patterns can be emitted to realize the intended semantics. + * + * A popular alternative (GenICode, javac) consists in duplicating the cleanup-chain at each early-return position. + * The principle at work being that once control is transferred to a cleanup-section, + * control will always stay within the cleanup-chain. + * That is, barring an exception being thrown in a cleanup-section, in which case the enclosing try-block + * (reached via abrupt termination) takes over. + * + * The observations above hint at another code layout, less verbose, for the cleanup-chain. + * + * The code layout that GenBCode emits takes into account that once a cleanup section has been reached, + * jumping to the next cleanup-section (and so on, until the outermost one) realizes the correct semantics. + * + * There is still code duplication in that two cleanup-chains are needed (but this is unavoidable, anyway): + * one for normal control flow and another chain consisting of exception handlers. + * The in-line comments below refer to them as + * - "early-return-cleanups" and + * - "exception-handler-version-of-finally-block" respectively. + * + */ + def genLoadTry(tree: Try): BType = { + + val Try(block, catches, finalizer) = tree + val kind = tpeTK(tree) + + val caseHandlers: List[EHClause] = + for (CaseDef(pat, _, caseBody) <- catches) yield { + pat match { + case Typed(Ident(nme.WILDCARD), tpt) => NamelessEH(tpeTK(tpt), caseBody) + case Ident(nme.WILDCARD) => NamelessEH(ThrowableReference, caseBody) + case Bind(_, _) => BoundEH (pat.symbol, caseBody) + } + } + + // ------ (0) locals used later ------ + + /* + * `postHandlers` is a program point denoting: + * (a) the finally-clause conceptually reached via fall-through from try-catch-finally + * (in case a finally-block is present); or + * (b) the program point right after the try-catch + * (in case there's no finally-block). + * The name choice emphasizes that the code section lies "after all exception handlers", + * where "all exception handlers" includes those derived from catch-clauses as well as from finally-blocks. + */ + val postHandlers = new asm.Label + + val hasFinally = (finalizer != EmptyTree) + + /* + * used in the finally-clause reached via fall-through from try-catch, if any. + */ + val guardResult = hasFinally && (kind != UNIT) && mayCleanStack(finalizer) + + /* + * please notice `tmp` has type tree.tpe, while `earlyReturnVar` has the method return type. + * Because those two types can be different, dedicated vars are needed. + */ + val tmp = if (guardResult) locals.makeLocal(tpeTK(tree), "tmp") else null; + + /* + * upon early return from the try-body or one of its EHs (but not the EH-version of the finally-clause) + * AND hasFinally, a cleanup is needed. + */ + val finCleanup = if (hasFinally) new asm.Label else null + + /* ------ (1) try-block, protected by: + * (1.a) the EHs due to case-clauses, emitted in (2), + * (1.b) the EH due to finally-clause, emitted in (3.A) + * (1.c) whatever protects the whole try-catch-finally expression. + * ------ + */ + + val startTryBody = currProgramPoint() + registerCleanup(finCleanup) + genLoad(block, kind) + unregisterCleanup(finCleanup) + nopIfNeeded(startTryBody) + val endTryBody = currProgramPoint() + bc goTo postHandlers + + /* ------ (2) One EH for each case-clause (this does not include the EH-version of the finally-clause) + * An EH in (2) is reached upon abrupt termination of (1). + * An EH in (2) is protected by: + * (2.a) the EH-version of the finally-clause, if any. + * (2.b) whatever protects the whole try-catch-finally expression. + * ------ + */ + + for (ch <- caseHandlers) { + + // (2.a) emit case clause proper + val startHandler = currProgramPoint() + var endHandler: asm.Label = null + var excType: BType = null + registerCleanup(finCleanup) + ch match { + case NamelessEH(typeToDrop, caseBody) => + bc drop typeToDrop + genLoad(caseBody, kind) // adapts caseBody to `kind`, thus it can be stored, if `guardResult`, in `tmp`. + nopIfNeeded(startHandler) + endHandler = currProgramPoint() + excType = typeToDrop + + case BoundEH (patSymbol, caseBody) => + // test/files/run/contrib674.scala , a local-var already exists for patSymbol. + // rather than creating on first-access, we do it right away to emit debug-info for the created local var. + val Local(patTK, _, patIdx, _) = locals.getOrMakeLocal(patSymbol) + bc.store(patIdx, patTK) + genLoad(caseBody, kind) + nopIfNeeded(startHandler) + endHandler = currProgramPoint() + emitLocalVarScope(patSymbol, startHandler, endHandler) + excType = patTK + } + unregisterCleanup(finCleanup) + // (2.b) mark the try-body as protected by this case clause. + protect(startTryBody, endTryBody, startHandler, excType) + // (2.c) emit jump to the program point where the finally-clause-for-normal-exit starts, or in effect `after` if no finally-clause was given. + bc goTo postHandlers + + } + + /* ------ (3.A) The exception-handler-version of the finally-clause. + * Reached upon abrupt termination of (1) or one of the EHs in (2). + * Protected only by whatever protects the whole try-catch-finally expression. + * ------ + */ + + // a note on terminology: this is not "postHandlers", despite appearences. + // "postHandlers" as in the source-code view. And from that perspective, both (3.A) and (3.B) are invisible implementation artifacts. + if (hasFinally) { + nopIfNeeded(startTryBody) + val finalHandler = currProgramPoint() // version of the finally-clause reached via unhandled exception. + protect(startTryBody, finalHandler, finalHandler, null) + val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(ThrowableReference, "exc")) + bc.store(eIdx, eTK) + emitFinalizer(finalizer, null, isDuplicate = true) + bc.load(eIdx, eTK) + emit(asm.Opcodes.ATHROW) + } + + /* ------ (3.B) Cleanup-version of the finally-clause. + * Reached upon early RETURN from (1) or upon early RETURN from one of the EHs in (2) + * (and only from there, ie reached only upon early RETURN from + * program regions bracketed by registerCleanup/unregisterCleanup). + * Protected only by whatever protects the whole try-catch-finally expression. + * + * Given that control arrives to a cleanup section only upon early RETURN, + * the value to return (if any) is always available. Therefore, a further RETURN + * found in a cleanup section is always ignored (a warning is displayed, @see `genReturn()`). + * In order for `genReturn()` to know whether the return statement is enclosed in a cleanup section, + * the variable `insideCleanupBlock` is used. + * ------ + */ + + // this is not "postHandlers" either. + // `shouldEmitCleanup` can be set, and at the same time this try expression may lack a finally-clause. + // In other words, all combinations of (hasFinally, shouldEmitCleanup) are valid. + if (hasFinally && shouldEmitCleanup) { + val savedInsideCleanup = insideCleanupBlock + insideCleanupBlock = true + markProgramPoint(finCleanup) + // regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted. + emitFinalizer(finalizer, null, isDuplicate = true) + pendingCleanups() + insideCleanupBlock = savedInsideCleanup + } + + /* ------ (4) finally-clause-for-normal-nonEarlyReturn-exit + * Reached upon normal, non-early-return termination of (1) or of an EH in (2). + * Protected only by whatever protects the whole try-catch-finally expression. + * TODO explain what happens upon RETURN contained in (4) + * ------ + */ + + markProgramPoint(postHandlers) + if (hasFinally) { + emitFinalizer(finalizer, tmp, isDuplicate = false) // the only invocation of emitFinalizer with `isDuplicate == false` + } + + kind + } // end of genLoadTry() + + /* if no more pending cleanups, all that remains to do is return. Otherwise jump to the next (outer) pending cleanup. */ + private def pendingCleanups() { + cleanups match { + case Nil => + if (earlyReturnVar != null) { + locals.load(earlyReturnVar) + bc.emitRETURN(locals(earlyReturnVar).tk) + } else { + bc emitRETURN UNIT + } + shouldEmitCleanup = false + + case nextCleanup :: _ => + bc goTo nextCleanup + } + } + + def protect(start: asm.Label, end: asm.Label, handler: asm.Label, excType: BType) { + val excInternalName: String = + if (excType == null) null + else excType.getInternalName + assert(start != end, "protecting a range of zero instructions leads to illegal class format. Solution: add a NOP to that range.") + mnode.visitTryCatchBlock(start, end, handler, excInternalName) + } + + /* `tmp` (if non-null) is the symbol of the local-var used to preserve the result of the try-body, see `guardResult` */ + def emitFinalizer(finalizer: Tree, tmp: Symbol, isDuplicate: Boolean) { + var saved: immutable.Map[ /* LabelDef */ Symbol, asm.Label ] = null + if (isDuplicate) { + saved = jumpDest + for(ldef <- labelDefsAtOrUnder(finalizer)) { + jumpDest -= ldef.symbol + } + } + // when duplicating, the above guarantees new asm.Labels are used for LabelDefs contained in the finalizer (their vars are reused, that's ok) + if (tmp != null) { locals.store(tmp) } + genLoad(finalizer, UNIT) + if (tmp != null) { locals.load(tmp) } + if (isDuplicate) { + jumpDest = saved + } + } + + /* Does this tree have a try-catch block? */ + def mayCleanStack(tree: Tree): Boolean = tree exists { t => t.isInstanceOf[Try] } + + abstract class Cleanup(val value: AnyRef) { } + case class MonitorRelease(v: Symbol) extends Cleanup(v) { } + case class Finalizer(f: Tree) extends Cleanup (f) { } + + trait EHClause + case class NamelessEH(typeToDrop: BType, caseBody: Tree) extends EHClause + case class BoundEH (patSymbol: Symbol, caseBody: Tree) extends EHClause + + } + +} |