Commit 607995f8 authored by Kunshan Wang's avatar Kunshan Wang

WIP: Expose MuCtx to C clients.

C client can call MuCtx methods and close it now. It looks like it's on
the right track.
parent 23932cb1
package uvm.refimpl.nat package uvm.refimpl.nat
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import scala.collection.mutable.HashMap import scala.collection.mutable.HashMap
import scala.reflect.ClassTag import scala.reflect.ClassTag
import scala.reflect.runtime.universe import scala.reflect.runtime.universe
import scala.reflect.runtime.{ universe => ru } import scala.reflect.runtime.{ universe => ru }
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import com.kenai.jffi.CallingConvention import com.kenai.jffi.CallingConvention
import com.kenai.jffi.Closure import com.kenai.jffi.Closure
import com.kenai.jffi.Closure.Buffer import com.kenai.jffi.Closure.Buffer
import com.kenai.jffi.{ Type => JType } import com.kenai.jffi.{ Type => JType }
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import NativeSupport._ import NativeSupport._
import PlatformConstants.WORD_SIZE_BYTES import PlatformConstants.WORD_SIZE_BYTES
import PlatformConstants.Word import PlatformConstants.Word
import jnr.ffi.ObjectReferenceManager import jnr.ffi.ObjectReferenceManager
import jnr.ffi.Pointer import jnr.ffi.Pointer
import uvm.refimpl._ import uvm.refimpl._
import scala.collection.mutable.HashSet
/** /**
* This object calls a C function to handle the trap. * This object calls a C function to handle the trap.
...@@ -41,8 +38,8 @@ object NativeMuVM { ...@@ -41,8 +38,8 @@ object NativeMuVM {
def new_context(microVM: MicroVM): MuCtxPtr = { def new_context(microVM: MicroVM): MuCtxPtr = {
val ctx = microVM.newContext() val ctx = microVM.newContext()
val ptr = NativeClientSupport.muCtxs.add(ctx) val ptr = NativeClientSupport.exposeMuCtx(ctx)
ptr.address() ptr
} }
def id_of(microVM: MicroVM, name: String): Int = microVM.idOf(name) def id_of(microVM: MicroVM, name: String): Int = microVM.idOf(name)
def name_of(microVM: MicroVM, id: Int): String = microVM.nameOf(id) def name_of(microVM: MicroVM, id: Int): String = microVM.nameOf(id)
...@@ -51,6 +48,31 @@ object NativeMuVM { ...@@ -51,6 +48,31 @@ object NativeMuVM {
} }
} }
/**
* This object routes calls from native clients to a MuCtx object.
*/
object NativeMuCtx {
// Syntax sugar for MuCtx and MuValue return values, which are pointers
type MuCtxPtr = Word
type MuValuePtr = Word
def id_of(ctx: MuCtx, name: String): Int = ctx.idOf(name)
def name_of(ctx: MuCtx, id: Int): String = ctx.nameOf(id)
/**
* NOTE: Should have taken MuCtx as the first parameter, but we need to de-allocate
* the function table, too.
*/
def close_context(funcTableAddr: Word): Unit = {
val ctx = NativeClientSupport.getObjNotNull(funcTableAddr, NativeClientSupport.muCtxs)
for (muValueAddr <- NativeClientSupport.muCtxToMuValues.remove(ctx).get) {
NativeClientSupport.unexposeMuValue(muValueAddr)
}
NativeClientSupport.unexposeMuCtx(funcTableAddr)
ctx.closeContext()
}
}
object ClientAccessibleClassExposer { object ClientAccessibleClassExposer {
val MAX_NAME_SIZE = 65536 val MAX_NAME_SIZE = 65536
...@@ -109,13 +131,8 @@ object ClientAccessibleClassExposer { ...@@ -109,13 +131,8 @@ object ClientAccessibleClassExposer {
} }
private def getObj[T](buffer: Buffer, index: Int, orm: ObjectReferenceManager[T]): T = { private def getObj[T](buffer: Buffer, index: Int, orm: ObjectReferenceManager[T]): T = {
val arg0 = buffer.getLong(index) val funcTableAddr = buffer.getLong(index)
val objAddr = theMemory.getAddress(arg0) NativeClientSupport.getObjNotNull(funcTableAddr, orm)
val obj = orm.get(jnrMemoryManager.newPointer(objAddr))
if (obj == null) {
throw new UvmRefImplException("Exposed object not found. Address: %d 0x%x".format(objAddr, objAddr))
}
obj
} }
// Reflection utils. // Reflection utils.
...@@ -217,21 +234,26 @@ class ClientAccessibleClassExposer[T: ru.TypeTag: ClassTag](obj: T) { ...@@ -217,21 +234,26 @@ class ClientAccessibleClassExposer[T: ru.TypeTag: ClassTag](obj: T) {
/** /**
* Allocate and populate a native function table, * Allocate and populate a native function table,
* i.e. the in-memory structure of struct MuVM and struct MuCtx in the client interface. * i.e. the in-memory structure of struct MuVM and struct MuCtx in the client interface.
*
* @param objAddr The "address" of the object, created by the ObjectReferenceManager.
* This will be the first field of the table.
*
* @return The address of the function table.
*/ */
def makeFuncTable(instance: Word): Word = { def makeFuncTable(objAddr: Word): Word = {
val nMethods = closureHandles.size val nMethods = closureHandles.size
val structSizeWords = nMethods + 1 val structSizeWords = nMethods + 1
val structSizeBytes = structSizeWords * WORD_SIZE_BYTES.toInt val structSizeBytes = structSizeWords * WORD_SIZE_BYTES.toInt
val s = jnrMemoryManager.allocateDirect(structSizeBytes) val s = jnrMemoryManager.allocateDirect(structSizeBytes)
s.putLong(0L, instance) s.putLong(0L, objAddr)
for ((handle, i) <- closureHandles.zipWithIndex) { for ((handle, i) <- closureHandles.zipWithIndex) {
val offset = (i + 1) * WORD_SIZE_BYTES val offset = (i + 1) * WORD_SIZE_BYTES
val funcAddr = handle.getAddress() val funcAddr = handle.getAddress()
s.putLong(offset, funcAddr) s.putLong(offset, funcAddr)
} }
val addr = s.address() val funcTableAddr = s.address()
NativeClientSupport.funcTableToPointer(addr) = s NativeClientSupport.funcTableToPointer(funcTableAddr) = s
addr funcTableAddr
} }
} }
...@@ -243,6 +265,18 @@ object NativeClientSupport { ...@@ -243,6 +265,18 @@ object NativeClientSupport {
val microVMs = jnrRuntime.newObjectReferenceManager[MicroVM]() val microVMs = jnrRuntime.newObjectReferenceManager[MicroVM]()
val muCtxs = jnrRuntime.newObjectReferenceManager[MuCtx]() val muCtxs = jnrRuntime.newObjectReferenceManager[MuCtx]()
val muValues = jnrRuntime.newObjectReferenceManager[MuValue]() val muValues = jnrRuntime.newObjectReferenceManager[MuValue]()
/** Map each MuCtx to all of its current MuValues. This is needed when closing a MuCtx. */
val muCtxToMuValues = HashMap[MuCtx, HashSet[Word]]()
def getObjNotNull[T](funcTableAddr: Word, orm: ObjectReferenceManager[T]): T = {
val objAddr = theMemory.getAddress(funcTableAddr)
val obj = orm.get(jnrMemoryManager.newPointer(objAddr))
if (obj == null) {
throw new UvmRefImplException("Exposed object not found. Address: %d 0x%x".format(objAddr, objAddr))
}
obj
}
/** /**
* Map function table addresses to Pointer so they can be closed. JFFI uses the TrantientNativeMemory (wrapped * Map function table addresses to Pointer so they can be closed. JFFI uses the TrantientNativeMemory (wrapped
...@@ -255,12 +289,47 @@ object NativeClientSupport { ...@@ -255,12 +289,47 @@ object NativeClientSupport {
* alive so that the native program can access them. * alive so that the native program can access them.
*/ */
val stringPool = HashMap[String, Pointer]() val stringPool = HashMap[String, Pointer]()
// These "exposer" can repeatedly generate function tables.
val muVMExposer = new ClientAccessibleClassExposer(NativeMuVM)
val muCtxExposer = new ClientAccessibleClassExposer(NativeMuCtx)
// Expose and unexpose objects // Expose and unexpose objects
def exposeMicroVM(microVM: MicroVM): Word = microVMs.add(microVM).address() /** Expose a MicroVM. Return a pointer to the C MuVM structure. */
def exposeMuCtx(muCtx: MuCtx): Word = muCtxs.add(muCtx).address() def exposeMicroVM(microVM: MicroVM): Word = {
val objAddr = microVMs.add(microVM).address()
val funcTableAddr = muVMExposer.makeFuncTable(objAddr)
funcTableAddr
}
/** Expose a MuCtx. Return a pointer to the C MuCtx structure. */
def exposeMuCtx(muCtx: MuCtx): Word = {
require(!muCtxToMuValues.contains(muCtx), "Context %s is already exposed.".format(muCtx))
muCtxToMuValues(muCtx) = HashSet()
val objAddr = muCtxs.add(muCtx).address()
val funcTableAddr = muCtxExposer.makeFuncTable(objAddr)
funcTableAddr
}
/** Expose a MuValue. Return an "object pointer", i.e. faked by ObjectReferenceManager. */
def exposeMuValue(muValue: MuValue): Word = muValues.add(muValue).address() def exposeMuValue(muValue: MuValue): Word = muValues.add(muValue).address()
def unexposeMicroVM(addr: Long): Unit = microVMs.remove(jnrMemoryManager.newPointer(addr))
def unexposeMuCtx(addr: Long): Unit = muCtxs.remove(jnrMemoryManager.newPointer(addr)) /** Unexpose a MicroVM. Remove the faked pointer and deallocate the function table (by removing the Pointer). */
def unexposeMicroVM(funcTableAddr: Word): Unit = {
require(funcTableToPointer.contains(funcTableAddr),
"MuVM struct pointer %d 0x%x does not exist".format(funcTableAddr, funcTableAddr))
val obj = getObjNotNull(funcTableAddr, microVMs)
val objAddr = theMemory.getAddress(funcTableAddr)
microVMs.remove(jnrMemoryManager.newPointer(objAddr))
funcTableToPointer.remove(funcTableAddr)
}
/** Unexpose a MuCtx. Remove the faked pointer and deallocate the function table (by removing the Pointer). */
def unexposeMuCtx(funcTableAddr: Long): Unit = {
require(funcTableToPointer.contains(funcTableAddr),
"MuCtx struct pointer %d 0x%x does not exist".format(funcTableAddr, funcTableAddr))
val obj = getObjNotNull(funcTableAddr, muCtxs)
val objAddr = theMemory.getAddress(funcTableAddr)
muCtxs.remove(jnrMemoryManager.newPointer(objAddr))
funcTableToPointer.remove(funcTableAddr)
}
/** Unexpose a MuValue by removing it from the ORM. */
def unexposeMuValue(addr: Long): Unit = muValues.remove(jnrMemoryManager.newPointer(addr)) def unexposeMuValue(addr: Long): Unit = muValues.remove(jnrMemoryManager.newPointer(addr))
} }
\ No newline at end of file
...@@ -21,18 +21,17 @@ class NativeClientSupportTest extends UvmBundleTesterBase { ...@@ -21,18 +21,17 @@ class NativeClientSupportTest extends UvmBundleTesterBase {
trait NcsTestsLib { trait NcsTestsLib {
def test_basic(mvm: Word, theID: Int, theName: String): Int def test_basic(mvm: Word, theID: Int, theName: String): Int
def test_with_ctx(mvm: Word, theID: Int, theName: String): Int
} }
val ncs_tests = LibraryLoader.create(classOf[NcsTestsLib]).load(fileName) val ncs_tests = LibraryLoader.create(classOf[NcsTestsLib]).load(fileName)
"The ClientAccessibleClassExposer" should "enumerate declared methods" in { val microVMFuncTableAddr = NativeClientSupport.exposeMicroVM(microVM)
val cace = new ClientAccessibleClassExposer(NativeMuVM)
val microVMAddr = NativeClientSupport.exposeMicroVM(microVM) behavior of "The ClientAccessibleClassExposer"
val funcTable = cace.makeFuncTable(microVMAddr) it should "be able to access the exposed MicroVM" in {
val funcTablePtr = jnrMemoryManager.newPointer(microVMFuncTableAddr)
val funcTablePtr = NativeClientSupport.funcTableToPointer(funcTable)
val header = funcTablePtr.getAddress(0) val header = funcTablePtr.getAddress(0)
val mvm = NativeClientSupport.microVMs.get(jnrMemoryManager.newPointer(header)) val mvm = NativeClientSupport.microVMs.get(jnrMemoryManager.newPointer(header))
...@@ -41,7 +40,19 @@ class NativeClientSupportTest extends UvmBundleTesterBase { ...@@ -41,7 +40,19 @@ class NativeClientSupportTest extends UvmBundleTesterBase {
val theName = "@i64" val theName = "@i64"
val theID = microVM.idOf(theName) val theID = microVM.idOf(theName)
val result = ncs_tests.test_basic(funcTable, theID, theName) val result = ncs_tests.test_basic(microVMFuncTableAddr, theID, theName)
if (result == 0) {
fail("Failed in the native program.")
}
}
it should "be able to create MuCtx and use it" in {
val funcTablePtr = jnrMemoryManager.newPointer(microVMFuncTableAddr)
val theName = "@double"
val theID = microVM.idOf(theName)
val result = ncs_tests.test_with_ctx(microVMFuncTableAddr, theID, theName)
if (result == 0) { if (result == 0) {
fail("Failed in the native program.") fail("Failed in the native program.")
} }
......
...@@ -27,3 +27,25 @@ int test_basic(MuVM *mvm, int theID, char *theName) { ...@@ -27,3 +27,25 @@ int test_basic(MuVM *mvm, int theID, char *theName) {
return 1; return 1;
} }
int test_with_ctx(MuVM *mvm, int theID, char *theName) {
MuCtx *ctx = mvm->new_context(mvm);
int id = ctx->id_of(ctx, theName);
printf("[C] id = %d\n", id);
if (id != theID) {
printf("[C] ID %d is not equal to %d\n", id, theID);
return 0;
}
char *name = ctx->name_of(ctx, theID);
printf("[C] name = %s\n", name);
if (strcmp(name, theName) != 0) {
printf("[C] name %s is not equal to %s\n", name, theName);
return 0;
}
ctx->close_context(ctx);
return 1;
}
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