nativeStackKeepers.scala 5.98 KB
Newer Older
Kunshan Wang's avatar
Kunshan Wang committed
1 2 3 4 5 6 7
package uvm.refimpl.nat

import uvm.FuncSig
import uvm.refimpl.itpr.ValueBox
import uvm.refimpl.mem.TypeSizes.Word
import uvm.{ Function => MFunc }
import uvm.refimpl.UvmRuntimeException
8 9 10 11 12 13
import com.typesafe.scalalogging.Logger
import org.slf4j.LoggerFactory

object PoorManAgent {
  val logger = Logger(LoggerFactory.getLogger(getClass.getName))
}
Kunshan Wang's avatar
Kunshan Wang committed
14 15 16 17 18 19

/**
 * An Agent is something that has a mailbox. It allows Erlang-like usage pattern. But it does not automatically
 * provide a thread.
 */
trait PoorManAgent[T] {
20
  import PoorManAgent._
Kunshan Wang's avatar
Kunshan Wang committed
21 22 23 24 25 26 27 28 29 30 31 32 33
  val inBox = new java.util.concurrent.ArrayBlockingQueue[T](1)

  def send(msg: T) {
    inBox.add(msg)
  }

  def receive[T]() = {
    inBox.take()
  }
}

abstract class NativeCallResult
object NativeCallResult {
34
  case class CallBack(func: MFunc, cookie: Long, args: Seq[ValueBox], retBox: ValueBox) extends NativeCallResult
Kunshan Wang's avatar
Kunshan Wang committed
35 36 37
  case class Return() extends NativeCallResult
}

38 39 40 41
object NativeStackKeeper {
  val logger = Logger(LoggerFactory.getLogger(getClass.getName))
}

Kunshan Wang's avatar
Kunshan Wang committed
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
/**
 * This class (the master) contains a slave: a Java thread which contains many native frames.
 * <p>
 * A NativeStackKeeper is allocated for each Mu stack. All native frames are kept on the slave.
 * Calling native function is done by sending the slave a message so the slave calls the native
 * function on behalf of the master, i.e. the interpreter thread.
 * <p>
 * The slave calls the native function and may have two results. It either returns directly, or
 * calls back to Java (Scala) one or more times before returning, i.e. it eventually returns.
 * <ol>
 * <li>If the native function returns, the slave sends the Return message to the master.
 * <li>If the native function calls back, the slave sends the CallBack message to the caller and
 * wait for the master to supply the return value of the call-back function.
 * </ol>
 * When the Mu stack is killed, the master asks the slave to stop by sending the Stop message.
 *
 * this
 * thread sends back a message to the interpreter thread * while keeping the native frame on itself, "freezing" the native frame until the interpreter thread returns.
 * This allows the interpreter thread to swap between multiple Mu stacks (including native frames).
 */
class NativeStackKeeper(implicit nativeCallHelper: NativeCallHelper) extends PoorManAgent[NativeCallResult] {
63
  import NativeStackKeeper._
Kunshan Wang's avatar
Kunshan Wang committed
64 65 66 67 68 69 70 71 72 73 74 75 76 77

  abstract class ToSlave
  object ToSlave {
    case class CallNative(sig: FuncSig, func: Word, args: Seq[ValueBox], retBox: ValueBox) extends ToSlave
    case class ReturnToCallBack() extends ToSlave
    case class Stop() extends ToSlave
  }

  private val master = this

  class Slave extends Runnable with PoorManAgent[ToSlave] {

    def run(): Unit = {
      while (true) {
78 79 80 81 82 83 84 85
        val received = try {
          receive()
        } catch {
          case e: InterruptedException =>
            logger.debug("Native stack keeper slave thread interrupted. No native frames. Stopping slave...")
            return
        }
        received match {
Kunshan Wang's avatar
Kunshan Wang committed
86 87 88 89 90 91
          case ToSlave.CallNative(sig, func, args, retBox) => {
            nativeCallHelper.callNative(master, sig, func, args, retBox)
            master.send(NativeCallResult.Return())
          }

          case ToSlave.ReturnToCallBack() => {
92
            throw new UvmNativeCallException("Attempt to return to native function, but no native function called back before")
Kunshan Wang's avatar
Kunshan Wang committed
93 94 95
          }

          case ToSlave.Stop() => {
96
            logger.debug("Received Stop msg. Stopping slave...")
Kunshan Wang's avatar
Kunshan Wang committed
97 98 99 100 101 102
            return
          }
        }
      }
    }

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
    def onCallBack(func: MFunc, cookie: Long, args: Seq[ValueBox], retBox: ValueBox): Unit = {
      logger.debug("sending master the CallBack message...")
      master.send(NativeCallResult.CallBack(func, cookie, args, retBox))
      logger.debug("msg sent. Waiting for master's reply...")
      try {
        while (true) {
          val received = try {
            receive()
          } catch {
            case e: InterruptedException =>
              logger.debug("Native stack keeper slave thread interrupted while there are native frames alive. " +
                "This may happen when the micro VM itself is killed but the stack is still alive. " +
                "Prepare for undefined behaviours in native frames (or JVM frames if the native calls back again).")
              throw e
          }
          received match {
            case ToSlave.CallNative(sig, func, args, retBox) => {
              nativeCallHelper.callNative(master, sig, func, args, retBox)
              master.send(NativeCallResult.Return())
            }

            case ToSlave.ReturnToCallBack() => {
              return
            }

            case ToSlave.Stop() => {
              val msg = "Attempt to kill the native stack, but there are still native frames alive. " +
                "This has implementation-defined behaviour. Although not forbidden, it is almost always dangerous."
              logger.debug(msg)
              throw new UvmNativeCallException(msg)
            }
          }
        }
      } catch {
        case e: Exception =>
          logger.debug("Exception occured in the slave thread when there are native threads alive. " +
            "Prepare for undefined behaviours in native frames (or JVM frames if the native calls back again).")
          throw e
      }
Kunshan Wang's avatar
Kunshan Wang committed
142

143
      logger.debug("returning...")
Kunshan Wang's avatar
Kunshan Wang committed
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
    }
  }

  val slave = new Slave()
  val slaveThread = new Thread(slave)
  slaveThread.start()

  def callNative(sig: FuncSig, func: Word, args: Seq[ValueBox], retBox: ValueBox): NativeCallResult = {
    slave.send(ToSlave.CallNative(sig, func, args, retBox))
    val result = receive()
    result
  }

  def returnToCallBack(): NativeCallResult = {
    slave.send(ToSlave.ReturnToCallBack())
    val result = receive()
    result
  }

  def close(): Unit = {
    slave.send(ToSlave.Stop())
    slaveThread.join()
  }
}

/**
 * Thrown when an error happens which involves calling native functions.
 */
class UvmNativeCallException(message: String = null, cause: Throwable = null) extends UvmRuntimeException(message, cause)