Commit b4f80372 authored by Kunshan Wang's avatar Kunshan Wang

Fixed and tested one-level C-Mu callback.

parent 1d8bf3c1
......@@ -56,6 +56,11 @@ class MicroVM(heapSize: Word = MicroVM.DEFAULT_HEAP_SIZE,
for (gc <- bundle.globalCellNs.all) {
memoryManager.globalMemory.addGlobalCell(gc)
}
for (ef <- bundle.expFuncNs.all) {
val addr = nativeCallHelper.exposeFunc(ef.func, ef.cookie.num.toLong, false)
ef.addr = addr
}
// Must allocate the memory and expose the functions before making constants.
for (g <- bundle.globalVarNs.all) {
constantPool.addGlobalVar(g)
}
......
......@@ -33,6 +33,16 @@ class ClientAgent(mutator: Mutator)(
val pinSet = new ArrayBuffer[Word]
/**
* Given a name, get the ID of an identified entity.
*/
def idOf(name: String): Int = microVM.idOf(name)
/**
* Given an ID, get the name of an identified entity.
*/
def nameOf(id: Int): String = microVM.nameOf(id)
def close(): Unit = {
handles.clear()
mutator.close()
......@@ -117,6 +127,13 @@ class ClientAgent(mutator: Mutator)(
newHandle(t, box)
}
def putExpFunc(id: Int): Handle = {
val ef = microVM.globalBundle.expFuncNs(id)
val t = InternalTypePool.funcPtrOf(ef.func.sig)
val box = BoxPointer(ef.addr)
newHandle(t, box)
}
def deleteHandle(h: Handle): Unit = {
handles.remove(h)
}
......
......@@ -35,6 +35,7 @@ class ConstantPool(implicit microVM: MicroVM) {
case ConstPointer(ty, addr) => BoxPointer(addr)
case gc:GlobalCell => BoxIRef(0L, microVM.memoryManager.globalMemory.addrForGlobalCell(gc))
case f:Function => BoxFunc(Some(f))
case ef:ExposedFunc => BoxPointer(ef.addr)
}
def getGlobalVarBox(g: GlobalVariable): ValueBox = globalVarBoxes(g)
......
......@@ -810,6 +810,8 @@ class InterpreterThread(val id: Int, initialStack: InterpreterStack, val mutator
throw new UvmRefImplException(ctx + "Currently only support the #DEFAULT callConv. %s found.".format(callConv.name))
}
curInstHalfExecuted = true
val addr = boxOf(callee).asInstanceOf[BoxPointer].addr
val argBoxes = argList.map(boxOf)
......@@ -1131,17 +1133,17 @@ class InterpreterThread(val id: Int, initialStack: InterpreterStack, val mutator
case "@uvm.native.expose" => {
???
}
case "@uvm.native.unexpose" => {
???
}
case "@uvm.native.get_cookie" => {
val cookie = topMu.cookie
boxOf(i).asInstanceOf[BoxInt].value = OpHelper.trunc(cookie, 64)
continueNormally()
}
// Insert more CommInsts here.
case ciName => {
......@@ -1294,7 +1296,6 @@ class InterpreterThread(val id: Int, initialStack: InterpreterStack, val mutator
case NativeCallResult.CallBack(func, cookie, args, retBox) => {
val funcVer = getFuncDefOrTriggerCallback(func)
curInstHalfExecuted = true
curStack.pushMuFrameForCallBack(funcVer, cookie, args)
}
case NativeCallResult.Return() => {
......
......@@ -62,15 +62,18 @@ class NativeCallHelper {
}
/**
* A dynamically-exposed Mu function. A Mu function may be exposed many times. Each DynExpFunc corresponds to one
* such callable instance.
* A run-time record of an exposed Mu function. A Mu function may be exposed many times. Each ExpFuncRecord
* corresponds to one such callable instance.
* <p>
* A ".expose" definition will permanently create an instance.
* <p>
* The "@uvm.native.expose" instruction will also create one such instance. Such instances can be removed later by
* "@uvm.native.unexpose". The equivalent API calls do the same.
*
* @param isDynamic true if it is exposed via the "@uvm.native.expose" instruction or the equivalent API call. false
* if it is exposed by the ".expose" top-level definintion of the Mu IR.
*/
class DynExpFunc(val muFunc: MFunc, val cookie: Long, val closure: MuCallbackClosure, val closureHandle: Closure.Handle)
class DynExpFunc(val muFunc: MFunc, val cookie: Long, val closure: MuCallbackClosure, val closureHandle: Closure.Handle, val isDynamic: Boolean)
/**
* Map each address of closure handle to the DynExpFunc record so that the closure handle can be disposed.
......@@ -212,7 +215,7 @@ class NativeCallHelper {
*
* @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, isDynamic: Boolean): Word = {
val sig = muFunc.sig
val jParamTypes = sig.paramTy.map(jffiTypePool.apply)
val jRetTy = jffiTypePool(sig.retTy)
......@@ -221,7 +224,7 @@ class NativeCallHelper {
val handle = NativeSupport.closureManager.newClosure(clos, jRetTy, jParamTypes.toArray, CallingConvention.DEFAULT)
val addr = handle.getAddress
val dynExpFunc = new DynExpFunc(muFunc, cookie, clos, handle)
val dynExpFunc = new DynExpFunc(muFunc, cookie, clos, handle, isDynamic)
exposedFuncs(addr) = dynExpFunc
......@@ -229,10 +232,16 @@ class NativeCallHelper {
}
def unexposeFunc(addr: Word): Unit = {
val dynExpFunc = exposedFuncs.remove(addr).getOrElse {
val dynExpFunc = exposedFuncs.get(addr).getOrElse {
throw new UvmRuntimeException("Attempt to unexpose function %d (0x%x) which has not been exposed.".format(addr, addr))
}
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()
}
......@@ -257,7 +266,7 @@ class NativeCallHelper {
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)
......
......@@ -36,7 +36,10 @@ case class ConstPointer(var constTy: Type, var addr: Long) extends Constant
case class GlobalCell(var cellTy: Type) extends GlobalVariable
case class ExposedFunc(var func: Function, var callConv: Flag, var cookie: ConstInt) extends GlobalVariable
case class ExposedFunc(var func: Function, var callConv: Flag, var cookie: ConstInt) extends GlobalVariable {
/** This value will be supplied when the function is actually exposed. */
var addr: Long = 0L
}
// Local variables: Parameters and Instructions
......
package uvm.refimpl.itpr
import org.scalatest._
import java.io.FileReader
import uvm._
import uvm.types._
import uvm.ssavariables._
import uvm.refimpl._
import uvm.refimpl.itpr._
import MemoryOrder._
import AtomicRMWOptr._
import uvm.refimpl.mem.TypeSizes.Word
import ch.qos.logback.classic.Level._
import uvm.refimpl.UvmBundleTesterBase
import com.kenai.jffi.Library
import jnr.posix.POSIXFactory
import uvm.refimpl.nat.NativeLibraryTestHelper
class UvmInterpreterNativeCallbackTests extends UvmBundleTesterBase {
setLogLevels(
ROOT_LOGGER_NAME -> INFO,
"uvm.refimpl.itpr" -> DEBUG,
"uvm.refimpl.nat" -> DEBUG)
preloadBundles("tests/uvm-refimpl-test/native-callback-tests.uir")
val libCallbacktest = NativeLibraryTestHelper.loadTestLibrary("callbacktest")
"The exposing definition" should "allow a native function to call it" in {
val ca = microVM.newClientAgent()
val hCB = ca.putExpFunc("@square.exposed")
val hCBAddr = ca.toPointer(hCB)
println("Callback address: 0x%x".format(hCBAddr))
hCBAddr should not be 0L
val nativeFuncAddr = libCallbacktest.getSymbolAddress("one_level")
assert(nativeFuncAddr != 0L)
val hFP = ca.putPointer("@one_level.fp", nativeFuncAddr)
val nativeFuncGlobal = ca.putGlobal("@one_level.global")
ca.store(MemoryOrder.SEQ_CST, nativeFuncGlobal, hFP)
val muFunc = ca.putFunction("@one_level_test")
var callbackCalled: Int = 0
testFunc(ca, muFunc, Seq()) { (ca, th, st, wp) =>
ca.nameOf(ca.currentInstruction(st, 0)) match {
case "@square.v1.trap" => {
val Seq(value, cok) = ca.dumpKeepalives(st, 0)
ca.toDouble(value) shouldBe (callbackCalled match {
case 0 => 3.0
case 1 => 4.0
})
ca.toInt(cok).toLong shouldBe 42L
callbackCalled += 1
TrapRebindPassVoid(st)
}
case "@one_level_test.v1.pretrap" => {
val Seq(fp) = ca.dumpKeepalives(st, 0)
ca.toPointer(fp) shouldEqual nativeFuncAddr
TrapRebindPassVoid(st)
}
case "@one_level_test.v1.trap" => {
val Seq(fp, rv) = ca.dumpKeepalives(st, 0)
ca.toPointer(fp) shouldEqual nativeFuncAddr
ca.toDouble(rv) shouldEqual 25.0
TrapRebindPassVoid(st)
}
}
}
ca.close()
}
}
\ No newline at end of file
......@@ -81,7 +81,7 @@ class NativeStackKeeperTest extends FlatSpec with Matchers with ExtraMatchers {
val mockMuCallbackFunc = new MFunc()
mockMuCallbackFunc.sig = dtdSig
val mockClosAddr = nch.exposeFunc(mockMuCallbackFunc, 42L)
val mockClosAddr = nch.exposeFunc(mockMuCallbackFunc, 42L, true)
mockClosAddr should not be 0
......
.typedef @i1 = int<1>
.typedef @i6 = int<6>
.typedef @i8 = int<8>
.typedef @i16 = int<16>
.typedef @i32 = int<32>
.typedef @i52 = int<52>
.typedef @i64 = int<64>
.typedef @float = float
.typedef @double = double
.typedef @void = void
.funcsig @noparamsnoret = @void ()
.typedef @funcdumb = func<@noparamsnoret>
.typedef @thread = thread
.typedef @stack = stack
.typedef @tagref64 = tagref64
.const @TRUE <@i64> = 1
.const @FALSE <@i64> = 0
.const @I32_0 <@i32> = 0
.const @I32_1 <@i32> = 1
.const @I32_2 <@i32> = 2
.const @I32_3 <@i32> = 3
.const @I32_4 <@i32> = 4
.const @I32_5 <@i32> = 5
.const @I32_6 <@i32> = 6
.const @I32_7 <@i32> = 7
.const @I64_0 <@i64> = 0
.const @I64_1 <@i64> = 1
.const @I64_2 <@i64> = 2
.const @I64_3 <@i64> = 3
.const @I64_4 <@i64> = 4
.const @I64_5 <@i64> = 5
.const @I64_6 <@i64> = 6
.const @I64_7 <@i64> = 7
.const @F_0 <@float> = 0.0f
.const @F_1 <@float> = 1.0f
.const @F_2 <@float> = 2.0f
.const @F_3 <@float> = 3.0f
.const @F_4 <@float> = 4.0f
.const @F_5 <@float> = 5.0f
.const @F_6 <@float> = 6.0f
.const @F_7 <@float> = 7.0f
.const @D_0 <@double> = 0.0d
.const @D_1 <@double> = 1.0d
.const @D_2 <@double> = 2.0d
.const @D_3 <@double> = 3.0d
.const @D_4 <@double> = 4.0d
.const @D_5 <@double> = 5.0d
.const @D_6 <@double> = 6.0d
.const @D_7 <@double> = 7.0d
.typedef @4xfloat = vector <@float 4>
.typedef @4xi32 = vector <@i32 4>
.typedef @2xdouble = vector <@double 2>
.const @4xI32_V1 <@4xi32> = VEC {@I32_0 @I32_1 @I32_2 @I32_3}
.const @4xI32_V2 <@4xi32> = VEC {@I32_4 @I32_5 @I32_6 @I32_7}
.const @4xF_V1 <@4xfloat> = VEC {@F_0 @F_1 @F_2 @F_3}
.const @4xF_V2 <@4xfloat> = VEC {@F_4 @F_5 @F_6 @F_7}
.const @2xD_V1 <@2xdouble> = VEC {@D_0 @D_1}
.const @2xD_V2 <@2xdouble> = VEC {@D_2 @D_3}
.funcsig @i_i = @i64 (@i64)
.funcsig @i_ii = @i64 (@i64 @i64)
.typedef @refvoid = ref<@void>
.typedef @irefvoid = iref<@void>
.typedef @weakrefvoid = weakref<@void>
.const @NULLREF <@refvoid> = NULL
.const @NULLIREF <@irefvoid> = NULL
.const @NULLFUNC <@funcdumb> = NULL
.const @NULLSTACK <@stack> = NULL
.typedef @refi8 = ref<@i8>
.typedef @refi16 = ref<@i16>
.typedef @refi32 = ref<@i32>
.typedef @refi64 = ref<@i64>
.typedef @reffloat = ref<@float>
.typedef @refdouble = ref<@double>
.typedef @irefi8 = iref<@i8>
.typedef @irefi16 = iref<@i16>
.typedef @irefi32 = iref<@i32>
.typedef @irefi64 = iref<@i64>
.typedef @ireffloat = iref<@float>
.typedef @irefdouble= iref<@double>
.const @NULLREF_I64 <@refi64> = NULL
.const @NULLIREF_I64 <@irefi64> = NULL
.typedef @ptrvoid = ptr<@void>
.typedef @ptri8 = ptr<@i8>
.typedef @ptri16 = ptr<@i16>
.typedef @ptri32 = ptr<@i32>
.typedef @ptri64 = ptr<@i64>
.typedef @ptrfloat = ptr<@float>
.typedef @ptrdouble = ptr<@double>
.typedef @ptrptrvoid = ptr<@ptrvoid>
.typedef @ptrfpnoparamsnoret = ptr<@fpnoparamsnoret>
.typedef @ptrfpi_i = ptr<@fpi_i>
.typedef @fpnoparamsnoret = funcptr<@noparamsnoret>
.typedef @fpi_i = funcptr<@i_i>
.typedef @fpi_ii = funcptr<@i_ii>
.funcsig @v_a = @void (@i64)
.funcsig @getpid_sig = @i32 ()
.typedef @getpid_fp = funcptr<@getpid_sig>
.funcdef @getpidtest VERSION @getpidtest_v1 <@v_a> (%p0) {
%entry:
%fp = PTRCAST <@i64 @getpid_fp> %p0
%rv = CCALL #DEFAULT <@getpid_fp @getpid_sig> %fp ()
%trap = TRAP <@void> KEEPALIVE (%fp %rv)
COMMINST @uvm.thread_exit
}
.typedef @size_t = int<64>
.funcsig @write_sig = @size_t (@i32 @ptrvoid @size_t)
.typedef @write_fp = funcptr<@write_sig>
.typedef @CharBuf = hybrid<@i64 @i8>
.const @I8_H <@i8> = 0x48
.const @I8_e <@i8> = 0x65
.const @I8_l <@i8> = 0x6c
.const @I8_o <@i8> = 0x6f
.const @I8_NL <@i8> = 0x0a
.funcsig @DoubleToDouble.sig = @double (@double)
.typedef @DoubleToDouble.fp = funcptr<@DoubleToDouble.sig>
.funcsig @one_level.sig = @double (@double @DoubleToDouble.fp)
.typedef @one_level.fp = funcptr<@one_level.sig>
.funcdef @square VERSION @square.v1 <@DoubleToDouble.sig> (%value) {
%entry:
%cok = COMMINST @uvm.native.get_cookie
%rv = FMUL <@double> %value %value
%trap = TRAP <@void> KEEPALIVE (%value %cok)
RET <@double> %rv
}
.const @I64_42 <@i64> = 42
.expose @square.exposed = @square #DEFAULT @I64_42
.global @one_level.global <@one_level.fp>
.funcdef @one_level_test VERSION @one_level_test.v1 <@noparamsnoret> () {
%entry:
%fp = LOAD <@one_level.fp> @one_level.global
%pretrap = TRAP <@void> KEEPALIVE (%fp)
%rv = CCALL #DEFAULT <@one_level.fp @one_level.sig> %fp (@D_3 @square.exposed)
%trap = TRAP <@void> KEEPALIVE (%fp %rv)
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