Commit ff2c290e authored by Kunshan Wang's avatar Kunshan Wang
Browse files

Trap handler and tests.

In theory, all Mu client API functions are available to C, but not all
are tested.  Now a client in C is known to be able to create stack,
thread, handle traps and introspect stacks.
parent 22a4132f
......@@ -120,6 +120,9 @@ struct MuVM {
// Set handlers
void (*set_trap_handler )(MuVM *mvm, MuTrapHandler trap_handler, MuCPtr userdata);
// Proprietary API: let the micro VM execute
void (*execute)(MuVM *mvm);
};
// A local context. It can only be used by one thread at a time. It holds many
......@@ -221,7 +224,7 @@ struct MuCtx {
// Thread and stack creation and stack destruction
MuStackRefValue (*new_stack )(MuCtx *ctx, MuFuncRefValue func);
MuThreadRefValue (*new_thread)(MuCtx *ctx, MuStackRefValue stack,
MuHowToResume *htr, MuValue *vals, int nvals, MuRefValue exc);
MuHowToResume htr, MuValue *vals, int nvals, MuRefValue exc);
void (*kill_stack)(MuCtx *ctx, MuStackRefValue stack);
// Frame cursor operations
......
......@@ -17,14 +17,14 @@ class TrapManager(implicit microVM: MicroVM) {
object DefaultTrapHandler extends TrapHandler {
def handleTrap(ctx: MuCtx, thread: MuThreadRefValue, stack: MuStackRefValue, watchPointID: Int): TrapHandlerResult = {
val thrID = thread.vb.asInstanceOf[BoxThread].thread.get.id
// val curFuncID = ctx.
// val funcVerID = ca.currentFuncVer(stack, 0)
// val funcVer = microVM.globalBundle.funcVerNs(funcVerID)
// val instID = ca.currentInstruction(stack, 0)
// val inst = microVM.globalBundle.varNs(instID)
// throw new UvmRuntimeException("Unhandled trap. Thread %d, funcver %s, trap inst %s, watch point ID %d".format(
// thr.id, funcVer.repr, inst.repr, watchPointID))
???
val staID = stack.vb.asInstanceOf[BoxStack].stack.get.id
val cursor = ctx.newCursor(stack)
val curFuncID = ctx.curFunc(cursor)
val curFuncVerID = ctx.curFuncVer(cursor)
val curInstID = ctx.curInst(cursor)
ctx.closeCursor(cursor)
throw new UvmRuntimeException("Unhandled trap. thread %d, stack: %d, func: %d, funcver: %d, inst %d, watch point ID %d".format(
thrID, staID, curFuncID, curFuncVerID, curInstID, watchPointID))
}
}
......
......@@ -22,13 +22,129 @@ import uvm.ssavariables.MemoryOrder
import uvm.ssavariables.AtomicRMWOptr
import uvm.ssavariables.Flag
import uvm.ssavariables.Flag
import com.kenai.jffi.Invoker
import com.kenai.jffi.CallContext
import com.kenai.jffi.HeapInvocationBuffer
import NativeClientSupport._
object NativeTrapHandler {
val jffiInvoker = Invoker.getInstance
val trapHandlerCallContext = new CallContext(
JType.VOID, // return value
// input params
JType.POINTER, //MuCtx *ctx,
JType.POINTER, //MuThreadRefValue thread,
JType.POINTER, //MuStackRefValue stack,
JType.SINT32, //int wpid,
// output params
JType.POINTER, //MuTrapHandlerResult *result,
JType.POINTER, //MuStackRefValue *new_stack,
JType.POINTER, //MuValue **values,
JType.POINTER, //int *nvalues,
JType.POINTER, //MuValuesFreer *freer,
JType.POINTER, //MuCPtr *freerdata,
JType.POINTER, //MuRefValue *exception,
// user data
JType.POINTER //MuCPtr userdata
);
val freerCallContext = new CallContext(
JType.VOID, // return value
// input params
JType.POINTER, // MuValue *values,
JType.POINTER //MuCPtr freerdata
)
def callFreer(freerFP: MuValuesFreerFP, valuesPtr: MuValueFakArrayPtr, freerData: MuCPtr): Unit = {
val freerFunc = new com.kenai.jffi.Function(freerFP, freerCallContext)
logger.debug("Calling freer: 0x%x with args (0x%x, 0x%x)".format(freerFP, valuesPtr, freerData))
jffiInvoker.invokeLLrL(freerFunc, valuesPtr, freerData)
logger.debug("Returned from freer")
}
}
/**
* This object calls a C function to handle the trap.
*/
class NativeTrapHandler(val funcAddr: Long, val userdata: Long) extends TrapHandler {
class NativeTrapHandler(val funcAddr: MuTrapHandlerFP, val userdata: MuCPtr) extends TrapHandler {
import NativeTrapHandler._
import NativeMuHelpers._
val jffiFunction = new com.kenai.jffi.Function(funcAddr, trapHandlerCallContext)
def handleTrap(ctx: MuCtx, thread: MuThreadRefValue, stack: MuStackRefValue, watchPointID: Int): TrapHandlerResult = {
???
logger.debug("Trapped. Prepare to call native trap handler: %d 0x%x".format(funcAddr, funcAddr))
val hib = new HeapInvocationBuffer(jffiFunction)
// input args
val ctxFak = exposeMuCtx(ctx) // It is a fresh context.
hib.putAddress(ctxFak)
val threadFak = exposeMuValue(ctx, thread) // It is a fresh handle, too.
hib.putAddress(threadFak)
val stackFak = exposeMuValue(ctx, stack) // It is a fresh handle, too.
hib.putAddress(stackFak)
hib.putInt(watchPointID)
// output args
val nOutArgs = 7
val outBuf = jnrMemoryManager.allocateDirect((7L * WORD_SIZE_BYTES).toInt, true) // output values are received here.
val outBufAddr = outBuf.address()
for (i <- 0 until nOutArgs) {
val outAddr = outBufAddr + i * WORD_SIZE_BYTES
logger.debug("The %d-th out arg: 0x%x".format(i, outAddr))
hib.putAddress(outAddr)
}
// user data
hib.putAddress(userdata)
// Call
logger.debug("Calling native trap handler: 0x%x".format(funcAddr))
jffiInvoker.invokeLong(jffiFunction, hib)
logger.debug("Returned from native trap handler: 0x%x".format(funcAddr))
// Inspect return value
val result = outBuf.getInt(0L)
val scalaResult: TrapHandlerResult = result match {
case MU_THREAD_EXIT => TrapHandlerResult.ThreadExit()
case MU_REBIND_PASS_VALUES => {
val nsFak = outBuf.getAddress(1 * WORD_SIZE_BYTES)
val newStack = getMuValueNotNull(nsFak).asInstanceOf[MuStackRefValue]
val valuesPtr = outBuf.getAddress(2 * WORD_SIZE_BYTES)
val nValues = outBuf.getInt(3 * WORD_SIZE_BYTES)
val valueFaks = for (i <- 0 until nValues) yield {
theMemory.getAddress(valuesPtr + i * WORD_SIZE_BYTES)
}
val freerFP = outBuf.getAddress(4 * WORD_SIZE_BYTES)
if (freerFP != 0L) {
val freerData = outBuf.getAddress(5 * WORD_SIZE_BYTES)
callFreer(freerFP, valuesPtr, freerData)
}
val values = valueFaks.map(getMuValueNotNull)
TrapHandlerResult.Rebind(newStack, HowToResume.PassValues(values))
}
case MU_REBIND_THROW_EXC => {
val nsFak = outBuf.getAddress(1 * WORD_SIZE_BYTES)
val newStack = getMuValueNotNull(nsFak).asInstanceOf[MuStackRefValue]
val excFak = outBuf.getAddress(6 * WORD_SIZE_BYTES)
val excVal = getMuValueNotNull(excFak).asInstanceOf[MuRefValue]
TrapHandlerResult.Rebind(newStack, HowToResume.ThrowExc(excVal))
}
}
unexposeMuCtx(ctxFak) // Mu will close the context, but not un-expose it.
scalaResult
}
}
......@@ -37,7 +153,6 @@ class NativeTrapHandler(val funcAddr: Long, val userdata: Long) extends TrapHand
*/
object NativeMuVM {
import NativeMuHelpers._
import NativeClientSupport._
def new_context(microVM: MicroVM): MuCtxFak = {
val ctx = microVM.newContext()
......@@ -49,6 +164,7 @@ object NativeMuVM {
def set_trap_handler(microVM: MicroVM, trap_handler: MuTrapHandlerFP, userdata: MuCPtr): Unit = {
microVM.setTrapHandler(new NativeTrapHandler(trap_handler, userdata))
}
def execute(microVM: MicroVM): Unit = microVM.execute()
}
/**
......@@ -59,7 +175,6 @@ object NativeMuVM {
*/
object NativeMuCtx {
import NativeMuHelpers._
import NativeClientSupport._
def id_of(ctx: MuCtx, name: MuName): MuID = ctx.idOf(name)
def name_of(ctx: MuCtx, id: MuID): MuName = ctx.nameOf(id)
......@@ -157,7 +272,10 @@ object NativeMuCtx {
def fence(ctx: MuCtx, ord: MuMemOrd): Unit = ctx.fence(ord)
def new_stack(ctx: MuCtx, func: MuFuncRefValue): MuValueFak = exposeMuValue(ctx, ctx.newStack(func))
def new_thread(ctx: MuCtx, stack: MuStackRefValue, htr: MuHowToResume, vals: MuValueFakArrayPtr, nvals: Int, exc: MuRefValue): MuValueFak = {
// NOTE: parameter exc must not be a MuValue because this parameter is ignored when htr is MU_REBIND_PASS_VALUE.
// Setting the type to MuValue (or MuRefValue) will force the exposer to eagerly resolve the underlying actual MuValue.
def new_thread(ctx: MuCtx, stack: MuStackRefValue, htr: MuHowToResume, vals: MuValueFakArrayPtr, nvals: Int, exc: MuValueFak): MuValueFak = {
val scalaHtr = htr match {
case MU_REBIND_PASS_VALUES => {
val values = for (i <- 0L until nvals) yield {
......@@ -169,7 +287,8 @@ object NativeMuCtx {
HowToResume.PassValues(values)
}
case MU_REBIND_THROW_EXC => {
HowToResume.ThrowExc(exc)
val excVal = getMuValueNotNull(exc).asInstanceOf[MuRefValue]
HowToResume.ThrowExc(excVal)
}
}
val rv = ctx.newThread(stack, scalaHtr)
......@@ -211,10 +330,10 @@ object NativeMuCtx {
def enable_watchpoint(ctx: MuCtx, wpid: Int): Unit = ctx.enableWatchpoint(wpid)
def disable_watchpoint(ctx: MuCtx, wpid: Int): Unit = ctx.disableWatchpoint(wpid)
def pin(ctx: MuCtx, loc: MuValue): MuValueFak = exposeMuValue(ctx, ctx.pin(loc))
def unpin(ctx: MuCtx, loc: MuValue): Unit = ctx.unpin(loc)
def expose(ctx: MuCtx, func: MuFuncRefValue, call_conv: MuCallConv, cookie: MuIntValue): MuValueFak = exposeMuValue(ctx, ctx.expose(func, call_conv, cookie))
def unexpose(ctx: MuCtx, call_conv: MuCallConv, value: MuUFPValue): Unit = ctx.unexpose(call_conv, value)
}
......@@ -223,7 +342,6 @@ object NativeMuCtx {
* These functions are not exposed.
*/
object NativeMuHelpers {
import NativeClientSupport._
private val ONE = BigInt(1)
......@@ -264,7 +382,7 @@ object NativeMuHelpers {
val MU_MIN = 0x08
val MU_UMAX = 0x09
val MU_UMIN = 0x0A
val MU_DEFAULT = 0x00
implicit def intToMemOrd(ord: MuMemOrd): MemoryOrder.MemoryOrder = ord match {
......@@ -290,14 +408,13 @@ object NativeMuHelpers {
case MU_UMAX => AtomicRMWOptr.UMAX
case MU_UMIN => AtomicRMWOptr.UMIN
}
implicit def intToCallConv(cc: MuCallConv): Flag = cc match {
case MU_DEFAULT => Flag("#DEFAULT")
}
}
object ClientAccessibleClassExposer {
import NativeClientSupport._
val MAX_NAME_SIZE = 65536
......@@ -405,8 +522,6 @@ object ClientAccessibleClassExposer {
*/
class ClientAccessibleClassExposer[T: ru.TypeTag: ClassTag](obj: T) {
import ClientAccessibleClassExposer._
import NativeSupport._
import NativeClientSupport._
/**
* A list of JFFI closure handles, one for each declared method in obj, in the declared order.
......@@ -516,6 +631,7 @@ object NativeClientSupport {
type CharArrayPtr = Word
type MuValueFakPtr = Word
type MuValueFakArrayPtr = Word
type MuValuesFreerFP = Word
// Mu API C-level types.
type MuID = Int
......
......@@ -27,6 +27,7 @@ class NativeClientSupportTest extends UvmBundleTesterBase {
def test_with_ctx(mvm: Word, theID: Int, theName: String): CBool
def test_basic_conv(mvm: Word): CBool
def test_global_vars(mvm: Word, the_plus_one_fp: Word): CBool
def test_traps(mvm: Word): CBool
}
val ncs_tests = LibraryLoader.create(classOf[NcsTestsLib]).load(fileName)
......@@ -77,4 +78,9 @@ class NativeClientSupportTest extends UvmBundleTesterBase {
val result = ncs_tests.test_global_vars(microVMFuncTableAddr, thePlusOneFP)
assertNativeSuccess(result)
}
it should "support traps" in {
val result = ncs_tests.test_traps(microVMFuncTableAddr)
assertNativeSuccess(result)
}
}
\ No newline at end of file
......@@ -3,6 +3,7 @@
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <muapi.h>
......@@ -11,7 +12,13 @@
return false; \
}
#define MU_ASSERT_EQUALS_TRAP(a, b, f) if (!(a == b)) { \
muprintf("%s (%" f ") is not equal to %s (%" f ")\n", #a, a, #b, b); \
exit(1); \
}
#define ID(name) ctx->id_of(ctx, name)
#define NAME(id) ctx->name_of(ctx, id)
#define muprintf(fmt, ...) printf("[C:%d:%s] " fmt, __LINE__, __func__, ## __VA_ARGS__)
......@@ -208,3 +215,127 @@ bool test_global_vars(MuVM *mvm, int64_t(*the_plus_one_fp)(int64_t)) {
return true;
}
struct simple_context {
int magic;
};
void malloc_freer(MuValue *values, MuCPtr freerdata) {
free(values);
}
void nop_freer(MuValue *values, MuCPtr freerdata) {
}
void simple_trap_handler(MuCtx *ctx, MuThreadRefValue thread,
MuStackRefValue stack, int wpid, MuTrapHandlerResult *result,
MuStackRefValue *new_stack, MuValue **values, int *nvalues,
MuValuesFreer *freer, MuCPtr *freerdata, MuRefValue *exception,
MuCPtr userdata) {
muprintf("Hi! I am the native trap handler!\n");
struct simple_context *userctx = (struct simple_context*)userdata;
int magic = userctx->magic;
muprintf("My magic is %d\n", magic);
MU_ASSERT_EQUALS_TRAP(magic, 42, "d");
muprintf("I am going to introspect the stack.\n");
MuFCRefValue *cursor = ctx->new_cursor(ctx, stack);
MuID fid = ctx->cur_func(ctx, cursor);
muprintf("Function: %d: %s\n", fid, NAME(fid));
MuID fvid = ctx->cur_func_ver(ctx, cursor);
muprintf("Version: %d: %s\n", fvid, NAME(fvid));
MuID iid = ctx->cur_inst(ctx, cursor);
muprintf("Instruction: %d: %s\n", iid, NAME(iid));
MuID trap1_id = ID("@trapper.v1.entry.trap1");
MuID trap2_id = ID("@trapper.v1.entry.trap2");
MU_ASSERT_EQUALS_TRAP(fid , ID("@trapper"), "d");
MU_ASSERT_EQUALS_TRAP(fvid, ID("@trapper.v1"), "d");
muprintf("Selecting branch according to the instruction ID: %d\n", iid);
if (iid == trap1_id) {
muprintf("It is %%trap1\n");
MuValue kas[1];
muprintf("Dumping keep-alives...\n");
ctx->dump_keepalives(ctx, cursor, kas);
muprintf("Dumped\n");
ctx->close_cursor(ctx, cursor);
int64_t oldKa = ctx->handle_to_sint64(ctx, kas[0]);
muprintf("The keep-alive variable is %" PRId64 "\n", oldKa);
MU_ASSERT_EQUALS_TRAP(oldKa, 42LL, PRId64);
int64_t newKa = oldKa + 1;
muprintf("Prepare to return\n");
muprintf("Writing result at %p\n", result);
*result = MU_REBIND_PASS_VALUES;
muprintf("Writing new_stack at %p\n", new_stack);
*new_stack = stack;
muprintf("Allocating values array, writing at %p\n", values);
*values = (MuValue*)malloc(8);
muprintf("Setting the only value. Addr: %p\n", &(*values)[0]);
(*values)[0] = ctx->handle_from_sint64(ctx, newKa, 64);
muprintf("Writing nvalues at %p\n", nvalues);
*nvalues = 1;
muprintf("Writing freer at %p\n", freer);
*freer = malloc_freer;
muprintf("Writing freerdata at %p\n", freerdata);
*freerdata = NULL;
muprintf("Bye!\n");
return;
} else if (iid == trap2_id) {
muprintf("It is %%trap2\n");
MuValue kas[1];
ctx->dump_keepalives(ctx, cursor, kas);
ctx->close_cursor(ctx, cursor);
muprintf("Introspect the KAs...\n");
int64_t v1 = ctx->handle_to_sint64(ctx, kas[0]);
muprintf("KA value %%v1 is %" PRId64 "\n", v1);
MU_ASSERT_EQUALS_TRAP(v1, 43LL, PRId64);
muprintf("Prepare to return from trap handler...\n");
*result = MU_REBIND_PASS_VALUES;
*new_stack = stack;
*values = NULL;
*nvalues = 0;
*freer = NULL;
*freerdata = NULL;
muprintf("Bye!\n");
return;
} else {
muprintf("Unknown trap. ID: %d\n", iid);
MuName trapName = NAME(iid);
muprintf("name: %s\n", trapName);
exit(1);
}
return; // Unreachable, but the C compiler may not be smart enough.
}
bool test_traps(MuVM *mvm) {
struct simple_context userctx = { 42 };
mvm->set_trap_handler(mvm, simple_trap_handler, &userctx);
MuCtx *ctx = mvm->new_context(mvm);
MuValue args[1];
args[0] = ctx->handle_from_sint64(ctx, 42LL, 64);
MuFuncRefValue func = ctx->handle_from_func(ctx, ID("@trapper"));
MuStackRefValue stack = ctx->new_stack(ctx, func);
MuThreadRefValue thread = ctx->new_thread(ctx, stack, MU_REBIND_PASS_VALUES,
args, 1, NULL);
mvm->execute(mvm);
ctx->close_context(ctx);
return true;
}
......@@ -9,3 +9,10 @@
}
.expose @plus_one_native = @plus_one #DEFAULT @I64_0
.funcdef @trapper VERSION %v1 <@i_i> {
%entry(<@i64> %n):
%v1 = [%trap1] TRAP <@i64> KEEPALIVE (%n)
[%trap2] TRAP <> KEEPALIVE (%v1)
COMMINST @uvm.thread_exit
}
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