Commit 3f6a1cd1 authored by Kunshan Wang's avatar Kunshan Wang

WIP: native stack keeper

parent 86493917
......@@ -780,9 +780,11 @@ 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)
???
//
// microVM.threadStackManager.threadCallingNative = Some(this)
//
// microVM.nativeCallHelper.callNative(sig, addr, argBoxes, retBox)
continueNormally()
}
......
......@@ -77,10 +77,12 @@ class NativeCallHelper(implicit microVM: MicroVM) {
*/
val exposedFuncs = new HashMap[Word, DynExpFunc]()
/** Call a native (C) function. */
def callNative(sig: FuncSig, func: Word, args: Seq[ValueBox], retBox: ValueBox): Unit = {
/**
* Call a native function. Must be called by a NativeStackKeeper.Slave thread.
*/
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)) {
......
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
/**
* 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] {
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 {
case class CallBack(sig: FuncSig, func: MFunc, args: Seq[ValueBox], retBox: ValueBox) extends NativeCallResult
case class Return() extends NativeCallResult
}
/**
* 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] {
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) {
receive() 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")
}
case ToSlave.Stop() => {
return
}
}
}
}
def onCallBack(): Unit = {
}
}
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)
package uvm.refimpl.nat
import com.kenai.jffi.Library
object NativeLibraryTestHelper {
def loadTestLibrary(name: String): Library = {
val relPath = s"tests/c-snippets/${name}.so"
if (!new java.io.File(relPath).isFile()) {
throw new RuntimeException(s"Need to compile the ${name}.so library. cd into tests/c-snippets and invoke 'make'.")
}
Library.openLibrary(relPath, Library.NOW)
}
}
\ No newline at end of file
package uvm.refimpl.nat
import org.scalatest.FlatSpec
import org.scalatest.Matchers
import com.kenai.jffi.CallingConvention
import com.kenai.jffi.Closure
import com.kenai.jffi.Closure.Buffer
import com.kenai.jffi.ClosureManager
import com.kenai.jffi.{ Type => JType }
import uvm.FuncSig
import uvm.refimpl.itpr.BoxDouble
import uvm.refimpl.itpr.BoxInt
import uvm.refimpl.itpr.BoxPointer
import uvm.types.TypeDouble
import uvm.types.TypeFuncPtr
import uvm.types.TypeInt
class NativeStackKeeperTest extends FlatSpec with Matchers {
behavior of "NativeStackKeeper"
implicit val nh = new NativeHelper()
val lib = NativeLibraryTestHelper.loadTestLibrary("callbacktest")
def autoClose[T](nsk: NativeStackKeeper)(f: => T) = {
try {
f
} finally {
nsk.close()
}
}
it should "stop the slave thread when closed" in {
val nsk = new NativeStackKeeper()
nsk.close()
nsk.slaveThread.isAlive() shouldBe false
}
it should "call non-callback native functions" in {
val addr = lib.getSymbolAddress("add")
val nsk = new NativeStackKeeper()
autoClose(nsk) {
val i32 = TypeInt(32)
val sig = FuncSig(i32, Seq(i32, i32))
val box1 = BoxInt(3)
val box2 = BoxInt(4)
val boxRv = BoxInt(-1)
nsk.callNative(sig, addr, Seq(box1, box2), boxRv)
boxRv.value shouldBe 7
}
}
it should "handle one-level callback" in {
val addr = lib.getSymbolAddress("one_level")
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)
(value == 3.0 || value == 4.0) shouldBe true
val rv = value * value
buf.setDoubleReturn(rv)
}
}
val closHandle = ClosureManager.getInstance.newClosure(clos, JType.DOUBLE, Array(JType.DOUBLE, JType.POINTER), CallingConvention.DEFAULT)
val b1 = BoxDouble(3.0)
val b2 = BoxPointer(0)
val br = BoxInt(-1)
nsk.callNative(sig, addr, Seq(b1, b2), br)
br.value shouldBe 7
}
}
}
\ No newline at end of file
all: structtest.so
all: structtest.so callbacktest.so
structtest.so: structtest.c
cc -fPIC -shared -o structtest.so structtest.c
callbacktest.so: callbacktest.c
cc -fPIC -shared -o callbacktest.so callbacktest.c
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
double (*DoubleToDouble)(double value);
double one_level(double v1, DoubleToDouble cb) {
double v2, v3;
v2 = cb(v1+1.0);
v3 = cb(v1+2.0);
return v2 + v3;
}
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