Commit d5bf3607 authored by Kunshan Wang's avatar Kunshan Wang

Use manual memory manager when possible.

This will fix the problem that some memory is reclaimed by the GC before
the intended end-of-life. Now many classes implement the AutoCloseable
interface and manage their resources in the C++ style.
parent 50a5bd46
......@@ -25,6 +25,7 @@ import uvm.utils.WithUtils.tryWithResource
import uvm.refimpl.bootimg.PrimordialInfo
import com.typesafe.scalalogging.Logger
import org.slf4j.LoggerFactory
import uvm.refimpl.cmdline.NativeArgv
object MicroVM {
val logger = Logger(LoggerFactory.getLogger(getClass.getName))
......@@ -58,7 +59,7 @@ object MicroVM {
}
}
class MicroVM private (val vmConf: VMConf, val allArgs: Option[Seq[String]]) extends IRBuilderListener {
class MicroVM private (val vmConf: VMConf, val allArgs: Option[Seq[String]]) extends IRBuilderListener with AutoCloseable {
// implicitly injected resources
private implicit val microVM = this
......@@ -101,6 +102,8 @@ class MicroVM private (val vmConf: VMConf, val allArgs: Option[Seq[String]]) ext
val nativeLibraryHolder = NativeLibraryHolder(vmConf.extraLibs: _*)
implicit val nativeCallHelper = new NativeCallHelper()
var maybeNativeArgv: Option[NativeArgv] = None
val threadStackManager = new ThreadStackManager()
......@@ -242,4 +245,11 @@ class MicroVM private (val vmConf: VMConf, val allArgs: Option[Seq[String]]) ext
loadBootImage(fileName)
}
}
override def close(): Unit = {
maybeNativeArgv.foreach(_.close())
threadStackManager.close()
nativeLibraryHolder.close()
memoryManager.close()
}
}
\ No newline at end of file
......@@ -339,6 +339,7 @@ class BootImageLoader(file: String, maybeAllArgs: Option[Seq[String]])(implicit
val stack = microVM.threadStackManager.newStack(func, mutator)
logger.info("All arguments: %s".format(allArgs))
val nativeArgv = new NativeArgv(allArgs)
microVM.maybeNativeArgv = Some(nativeArgv)
val argcBox = BoxInt(nativeArgv.argc)
val argvBox = BoxPointer(nativeArgv.argv)
val htr = HowToResume.PassValues(Seq(argcBox, argvBox))
......
......@@ -3,7 +3,7 @@ package uvm.refimpl.cmdline
import uvm.refimpl.WORD_SIZE_BYTES
import uvm.refimpl.nat.NativeSupport
class NativeArgv(args: Seq[String]) {
class NativeArgv(args: Seq[String]) extends AutoCloseable {
val argc = args.size
private val argvSB = new StringBuilder()
......@@ -14,9 +14,11 @@ class NativeArgv(args: Seq[String]) {
argvSB ++= arg += '\0'
}
// These two buffers are allocated natively and are freed by their finalizers. Keep references to them while using.
val cArgv = NativeSupport.jnrMemoryManager.allocateDirect(argvOffs.length * WORD_SIZE_BYTES.toInt)
val cArgvBuf = NativeSupport.jnrMemoryManager.allocateDirect(argvSB.length)
val cArgvMem = NativeSupport.allocateManual(argvOffs.length * WORD_SIZE_BYTES.toInt)
val cArgvBufMem = NativeSupport.allocateManual(argvSB.length)
val cArgv = cArgvMem.asJnrPointer()
val cArgvBuf = cArgvBufMem.asJnrPointer()
for (i <- 0 until argvOffs.length) {
cArgv.putAddress(i * WORD_SIZE_BYTES, cArgvBuf.address() + argvOffs(i))
......@@ -26,4 +28,9 @@ class NativeArgv(args: Seq[String]) {
}
val argv = cArgv.address()
override def close(): Unit = {
cArgvBufMem.close()
cArgvMem.close()
}
}
\ No newline at end of file
......@@ -69,9 +69,9 @@ object RunMu {
val allArgs = bootImg :: appArgs
val microVM = MicroVM(VMConf(props.toMap), allArgs)
microVM.execute()
tryWithResource(MicroVM(VMConf(props.toMap), allArgs)) { microVM =>
microVM.execute()
}
}
private val DOCSTRING = """USAGE: runmu.sh --prop1=value1 --prop2=value2 ... [--] bootimg [args...]
......
......@@ -18,8 +18,12 @@ object ThreadStackManager {
* The manager of all Mu threads and stacks. Also responsible for the actual execution of Mu IR code, i.e. as the "boss"
* of all InterpreterThread instances.
*/
class ThreadStackManager(implicit microVM: MicroVM, nativeCallHelper: NativeCallHelper) {
class ThreadStackManager(implicit microVM: MicroVM, nativeCallHelper: NativeCallHelper) extends AutoCloseable {
import ThreadStackManager._
override def close(): Unit = {
stackRegistry.values.foreach(_.close())
}
val stackRegistry = new IDObjectKeeper[InterpreterStack]("stack")
val threadRegistry = new IDObjectKeeper[InterpreterThread]("thread")
......
......@@ -34,7 +34,10 @@ object FrameState {
* Implements a Mu Stack. Contains both Mu frames and native frames.
*/
class InterpreterStack(val id: MuInternalID, val stackMemory: StackMemory, stackBottomFunc: Function)(
implicit nativeCallHelper: NativeCallHelper, microVM: MicroVM) extends HasID {
implicit nativeCallHelper: NativeCallHelper, microVM: MicroVM) extends HasID with AutoCloseable {
override def close(): Unit = kill()
var gcMark: Boolean = false // Mark for GC.
private var _top: InterpreterFrame = InterpreterFrame.forMuFunc(stackMemory.top, stackBottomFunc, 0L, None)
......
......@@ -14,7 +14,7 @@ object MemoryManager {
val logger = Logger(LoggerFactory.getLogger(getClass.getName))
}
class MemoryManager(val vmConf: VMConf)(implicit microVM: MicroVM) {
class MemoryManager(val vmConf: VMConf)(implicit microVM: MicroVM) extends AutoCloseable {
import MemoryManager._
logger.info("sosSize=%d, losSize=%d, globalSize=%d, stackSize=%d".format(
......@@ -56,4 +56,8 @@ class MemoryManager(val vmConf: VMConf)(implicit microVM: MicroVM) {
val stackMemory = new StackMemory(objRef, vmConf.stackSize)
stackMemory
}
override def close(): Unit = {
memorySupport.close()
}
}
......@@ -2,7 +2,6 @@ package uvm.refimpl.mem
import scala.annotation.implicitNotFound
import jnr.ffi.Memory
import uvm._
import uvm.refimpl._
import uvm.refimpl.integerize._
......@@ -12,7 +11,11 @@ import uvm.ssavariables.AtomicRMWOptr._
/**
* Support for native memory access. Backed by JNR-FFI.
*/
class MemorySupport(val muMemorySize: Word) {
class MemorySupport(val muMemorySize: Word) extends AutoCloseable {
override def close(): Unit = {
muMemory.close()
}
val jnrRuntime = NativeSupport.jnrRuntime
val theMemory = NativeSupport.theMemory
......@@ -24,8 +27,8 @@ class MemorySupport(val muMemorySize: Word) {
" Due to the limitation of JNR-FFI, the maximum available memory size is %d bytes.").format(muMemorySize, SIZE_LIMIT))
}
val muMemory = Memory.allocateDirect(jnrRuntime, muMemorySize.toInt, true)
val muMemoryBegin = muMemory.address()
val muMemory = NativeSupport.allocateManual(muMemorySize.toInt)
val muMemoryBegin = muMemory.address
val muMemoryEnd = muMemoryBegin + muMemorySize
def isInMuMemory(addr: Word): Boolean = muMemoryBegin <= addr && addr < muMemoryEnd
......
......@@ -3,6 +3,8 @@ package uvm.refimpl.nat
import com.kenai.jffi.ClosureManager
import jnr.ffi.provider.jffi.NativeRuntime
import com.kenai.jffi.MemoryIO
import jnr.ffi.Pointer
/**
* Holder of JNR-specific resources.
......@@ -15,4 +17,23 @@ object NativeSupport {
// This is from JFFI, not JNR-FFI.
val jffiClosureManager = ClosureManager.getInstance()
// Explicit memory management
val IO = MemoryIO.getInstance()
def allocateManual(size: Long): ManualMemory = allocateManual(size, true)
def allocateManual(size: Long, clear: Boolean): ManualMemory = {
val addr = IO.allocateMemory(size, clear)
new ManualMemory(addr, size)
}
}
class ManualMemory(val address: Long, val size: Long) extends AutoCloseable {
def asJnrPointer(): Pointer = Pointer.wrap(NativeSupport.jnrRuntime, address, size)
override def close(): Unit = {
NativeSupport.IO.freeMemory(address)
}
}
\ No newline at end of file
......@@ -20,6 +20,7 @@ import jnr.ffi.Pointer
import uvm.ir.irbuilder.IRBuilder
import uvm.refimpl._
import uvm.ssavariables._
import uvm.utils.WithUtils._
object NativeTrapHandler {
val jffiInvoker = Invoker.getInstance
......@@ -85,53 +86,56 @@ class NativeTrapHandler(val funcAddr: MuTrapHandlerFP, val userdata: MuCPtr) ext
// 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)
}
val scalaResult: TrapHandlerResult = tryWithResource(NativeSupport.allocateManual((7L * WORD_SIZE_BYTES).toInt)) { outBufMem => // output values are received here.
val outBuf = outBufMem.asJnrPointer
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 CDefs.MU_THREAD_EXIT => TrapHandlerResult.ThreadExit()
case CDefs.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)
// 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)
result match {
case CDefs.MU_THREAD_EXIT => TrapHandlerResult.ThreadExit()
case CDefs.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 CDefs.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]
val freerFP = outBuf.getAddress(4 * WORD_SIZE_BYTES)
if (freerFP != 0L) {
val freerData = outBuf.getAddress(5 * WORD_SIZE_BYTES)
callFreer(freerFP, valuesPtr, freerData)
TrapHandlerResult.Rebind(newStack, HowToResume.ThrowExc(excVal))
}
val values = valueFaks.map(getMuValueNotNull)
TrapHandlerResult.Rebind(newStack, HowToResume.PassValues(values))
}
case CDefs.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))
}
}
......@@ -148,7 +152,8 @@ class NativeTrapHandler(val funcAddr: MuTrapHandlerFP, val userdata: MuCPtr) ext
object MuErrorNumber {
val MU_NATIVE_ERRNO = 6481626 // muErrno is set to this number if an exception is thrown
val muErrorPtr = jnrMemoryManager.allocateDirect(16)
val muErrorMem = NativeSupport.allocateManual(16)
val muErrorPtr = muErrorMem.asJnrPointer()
def getMuError(): Int = muErrorPtr.getInt(0)
def setMuError(errno: Int): Unit = muErrorPtr.putInt(0, errno)
......
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