GitLab will be upgraded on June 2nd 2020 at 2.00 pm (AEDT) to 3.00 pm (AEDT) due to Critical Security Patch Availability. During the update, GitLab and Mattermost services will not be available. If you have any concerns with this, please talk to local Gitlab admin team.

Commit 86493917 authored by Kunshan Wang's avatar Kunshan Wang

WIP: callback

parent c589e5c8
......@@ -79,4 +79,11 @@ class MicroVM(heapSize: Word = MicroVM.DEFAULT_HEAP_SIZE,
* Given an ID, get the name of an identified entity.
*/
def nameOf(id: Int): String = globalBundle.allNs(id).name.get
/**
* Execute. This is the external pusher of the execution.
*/
def execute(): Unit = {
threadStackManager.execute()
}
}
\ No newline at end of file
......@@ -780,6 +780,8 @@ class InterpreterThread(val id: Int, initialStack: InterpreterStack, val mutator
val argBoxes = argList.map(boxOf)
val retBox = boxOf(i)
microVM.threadStackManager.threadCallingNative = Some(this)
microVM.nativeCallHelper.callNative(sig, addr, argBoxes, retBox)
continueNormally()
......
......@@ -12,6 +12,10 @@ object ThreadStackManager {
val logger = Logger(LoggerFactory.getLogger(getClass.getName))
}
/**
* The manager of all Mu threads and stacks. Also responsible for the actual execution of Mu IR code, i.e. as the "boss"
* of all InterpreterThread instances.
*/
class ThreadStackManager(implicit microVM: MicroVM) {
import ThreadStackManager._
......@@ -22,7 +26,7 @@ class ThreadStackManager(implicit microVM: MicroVM) {
def getStackByID(id: Int): Option[InterpreterStack] = stackRegistry.get(id)
def getThreadByID(id: Int): Option[InterpreterThread] = threadRegistry.get(id)
def iterateAllLiveStacks: Iterable[InterpreterStack] = stackRegistry.values.filter(_.state != StackState.Dead)
def iterateAllLiveThreads: Iterable[InterpreterThread] = threadRegistry.values.filter(_.isRunning)
......@@ -37,6 +41,11 @@ class ThreadStackManager(implicit microVM: MicroVM) {
val futexManager = new FutexManager
/**
* The current Mu thread that is calling a native function via CCALL.
*/
var threadCallingNative: Option[InterpreterThread] = None
/**
* Create a new stack with function and args as the stack-bottom function and its arguments.
* <p>
......@@ -64,60 +73,62 @@ class ThreadStackManager(implicit microVM: MicroVM) {
thr
}
def joinAll() {
/**
* Execute one instruction in each currently executable thread.
*/
def roundRobin(): Boolean = {
var someRunning: Boolean = false
var someWaiting: Boolean = false
futexManager.futexWakeTimeout()
someRunning = false
someWaiting = false
val curThreads = threadRegistry.values.toList
for (thr2 <- curThreads) {
if (thr2.isRunning)
if (thr2.isFutexWaiting) {
someWaiting = thr2.isFutexWaiting || someWaiting
} else {
thr2.step()
someRunning = thr2.isRunning || someRunning
}
}
var continue: Boolean = false
do {
futexManager.futexWakeTimeout()
someRunning = false
someWaiting = false
val curThreads = threadRegistry.values.toList
for (thr2 <- curThreads) {
if (thr2.isRunning)
if (thr2.isFutexWaiting) {
someWaiting = thr2.isFutexWaiting || someWaiting
} else {
thr2.step()
someRunning = thr2.isRunning || someRunning
val shouldContinue = if (someRunning) {
true
} else {
if (someWaiting) {
futexManager.nextWakeup match {
case Some(nextWakeup) => {
val now = System.currentTimeMillis() * 1000000L
val sleep = nextWakeup - now
val sleepMillis = sleep / 1000000L
val sleepNanos = sleep % 1000000L
logger.debug("Waiting for futex. Now: %d, next wake up: %d, sleep: %d".format(now, nextWakeup, sleep))
Thread.sleep(sleepMillis, sleepNanos.toInt)
true
}
}
continue = if (someRunning) {
true
} else {
if (someWaiting) {
futexManager.nextWakeup match {
case Some(nextWakeup) => {
val now = System.currentTimeMillis() * 1000000L
val sleep = nextWakeup - now
val sleepMillis = sleep / 1000000L
val sleepNanos = sleep % 1000000L
logger.debug("Waiting for futex. Now: %d, next wake up: %d, sleep: %d".format(now, nextWakeup, sleep))
Thread.sleep(sleepMillis, sleepNanos.toInt)
true
}
case None => {
logger.error("No threads are running. No threads are waiting for futex with timer. This is a deadlock situation.")
false
}
case None => {
logger.error("No threads are running. No threads are waiting for futex with timer. This is a deadlock situation.")
false
}
} else {
false
}
} else {
false
}
} while (continue)
}
shouldContinue
}
def joinThread(thr: InterpreterThread) {
while (thr.isRunning) {
val curThreads = threadRegistry.values.toList
for (thr2 <- curThreads) {
thr2.step()
}
}
/**
* Execute until all threads stopped.
*/
def execute() {
var shouldContinue: Boolean = false
do {
shouldContinue = roundRobin()
} while (shouldContinue)
}
}
......@@ -11,13 +11,17 @@ import uvm.refimpl.itpr._
import uvm.refimpl.itpr.ValueBox
import uvm.refimpl.mem.TypeSizes
import uvm.refimpl.mem.TypeSizes.Word
import uvm.{Function => MFunc}
import uvm.{ Function => MFunc }
import uvm.types._
import uvm.types.{ Type => MType }
import uvm.utils.LazyPool
import uvm.utils.HexDump
import scala.collection.mutable.HashMap
import com.kenai.jffi.Closure
import com.kenai.jffi.ClosureManager
import com.kenai.jffi.CallingConvention
import uvm.refimpl.UvmRuntimeException
import uvm.refimpl.MicroVM
object NativeCallHelper {
val logger = Logger(LoggerFactory.getLogger(getClass.getName))
......@@ -26,9 +30,9 @@ object NativeCallHelper {
/**
* Helps calling native functions and supports callbacks from native. Based on JFFI.
*/
class NativeCallHelper {
class NativeCallHelper(implicit microVM: MicroVM) {
import NativeCallHelper._
/** A mapping of Mu types to JFFI types. Cached for struct types. */
val jffiTypePool: LazyPool[MType, JType] = LazyPool {
case TypeVoid() => JType.VOID
......@@ -56,7 +60,7 @@ class NativeCallHelper {
new JFunction(funcAddr, jRetTy, jParamTypes: _*)
}
}
/**
* A dynamically-exposed Mu function. A Mu function may be exposed many times. Each DynExpFunc corresponds to one
* such callable instance.
......@@ -64,18 +68,68 @@ class NativeCallHelper {
* A ".expose" definition will permanently create an instance.
* <p>
* The "@uvm.native.expose" instruction will also create one such instance. Such instances can be removed later by
* "@uvm.native.unexpose". The equivalent API calls do the same.
* "@uvm.native.unexpose". The equivalent API calls do the same.
*/
class DynExpFunc(val muFunc: MFunc, val cookie: Long, val closure: MuCallbackClosure, val closureHandle: Closure.Handle)
/**
* Map each address of closure handle to the DynExpFunc record so that the closure handle can be disposed.
*/
class DynExpFunc(val muFunc: MFunc, val closureHandle: Closure.Handle) {
val addr = closureHandle.getAddress()
}
val exposedFuncs = new HashMap[Word, DynExpFunc]()
def exposeFunc(muFunc: MFunc, cookie: Word): Word = {
???
/** Call a native (C) function. */
def callNative(sig: FuncSig, func: Word, args: Seq[ValueBox], retBox: ValueBox): Unit = {
val jFunc = jffiFuncPool((sig, func))
val hib = new HeapInvocationBuffer(jFunc)
for ((mty, vb) <- (sig.paramTy zip args)) {
putArg(hib, mty, vb)
}
val inv = Invoker.getInstance
sig.retTy match {
case TypeVoid() => {
inv.invokeLong(jFunc, hib)
}
case TypeInt(8) => {
val rv = inv.invokeInt(jFunc, hib).toByte
retBox.asInstanceOf[BoxInt].value = OpHelper.trunc(BigInt(rv), 8)
}
case TypeInt(16) => {
val rv = inv.invokeInt(jFunc, hib).toShort
retBox.asInstanceOf[BoxInt].value = OpHelper.trunc(BigInt(rv), 16)
}
case TypeInt(32) => {
val rv = inv.invokeInt(jFunc, hib)
retBox.asInstanceOf[BoxInt].value = OpHelper.trunc(BigInt(rv), 32)
}
case TypeInt(64) => {
val rv = inv.invokeLong(jFunc, hib)
retBox.asInstanceOf[BoxInt].value = OpHelper.trunc(BigInt(rv), 64)
}
case TypeFloat() => {
val rv = inv.invokeFloat(jFunc, hib)
retBox.asInstanceOf[BoxFloat].value = rv
}
case TypeDouble() => {
val rv = inv.invokeDouble(jFunc, hib)
retBox.asInstanceOf[BoxDouble].value = rv
}
case TypeStruct(flds) => {
val rv = inv.invokeStruct(jFunc, hib)
val buf = ByteBuffer.wrap(rv).order(ByteOrder.LITTLE_ENDIAN)
logger.debug("Hexdump:\n" + HexDump.dumpByteBuffer(buf))
getArgFromBuf(buf, 0, sig.retTy, retBox)
}
case _: AbstractPointerType => {
val rv = inv.invokeAddress(jFunc, hib)
retBox.asInstanceOf[BoxPointer].addr = rv
}
}
}
private def putArgToBuf(buf: ByteBuffer, off: Int, mty: MType, vb: ValueBox): Unit = {
mty match {
case TypeInt(8) => buf.put(off, vb.asInstanceOf[BoxInt].value.toByte)
......@@ -135,58 +189,40 @@ class NativeCallHelper {
case _: AbstractPointerType => vb.asInstanceOf[BoxPointer].addr = buf.getLong(off)
}
}
/** Call a native (C) function. */
def callNative(sig: FuncSig, func: Word, args: Seq[ValueBox], retBox: ValueBox): Unit = {
val jFunc = jffiFuncPool((sig, func))
val hib = new HeapInvocationBuffer(jFunc)
for ((mty, vb) <- (sig.paramTy zip args)) {
putArg(hib, mty, vb)
/**
* Expose a Mu function.
*
* @return the address of the exposed function (i.e. of the closure handle)
*/
def exposeFunc(muFunc: MFunc, cookie: Long): Word = {
val sig = muFunc.sig
val jParamTypes = sig.paramTy.map(jffiTypePool.apply)
val jRetTy = jffiTypePool(sig.retTy)
val clos = new MuCallbackClosure(muFunc, cookie)
val handle = NativeSupport.cloaureManager.newClosure(clos, jRetTy, jParamTypes.toArray, CallingConvention.DEFAULT)
val addr = handle.getAddress
val dynExpFunc = new DynExpFunc(muFunc, cookie, clos, handle)
exposedFuncs(addr) = dynExpFunc
addr
}
def unexposeFunc(addr: Word): Unit = {
val dynExpFunc = exposedFuncs.remove(addr).getOrElse {
throw new UvmRuntimeException("Attempt to unexpose function %d (0x%x) which has not been exposed.".format(addr, addr))
}
val inv = Invoker.getInstance
sig.retTy match {
case TypeVoid() => {
inv.invokeLong(jFunc, hib)
}
case TypeInt(8) => {
val rv = inv.invokeInt(jFunc, hib).toByte
retBox.asInstanceOf[BoxInt].value = OpHelper.trunc(BigInt(rv), 8)
}
case TypeInt(16) => {
val rv = inv.invokeInt(jFunc, hib).toShort
retBox.asInstanceOf[BoxInt].value = OpHelper.trunc(BigInt(rv), 16)
}
case TypeInt(32) => {
val rv = inv.invokeInt(jFunc, hib)
retBox.asInstanceOf[BoxInt].value = OpHelper.trunc(BigInt(rv), 32)
}
case TypeInt(64) => {
val rv = inv.invokeLong(jFunc, hib)
retBox.asInstanceOf[BoxInt].value = OpHelper.trunc(BigInt(rv), 64)
}
case TypeFloat() => {
val rv = inv.invokeFloat(jFunc, hib)
retBox.asInstanceOf[BoxFloat].value = rv
}
case TypeDouble() => {
val rv = inv.invokeDouble(jFunc, hib)
retBox.asInstanceOf[BoxDouble].value = rv
}
case TypeStruct(flds) => {
val rv = inv.invokeStruct(jFunc, hib)
val buf = ByteBuffer.wrap(rv).order(ByteOrder.LITTLE_ENDIAN)
logger.debug("Hexdump:\n" + HexDump.dumpByteBuffer(buf))
getArgFromBuf(buf, 0, sig.retTy, retBox)
}
case _: AbstractPointerType => {
val rv = inv.invokeAddress(jFunc, hib)
retBox.asInstanceOf[BoxPointer].addr = rv
}
dynExpFunc.closureHandle.dispose()
}
/** Handles calling back from C */
class MuCallbackClosure(val muFunc: MFunc, val cookie: Long) extends Closure {
def invoke(buf: Closure.Buffer): Unit = {
???
}
}
}
\ No newline at end of file
package uvm.refimpl.nat
import jnr.ffi.{ Runtime, Memory, Pointer }
import com.kenai.jffi.ClosureManager
/**
* Holder of JNR-specific resources.
......@@ -8,4 +9,5 @@ import jnr.ffi.{ Runtime, Memory, Pointer }
object NativeSupport {
val jnrRuntime = Runtime.getSystemRuntime
val theMemory = Pointer.wrap(jnrRuntime, 0L)
val cloaureManager = ClosureManager.getInstance()
}
\ No newline at end of file
......@@ -36,7 +36,7 @@ object FactorialFromRPython extends App {
val sta = ca.newStack(m, Seq())
val thr = ca.newThread(sta)
microVM.threadStackManager.joinAll() // run until all threads stop
microVM.execute() // run until all threads stop
ca.close()
}
\ No newline at end of file
......@@ -68,7 +68,7 @@ abstract class UvmBundleTesterBase extends FlatSpec with Matchers {
microVM.trapManager.trapHandler = new MockTrapHandler(handler)
val hStack = ca.newStack(func, args)
val hThread = ca.newThread(hStack)
microVM.threadStackManager.joinAll()
microVM.execute()
}
implicit class MagicalBox(vb: ValueBox) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment