alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Scala example source code file (BCodeSyncAndTry.scala)

This example Scala source code file (BCodeSyncAndTry.scala) is included in my "Source Code Warehouse" project. The intent of this project is to help you more easily find Scala source code examples by using tags.

All credit for the original source code belongs to scala-lang.org; I'm just trying to make examples easier to find. (For my Scala work, see my Scala examples and tutorials.)

Scala tags/keywords

annotation, boolean, boundeh, btype, collection, ehclause, local, namelesseh, symbol, tree, try, unit

The BCodeSyncAndTry.scala Scala example source code

/* 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] }

    trait EHClause
    case class NamelessEH(typeToDrop: BType,  caseBody: Tree) extends EHClause
    case class BoundEH    (patSymbol: Symbol, caseBody: Tree) extends EHClause

  }

}

Other Scala source code examples

Here is a short list of links related to this Scala BCodeSyncAndTry.scala source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.