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 { ...@@ -76,7 +76,7 @@ class NativeCallHelper {
* Map each address of closure handle to the DynExpFunc record so that the closure handle can be disposed. * Map each address of closure handle to the DynExpFunc record so that the closure handle can be disposed.
*/ */
val exposedFuncs = new HashMap[Word, DynExpFunc]() val exposedFuncs = new HashMap[Word, DynExpFunc]()
/** /**
* The current NativeStackKeeper instance that makes the native call. * The current NativeStackKeeper instance that makes the native call.
* <p> * <p>
...@@ -93,14 +93,15 @@ class NativeCallHelper { ...@@ -93,14 +93,15 @@ class NativeCallHelper {
*/ */
def callNative(nsk: NativeStackKeeper, sig: FuncSig, func: Word, args: Seq[ValueBox], retBox: ValueBox): Unit = { def callNative(nsk: NativeStackKeeper, sig: FuncSig, func: Word, args: Seq[ValueBox], retBox: ValueBox): Unit = {
val jFunc = jffiFuncPool((sig, func)) val jFunc = jffiFuncPool((sig, func))
val hib = new HeapInvocationBuffer(jFunc) val hib = new HeapInvocationBuffer(jFunc)
for ((mty, vb) <- (sig.paramTy zip args)) { for ((mty, vb) <- (sig.paramTy zip args)) {
putArg(hib, mty, vb) putArg(hib, mty, vb)
} }
currentNativeStackKeeper.set(nsk) currentNativeStackKeeper.set(nsk)
assert(currentNativeStackKeeper.get() == nsk)
val inv = Invoker.getInstance val inv = Invoker.getInstance
...@@ -143,6 +144,7 @@ class NativeCallHelper { ...@@ -143,6 +144,7 @@ class NativeCallHelper {
retBox.asInstanceOf[BoxPointer].addr = rv retBox.asInstanceOf[BoxPointer].addr = rv
} }
} }
currentNativeStackKeeper.remove()
} }
private def putArgToBuf(buf: ByteBuffer, off: Int, mty: MType, vb: ValueBox): Unit = { private def putArgToBuf(buf: ByteBuffer, off: Int, mty: MType, vb: ValueBox): Unit = {
...@@ -207,37 +209,109 @@ class NativeCallHelper { ...@@ -207,37 +209,109 @@ class NativeCallHelper {
/** /**
* Expose a Mu function. * Expose a Mu function.
* *
* @return the address of the exposed function (i.e. of the closure handle) * @return the address of the exposed function (i.e. of the closure handle)
*/ */
def exposeFunc(muFunc: MFunc, cookie: Long): Word = { def exposeFunc(muFunc: MFunc, cookie: Long): Word = {
val sig = muFunc.sig val sig = muFunc.sig
val jParamTypes = sig.paramTy.map(jffiTypePool.apply) val jParamTypes = sig.paramTy.map(jffiTypePool.apply)
val jRetTy = jffiTypePool(sig.retTy) val jRetTy = jffiTypePool(sig.retTy)
val clos = new MuCallbackClosure(muFunc, cookie) 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 addr = handle.getAddress
val dynExpFunc = new DynExpFunc(muFunc, cookie, clos, handle) val dynExpFunc = new DynExpFunc(muFunc, cookie, clos, handle)
exposedFuncs(addr) = dynExpFunc exposedFuncs(addr) = dynExpFunc
addr addr
} }
def unexposeFunc(addr: Word): Unit = { def unexposeFunc(addr: Word): Unit = {
val dynExpFunc = exposedFuncs.remove(addr).getOrElse { val dynExpFunc = exposedFuncs.remove(addr).getOrElse {
throw new UvmRuntimeException("Attempt to unexpose function %d (0x%x) which has not been exposed.".format(addr, addr)) 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 */ /** Handles calling back from C */
class MuCallbackClosure(val muFunc: MFunc, val cookie: Long) extends Closure { class MuCallbackClosure(val muFunc: MFunc, val cookie: Long) extends Closure {
def invoke(buf: Closure.Buffer): Unit = { 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 ...@@ -9,5 +9,5 @@ import com.kenai.jffi.ClosureManager
object NativeSupport { object NativeSupport {
val jnrRuntime = Runtime.getSystemRuntime val jnrRuntime = Runtime.getSystemRuntime
val theMemory = Pointer.wrap(jnrRuntime, 0L) 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 ...@@ -5,12 +5,19 @@ import uvm.refimpl.itpr.ValueBox
import uvm.refimpl.mem.TypeSizes.Word import uvm.refimpl.mem.TypeSizes.Word
import uvm.{ Function => MFunc } import uvm.{ Function => MFunc }
import uvm.refimpl.UvmRuntimeException 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 * An Agent is something that has a mailbox. It allows Erlang-like usage pattern. But it does not automatically
* provide a thread. * provide a thread.
*/ */
trait PoorManAgent[T] { trait PoorManAgent[T] {
import PoorManAgent._
val inBox = new java.util.concurrent.ArrayBlockingQueue[T](1) val inBox = new java.util.concurrent.ArrayBlockingQueue[T](1)
def send(msg: T) { def send(msg: T) {
...@@ -24,10 +31,14 @@ trait PoorManAgent[T] { ...@@ -24,10 +31,14 @@ trait PoorManAgent[T] {
abstract class NativeCallResult abstract class NativeCallResult
object 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 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. * This class (the master) contains a slave: a Java thread which contains many native frames.
* <p> * <p>
...@@ -49,6 +60,7 @@ object NativeCallResult { ...@@ -49,6 +60,7 @@ object NativeCallResult {
* This allows the interpreter thread to swap between multiple Mu stacks (including native frames). * This allows the interpreter thread to swap between multiple Mu stacks (including native frames).
*/ */
class NativeStackKeeper(implicit nativeCallHelper: NativeCallHelper) extends PoorManAgent[NativeCallResult] { class NativeStackKeeper(implicit nativeCallHelper: NativeCallHelper) extends PoorManAgent[NativeCallResult] {
import NativeStackKeeper._
abstract class ToSlave abstract class ToSlave
object ToSlave { object ToSlave {
...@@ -63,25 +75,72 @@ class NativeStackKeeper(implicit nativeCallHelper: NativeCallHelper) extends Poo ...@@ -63,25 +75,72 @@ class NativeStackKeeper(implicit nativeCallHelper: NativeCallHelper) extends Poo
def run(): Unit = { def run(): Unit = {
while (true) { 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) => { case ToSlave.CallNative(sig, func, args, retBox) => {
nativeCallHelper.callNative(master, sig, func, args, retBox) nativeCallHelper.callNative(master, sig, func, args, retBox)
master.send(NativeCallResult.Return()) master.send(NativeCallResult.Return())
} }
case ToSlave.ReturnToCallBack() => { 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() => { case ToSlave.Stop() => {
logger.debug("Received Stop msg. Stopping slave...")
return 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 ...@@ -7,6 +7,7 @@ import com.kenai.jffi.Closure
import com.kenai.jffi.Closure.Buffer import com.kenai.jffi.Closure.Buffer
import com.kenai.jffi.ClosureManager import com.kenai.jffi.ClosureManager
import com.kenai.jffi.{ Type => JType } import com.kenai.jffi.{ Type => JType }
import uvm.{ Function => MFunc }
import uvm.FuncSig import uvm.FuncSig
import uvm.refimpl.itpr.BoxDouble import uvm.refimpl.itpr.BoxDouble
import uvm.refimpl.itpr.BoxInt import uvm.refimpl.itpr.BoxInt
...@@ -15,14 +16,16 @@ import uvm.types.TypeDouble ...@@ -15,14 +16,16 @@ import uvm.types.TypeDouble
import uvm.types.TypeFuncPtr import uvm.types.TypeFuncPtr
import uvm.types.TypeInt import uvm.types.TypeInt
import uvm.refimpl.MicroVM 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" behavior of "NativeStackKeeper"
val lib = NativeLibraryTestHelper.loadTestLibrary("callbacktest") val lib = NativeLibraryTestHelper.loadTestLibrary("callbacktest")
implicit val nch = new NativeCallHelper() implicit val nch = new NativeCallHelper()
def autoClose[T](nsk: NativeStackKeeper)(f: => T) = { def autoClose[T](nsk: NativeStackKeeper)(f: => T) = {
try { try {
f f
...@@ -53,7 +56,9 @@ class NativeStackKeeperTest extends FlatSpec with Matchers { ...@@ -53,7 +56,9 @@ class NativeStackKeeperTest extends FlatSpec with Matchers {
val box2 = BoxInt(4) val box2 = BoxInt(4)
val boxRv = BoxInt(-1) 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 boxRv.value shouldBe 7
} }
...@@ -61,7 +66,7 @@ class NativeStackKeeperTest extends FlatSpec with Matchers { ...@@ -61,7 +66,7 @@ class NativeStackKeeperTest extends FlatSpec with Matchers {
it should "handle one-level callback" in { it should "handle one-level callback" in {
val addr = lib.getSymbolAddress("one_level") val addr = lib.getSymbolAddress("one_level")
addr should not be 0 addr should not be 0
val nsk = new NativeStackKeeper() val nsk = new NativeStackKeeper()
...@@ -72,7 +77,71 @@ class NativeStackKeeperTest extends FlatSpec with Matchers { ...@@ -72,7 +77,71 @@ class NativeStackKeeperTest extends FlatSpec with Matchers {
val dtdSig = FuncSig(d, Seq(d)) val dtdSig = FuncSig(d, Seq(d))
val dtd = TypeFuncPtr(dtdSig) val dtd = TypeFuncPtr(dtdSig)
val sig = FuncSig(d, Seq(d, dtd)) 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() { val clos = new Closure() {
def invoke(buf: Buffer): Unit = { def invoke(buf: Buffer): Unit = {
val value = buf.getDouble(0) val value = buf.getDouble(0)
...@@ -81,11 +150,11 @@ class NativeStackKeeperTest extends FlatSpec with Matchers { ...@@ -81,11 +150,11 @@ class NativeStackKeeperTest extends FlatSpec with Matchers {
buf.setDoubleReturn(rv) buf.setDoubleReturn(rv)
} }
} }
val closHandle = ClosureManager.getInstance.newClosure(clos, JType.DOUBLE, Array(JType.DOUBLE, JType.POINTER), CallingConvention.DEFAULT) val closHandle = ClosureManager.getInstance.newClosure(clos, JType.DOUBLE, Array(JType.DOUBLE, JType.POINTER), CallingConvention.DEFAULT)
closHandle.getAddress should not be 0 closHandle.getAddress should not be 0
val b1 = BoxDouble(3.0) val b1 = BoxDouble(3.0)
val b2 = BoxPointer(closHandle.getAddress) val b2 = BoxPointer(closHandle.getAddress)
val br = BoxDouble(-1.0) val br = BoxDouble(-1.0)
...@@ -93,7 +162,7 @@ class NativeStackKeeperTest extends FlatSpec with Matchers { ...@@ -93,7 +162,7 @@ class NativeStackKeeperTest extends FlatSpec with Matchers {
nsk.callNative(sig, addr, Seq(b1, b2), br) nsk.callNative(sig, addr, Seq(b1, b2), br)
closHandle.dispose() closHandle.dispose()
br.value shouldBe 25.0 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