Commit f3796d66 authored by Kunshan Wang's avatar Kunshan Wang

WIP: The native stack keeper handles callbacks, but the interpreter

still does not work yet.
parent e7b1b1df
......@@ -76,7 +76,7 @@ class NativeCallHelper {
* Map each address of closure handle to the DynExpFunc record so that the closure handle can be disposed.
*/
val exposedFuncs = new HashMap[Word, DynExpFunc]()
/**
* The current NativeStackKeeper instance that makes the native call.
* <p>
......@@ -93,14 +93,15 @@ class NativeCallHelper {
*/
def callNative(nsk: NativeStackKeeper, 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)
}
currentNativeStackKeeper.set(nsk)
assert(currentNativeStackKeeper.get() == nsk)
val inv = Invoker.getInstance
......@@ -143,6 +144,7 @@ class NativeCallHelper {
retBox.asInstanceOf[BoxPointer].addr = rv
}
}
currentNativeStackKeeper.remove()
}
private def putArgToBuf(buf: ByteBuffer, off: Int, mty: MType, vb: ValueBox): Unit = {
......@@ -207,37 +209,109 @@ class NativeCallHelper {
/**
* 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 handle = NativeSupport.closureManager.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))
}
dynExpFunc.closureHandle.dispose()
dynExpFunc.closureHandle.dispose()
}
/** Handles calling back from C */
class MuCallbackClosure(val muFunc: MFunc, val cookie: Long) extends Closure {
def invoke(buf: Closure.Buffer): Unit = {
???
val nsk = currentNativeStackKeeper.get()
logger.debug("nsk = %s, currentThread = %s".format(nsk, Thread.currentThread()))
assert(nsk != null)
currentNativeStackKeeper.remove()
if (nsk == null) {
throw new UvmNativeCallException(s"Native calls Mu function ${muFunc.repr} with cookie ${cookie}, but Mu did not call native.")
}
val sig = muFunc.sig
val paramBoxes = for ((paramTy, i) <- sig.paramTy.zipWithIndex) yield {
makeBoxForParam(buf, paramTy, i)
}
val rvBox = ValueBox.makeBoxForType(sig.retTy)
nsk.slave.onCallBack(muFunc, cookie, paramBoxes, rvBox)
logger.debug("Back from onCallBack. Returning to native...")
putRvToBuf(buf, sig.retTy, rvBox)
currentNativeStackKeeper.set(nsk)
}
def makeBoxForParam(buf: Closure.Buffer, ty: MType, index: Int): ValueBox = ty match {
case TypeInt(8) => BoxInt(OpHelper.trunc(buf.getByte(index), 8))
case TypeInt(16) => BoxInt(OpHelper.trunc(buf.getShort(index), 16))
case TypeInt(32) => BoxInt(OpHelper.trunc(buf.getInt(index), 32))
case TypeInt(64) => BoxInt(OpHelper.trunc(buf.getLong(index), 64))
case TypeFloat() => BoxFloat(buf.getFloat(index))
case TypeDouble() => BoxDouble(buf.getDouble(index))
case s @ TypeStruct(flds) => {
val mem = buf.getStruct(index)
makeBoxFromMemory(ty, mem)
}
case _: AbstractPointerType => BoxPointer(buf.getAddress(index))
}
def makeBoxFromMemory(ty: MType, addr: Word): ValueBox = ty match {
case TypeInt(8) => BoxInt(OpHelper.trunc(NativeSupport.theMemory.getByte(addr), 8))
case TypeInt(16) => BoxInt(OpHelper.trunc(NativeSupport.theMemory.getShort(addr), 16))
case TypeInt(32) => BoxInt(OpHelper.trunc(NativeSupport.theMemory.getInt(addr), 32))
case TypeInt(64) => BoxInt(OpHelper.trunc(NativeSupport.theMemory.getLong(addr), 64))
case TypeFloat() => BoxFloat(NativeSupport.theMemory.getFloat(addr))
case TypeDouble() => BoxDouble(NativeSupport.theMemory.getDouble(addr))
case s @ TypeStruct(flds) => {
val fldvbs = for ((fty, i) <- flds.zipWithIndex) yield {
val off = TypeSizes.fieldOffsetOf(s, i)
makeBoxFromMemory(fty, addr + off.toInt)
}
BoxStruct(fldvbs)
}
case _: AbstractPointerType => BoxPointer(NativeSupport.theMemory.getAddress(addr))
}
def putRvToBuf(buf: Closure.Buffer, ty: MType, vb: ValueBox): Unit = ty match {
case TypeInt(8) => buf.setByteReturn(vb.asInstanceOf[BoxInt].value.toByte)
case TypeInt(16) => buf.setShortReturn(vb.asInstanceOf[BoxInt].value.toShort)
case TypeInt(32) => buf.setIntReturn(vb.asInstanceOf[BoxInt].value.toInt)
case TypeInt(64) => buf.setLongReturn(vb.asInstanceOf[BoxInt].value.toLong)
case TypeFloat() => buf.setFloatReturn(vb.asInstanceOf[BoxFloat].value)
case TypeDouble() => buf.setDoubleReturn(vb.asInstanceOf[BoxDouble].value)
case s @ TypeStruct(flds) => {
// Always allocate more space so that C may access the word that contains the byte instead of just the byte.
val bbuf = ByteBuffer.allocate(TypeSizes.alignUp(TypeSizes.sizeOf(ty), FORCE_ALIGN_UP).intValue())
bbuf.order(ByteOrder.LITTLE_ENDIAN)
putArgToBuf(bbuf, 0, ty, vb)
logger.debug("Hexdump:\n" + HexDump.dumpByteBuffer(bbuf))
buf.setStructReturn(bbuf.array(), bbuf.arrayOffset())
}
case _: AbstractPointerType => buf.setAddressReturn(vb.asInstanceOf[BoxPointer].addr)
}
}
}
\ No newline at end of file
......@@ -9,5 +9,5 @@ import com.kenai.jffi.ClosureManager
object NativeSupport {
val jnrRuntime = Runtime.getSystemRuntime
val theMemory = Pointer.wrap(jnrRuntime, 0L)
val cloaureManager = ClosureManager.getInstance()
val closureManager = ClosureManager.getInstance()
}
\ No newline at end of file
......@@ -5,12 +5,19 @@ import uvm.refimpl.itpr.ValueBox
import uvm.refimpl.mem.TypeSizes.Word
import uvm.{ Function => MFunc }
import uvm.refimpl.UvmRuntimeException
import com.typesafe.scalalogging.Logger
import org.slf4j.LoggerFactory
object PoorManAgent {
val logger = Logger(LoggerFactory.getLogger(getClass.getName))
}
/**
* 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] {
import PoorManAgent._
val inBox = new java.util.concurrent.ArrayBlockingQueue[T](1)
def send(msg: T) {
......@@ -24,10 +31,14 @@ trait PoorManAgent[T] {
abstract class NativeCallResult
object NativeCallResult {
case class CallBack(sig: FuncSig, func: MFunc, args: Seq[ValueBox], retBox: ValueBox) extends NativeCallResult
case class CallBack(func: MFunc, cookie: Long, args: Seq[ValueBox], retBox: ValueBox) extends NativeCallResult
case class Return() extends NativeCallResult
}
object NativeStackKeeper {
val logger = Logger(LoggerFactory.getLogger(getClass.getName))
}
/**
* This class (the master) contains a slave: a Java thread which contains many native frames.
* <p>
......@@ -49,6 +60,7 @@ object NativeCallResult {
* This allows the interpreter thread to swap between multiple Mu stacks (including native frames).
*/
class NativeStackKeeper(implicit nativeCallHelper: NativeCallHelper) extends PoorManAgent[NativeCallResult] {
import NativeStackKeeper._
abstract class ToSlave
object ToSlave {
......@@ -63,25 +75,72 @@ class NativeStackKeeper(implicit nativeCallHelper: NativeCallHelper) extends Poo
def run(): Unit = {
while (true) {
receive() match {
val received = try {
receive()
} catch {
case e: InterruptedException =>
logger.debug("Native stack keeper slave thread interrupted. No native frames. Stopping slave...")
return
}
received match {
case ToSlave.CallNative(sig, func, args, retBox) => {
nativeCallHelper.callNative(master, sig, func, args, retBox)
master.send(NativeCallResult.Return())
}
case ToSlave.ReturnToCallBack() => {
throw new UvmNativeCallException("Attempt to return to callback functions when there is no native function calling back")
throw new UvmNativeCallException("Attempt to return to native function, but no native function called back before")
}
case ToSlave.Stop() => {
logger.debug("Received Stop msg. Stopping slave...")
return
}
}
}
}
def onCallBack(): Unit = {
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
}
logger.debug("returning...")
}
}
......
......@@ -7,6 +7,7 @@ import com.kenai.jffi.Closure
import com.kenai.jffi.Closure.Buffer
import com.kenai.jffi.ClosureManager
import com.kenai.jffi.{ Type => JType }
import uvm.{ Function => MFunc }
import uvm.FuncSig
import uvm.refimpl.itpr.BoxDouble
import uvm.refimpl.itpr.BoxInt
......@@ -15,14 +16,16 @@ import uvm.types.TypeDouble
import uvm.types.TypeFuncPtr
import uvm.types.TypeInt
import uvm.refimpl.MicroVM
import uvm.ir.textinput.ExtraMatchers
import uvm.refimpl.itpr.BoxDouble
class NativeStackKeeperTest extends FlatSpec with Matchers {
class NativeStackKeeperTest extends FlatSpec with Matchers with ExtraMatchers {
behavior of "NativeStackKeeper"
val lib = NativeLibraryTestHelper.loadTestLibrary("callbacktest")
implicit val nch = new NativeCallHelper()
def autoClose[T](nsk: NativeStackKeeper)(f: => T) = {
try {
f
......@@ -53,7 +56,9 @@ class NativeStackKeeperTest extends FlatSpec with Matchers {
val box2 = BoxInt(4)
val boxRv = BoxInt(-1)
nsk.callNative(sig, addr, Seq(box1, box2), boxRv)
val result = nsk.callNative(sig, addr, Seq(box1, box2), boxRv)
result shouldBe NativeCallResult.Return()
boxRv.value shouldBe 7
}
......@@ -61,7 +66,7 @@ class NativeStackKeeperTest extends FlatSpec with Matchers {
it should "handle one-level callback" in {
val addr = lib.getSymbolAddress("one_level")
addr should not be 0
val nsk = new NativeStackKeeper()
......@@ -72,7 +77,71 @@ class NativeStackKeeperTest extends FlatSpec with Matchers {
val dtdSig = FuncSig(d, Seq(d))
val dtd = TypeFuncPtr(dtdSig)
val sig = FuncSig(d, Seq(d, dtd))
val mockMuCallbackFunc = new MFunc()
mockMuCallbackFunc.sig = dtdSig
val mockClosAddr = nch.exposeFunc(mockMuCallbackFunc, 42L)
mockClosAddr should not be 0
val b1 = BoxDouble(3.0)
val b2 = BoxPointer(mockClosAddr)
val br = BoxDouble(-1.0)
val r1 = nsk.callNative(sig, addr, Seq(b1, b2), br)
println("Hello. I received r1")
r1 shouldBeA[NativeCallResult.CallBack] { its =>
its.func shouldBe mockMuCallbackFunc
its.cookie shouldBe 42
its.args.size shouldBe 1
its.args(0) shouldBeA[BoxDouble] {whose =>
whose.value shouldBe 3.0
}
its.retBox shouldBeA[BoxDouble] { b =>
b.value = 9.0
}
}
val r2 = nsk.returnToCallBack()
println("Hello. I received r2")
r2 shouldBeA[NativeCallResult.CallBack] { its =>
its.func shouldBe mockMuCallbackFunc
its.cookie shouldBe 42
its.args.size shouldBe 1
its.args(0) shouldBeA[BoxDouble] {whose =>
whose.value shouldBe 4.0
}
its.retBox shouldBeA[BoxDouble] { b =>
b.value = 16.0
}
}
val r3 = nsk.returnToCallBack()
println("Hello. I received r3")
r3 shouldBe NativeCallResult.Return()
br.value shouldBe 25.0
}
}
"The 'one_level' function" should "work on usual callback closures" in {
val addr = lib.getSymbolAddress("one_level")
addr should not be 0
val nsk = new NativeStackKeeper()
autoClose(nsk) {
val d = TypeDouble()
val dtdSig = FuncSig(d, Seq(d))
val dtd = TypeFuncPtr(dtdSig)
val sig = FuncSig(d, Seq(d, dtd))
val clos = new Closure() {
def invoke(buf: Buffer): Unit = {
val value = buf.getDouble(0)
......@@ -81,11 +150,11 @@ class NativeStackKeeperTest extends FlatSpec with Matchers {
buf.setDoubleReturn(rv)
}
}
val closHandle = ClosureManager.getInstance.newClosure(clos, JType.DOUBLE, Array(JType.DOUBLE, JType.POINTER), CallingConvention.DEFAULT)
closHandle.getAddress should not be 0
val b1 = BoxDouble(3.0)
val b2 = BoxPointer(closHandle.getAddress)
val br = BoxDouble(-1.0)
......@@ -93,7 +162,7 @@ class NativeStackKeeperTest extends FlatSpec with Matchers {
nsk.callNative(sig, addr, Seq(b1, b2), br)
closHandle.dispose()
br.value shouldBe 25.0
}
}
......
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