GitLab will continue to be upgraded from 11.4.5-ce.0 on November 25th 2019 at 4.00pm (AEDT) to 5.00pm (AEDT) due to Critical Security Patch Availability. During the update, GitLab and Mattermost services will not be available.

Commit c726df9c authored by Kunshan Wang's avatar Kunshan Wang

Tested native calls involving swapstack.

parent ac4beb8f
......@@ -239,7 +239,7 @@ class NativeCallHelper {
if (!dynExpFunc.isDynamic) {
throw new UvmRuntimeException("Attempt to unexpose a function %d (0x%x) exposed via the '.expose' top-level definition.".format(addr, addr))
}
exposedFuncs.remove(addr)
dynExpFunc.closureHandle.dispose()
......@@ -248,33 +248,44 @@ class NativeCallHelper {
/** 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()
try {
val nsk = currentNativeStackKeeper.get()
logger.debug("Called back. nsk = %s, currentThread = %s, muFunc = %s".format(nsk, Thread.currentThread(), muFunc.repr))
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.")
}
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 sig = muFunc.sig
val paramBoxes = for ((paramTy, i) <- sig.paramTy.zipWithIndex) yield {
makeBoxForParam(buf, paramTy, i)
}
val paramBoxes = for ((paramTy, i) <- sig.paramTy.zipWithIndex) yield {
makeBoxForParam(buf, paramTy, i)
}
val rvBox = ValueBox.makeBoxForType(sig.retTy)
logger.debug("Calling to Mu nsk.slave...")
val rvBox = ValueBox.makeBoxForType(sig.retTy)
nsk.slave.onCallBack(muFunc, cookie, paramBoxes, rvBox)
nsk.slave.onCallBack(muFunc, cookie, paramBoxes, rvBox)
logger.debug("Back from nsk.slave. Returning to native...")
logger.debug("Back from onCallBack. Returning to native...")
putRvToBuf(buf, sig.retTy, rvBox)
putRvToBuf(buf, sig.retTy, rvBox)
currentNativeStackKeeper.set(nsk)
} catch {
case e: Throwable =>
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).", e)
throw e
currentNativeStackKeeper.set(nsk)
}
}
def makeBoxForParam(buf: Closure.Buffer, ty: MType, index: Int): ValueBox = ty match {
case TypeVoid() => BoxVoid()
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))
......@@ -306,6 +317,7 @@ class NativeCallHelper {
}
def putRvToBuf(buf: Closure.Buffer, ty: MType, vb: ValueBox): Unit = ty match {
case TypeVoid() => // do nothing
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)
......
......@@ -134,9 +134,9 @@ class NativeStackKeeper(implicit nativeCallHelper: NativeCallHelper) extends Poo
}
}
} catch {
case e: Exception =>
case e: Throwable =>
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).")
"Prepare for undefined behaviours in native frames (or JVM frames if the native calls back again).", e)
throw e
}
......
......@@ -260,8 +260,8 @@ class UvmInterpreterNativeCallbackTests extends UvmBundleTesterBase {
val peerAddr = ca.toPointer(peer)
pongCalled += 1
printf("Pong called. v=%d, peer=0x%x\n", vInt, peerAddr)
printf("[Mu:@pong]: Pong called. v=%d, peer=0x%x\n", vInt, peerAddr)
vInt shouldBe (pongCalled match {
case 1 => 10
......@@ -283,10 +283,10 @@ class UvmInterpreterNativeCallbackTests extends UvmBundleTesterBase {
respInt shouldBe (vInt match {
case 10 => 362880
case 8 => 5040
case 6 => 120
case 4 => 6
case 2 => 1
case 8 => 5040
case 6 => 120
case 4 => 6
case 2 => 1
})
TrapRebindPassVoid(st)
......@@ -313,4 +313,48 @@ class UvmInterpreterNativeCallbackTests extends UvmBundleTesterBase {
ca.close()
}
"The Mu micro VM" should "be able to swap between stacks with native frames" in {
val ca = microVM.newClientAgent()
val giverAddr = libCallbacktest.getSymbolAddress("giver")
println("Native func (giver) addr: 0x%x".format(giverAddr))
giverAddr should not be 0L
val takerAddr = libCallbacktest.getSymbolAddress("taker")
println("Native func (taker) addr: 0x%x".format(takerAddr))
takerAddr should not be 0L
val hGiverFP = ca.putPointer("@giver.fp", giverAddr)
val hGiverGlobal = ca.putGlobal("@giver.global")
ca.store(MemoryOrder.NOT_ATOMIC, hGiverGlobal, hGiverFP)
val hTakerFP = ca.putPointer("@taker.fp", takerAddr)
val hTakerGlobal = ca.putGlobal("@taker.global")
ca.store(MemoryOrder.NOT_ATOMIC, hTakerGlobal, hTakerFP)
val muFunc = ca.putFunction("@native_sched_test")
testFunc(ca, muFunc, Seq()) { (ca, th, st, wp) =>
ca.nameOf(ca.currentInstruction(st, 0)) match {
case "@native_sched_test.v1.exittrap" => {
try {
val Seq(rvTaker) = ca.dumpKeepalives(st, 0)
ca.toInt(rvTaker).toInt shouldEqual 3628800
TrapRebindPassVoid(st)
} catch {
case e: Exception =>
e.printStackTrace()
// Have to immediately stop the process because those native frames will not return.
System.exit(1)
throw new Exception("Unreachable")
}
}
}
}
ca.close()
}
}
\ No newline at end of file
......@@ -28,3 +28,30 @@ int ping(int v, PingPong cb) {
return v * cbPingPong(v-1, selfAnyFunc);
}
}
typedef int (*TakeFromMu)();
typedef void (*GiveToMu)(int v);
void giver(GiveToMu cb) {
int i;
for (i=1;i<=10;i++) {
printf("[c:giver] Sending %d...\n", i);
cb(i);
printf("[c:giver] Sent %d.\n", i);
}
}
int taker(TakeFromMu cb) {
int p = 1;
while(1) {
printf("[C:taker] p=%d. Receiving...\n", p);
int v = cb();
printf("[C:taker] Received value %d.\n", v);
if (v < 0) {
break;
}
p *= v;
}
printf("[C:taker] Returning value %d.\n", p);
return p;
}
......@@ -194,6 +194,8 @@
COMMINST @uvm.thread_exit
}
// Multi-level recursion
.funcsig @PingPong.sig = @i32 (@i32 @PingPong.fp)
.typedef @PingPong.fp = funcptr<@PingPong.sig>
......@@ -223,3 +225,128 @@
%exittrap = TRAP<@void> KEEPALIVE (%rv)
COMMINST @uvm.thread_exit
}
// Native and swapstack
.funcsig @GiveToMu.sig = @void (@i32)
.typedef @GiveToMu.fp = funcptr<@GiveToMu.sig>
.funcsig @TakeFromMu.sig = @i32 (@void)
.typedef @TakeFromMu.fp = funcptr<@TakeFromMu.sig>
.funcsig @giver.sig = @void (@GiveToMu.fp)
.typedef @giver.fp = funcptr<@giver.sig>
.global @giver.global <@giver.fp>
.funcsig @taker.sig = @i32 (@TakeFromMu.fp)
.typedef @taker.fp = funcptr<@taker.sig>
.global @taker.global <@taker.fp>
.typedef @stacktable = hybrid<@void @stack>
.typedef @refstacktable = ref<@stacktable>
.global @cookie_stack_table <@refstacktable>
.const @I64_STACK_TABLE_SZ <@i64> = 16
.const @I64_MY_MAGIC_NUM <@i64> = 7
.const @I32_N1 <@i32> = -1
.funcdef @native_sched_test VERSION @native_sched_test.v1 <@noparamsnoret> () {
%entry:
// Make a table to map cookies to values. This emulates supporting more
// than one "closures" as callbacks via cookies.
%stab = NEWHYBRID <@stacktable @i64> @I64_STACK_TABLE_SZ
STORE <@refstacktable> @cookie_stack_table %stab
// Put the main stack in the table.
%stab_i = GETIREF <@stacktable> %stab
%stab_v = GETVARPARTIREF <@stacktable> %stab_i
%myentry_i = SHIFTIREF <@stack @i64> %stab_v @I64_MY_MAGIC_NUM
%mainstack = COMMINST @uvm.current_stack
STORE <@stack> %myentry_i %mainstack
// Expose the two callbacks with cookies set to @I64_MY_MAGIC_NUM.
// In this way, both of the two callbacks are agnostic of the current
// stack. i.e. I can use those callbacks from different main stacks.
%give_to_mu_exposed = COMMINST @uvm.native.expose [#DEFAULT] <[@GiveToMu.sig]> (@give_to_mu @I64_MY_MAGIC_NUM)
%take_from_mu_exposed = COMMINST @uvm.native.expose [#DEFAULT] <[@TakeFromMu.sig]> (@take_from_mu @I64_MY_MAGIC_NUM)
%giver_stack = NEWSTACK <@giver_wrapper.sig> @giver_wrapper (%mainstack %give_to_mu_exposed)
%taker_stack = NEWSTACK <@taker_wrapper.sig> @taker_wrapper (%mainstack %take_from_mu_exposed)
// Let the taker reach its first swapstack point so that it is ready
// to consume values.
%taker_first_swap = SWAPSTACK %taker_stack RET_WITH <@void> PASS_VOID
// Enter loop
BRANCH %head
%head:
// Loop until @giver_wrapper throws an exception to us.
%rv = SWAPSTACK %giver_stack RET_WITH <@i32> PASS_VOID EXC(%body %exit)
%body:
// Normal return from %giver_stack. Feed the value to the taker.
%rv2 = SWAPSTACK %taker_stack RET_WITH <@void> PASS_VALUE <@i32> %rv
BRANCH %head
%exit:
// Receive the exception. In this case it is actually just an object
// with @void content, but real-world languages (such as Python) may
// expect a StopIteration exception.
%exc = LANDINGPAD
// Send taker -1 so it can stop. Then collect the result.
%rv_taker = SWAPSTACK %taker_stack RET_WITH <@i32> PASS_VALUE <@i32> @I32_N1
%exittrap = TRAP <@void> KEEPALIVE (%rv_taker)
COMMINST @uvm.thread_exit
}
.funcdef @give_to_mu VERSION @give_to_mu.v1 <@GiveToMu.sig> (%v) {
%entry:
%cok = COMMINST @uvm.native.get_cookie
%stab = LOAD <@refstacktable> @cookie_stack_table
%stab_i = GETIREF <@stacktable> %stab
%stab_v = GETVARPARTIREF <@stacktable> %stab_i
%myentry_i = SHIFTIREF <@stack @i64> %stab_v %cok
%mainstack = LOAD <@stack> %myentry_i
%ss = SWAPSTACK %mainstack RET_WITH <@void> PASS_VALUE <@i64> %v
RETVOID
}
.funcsig @giver_wrapper.sig = @void (@stack @GiveToMu.fp)
.funcdef @giver_wrapper VERSION @giver_wrapper.v1 <@giver_wrapper.sig> (%mainstack %cb) {
%entry:
%giver = LOAD <@giver.fp> @giver.global
%cc = CCALL #DEFAULT <@giver.fp @giver.sig> %giver (%cb)
%exc = NEW <@void>
%lastswap = SWAPSTACK %mainstack KILL_OLD THROW_EXC %exc
}
.funcdef @take_from_mu VERSION @take_from_mu.v1 <@TakeFromMu.sig> (%v) {
%entry:
%cok = COMMINST @uvm.native.get_cookie
%stab = LOAD <@refstacktable> @cookie_stack_table
%stab_i = GETIREF <@stacktable> %stab
%stab_v = GETVARPARTIREF <@stacktable> %stab_i
%myentry_i = SHIFTIREF <@stack @i64> %stab_v %cok
%mainstack = LOAD <@stack> %myentry_i
%ss = SWAPSTACK %mainstack RET_WITH <@i32> PASS_VOID
RET <@i32> %ss
}
.funcsig @taker_wrapper.sig = @void (@stack @TakeFromMu.fp)
.funcdef @taker_wrapper VERSION @taker_wrapper.v1 <@taker_wrapper.sig> (%mainstack %cb) {
%entry:
%taker = LOAD <@taker.fp> @taker.global
%cc = CCALL #DEFAULT <@taker.fp @taker.sig> %taker (%cb)
%lastswap = SWAPSTACK %mainstack KILL_OLD PASS_VALUE <@i32> %cc
}
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