Commit 25c803ec authored by Kunshan Wang's avatar Kunshan Wang

Object pinning

parent b2e215ec
......@@ -24,6 +24,9 @@ class MicroVM(heapSize: Word = MicroVM.DEFAULT_HEAP_SIZE,
val globalBundle = new Bundle()
val constantPool = new ConstantPool()
val memoryManager = new MemoryManager(heapSize, globalSize, stackSize)
private implicit val memorySupport = memoryManager.memorySupport
val threadStackManager = new ThreadStackManager()
val trapManager = new TrapManager()
val clientAgents = new HashSet[ClientAgent]()
......@@ -58,8 +61,12 @@ class MicroVM(heapSize: Word = MicroVM.DEFAULT_HEAP_SIZE,
/**
* Create a new ClientAgent.
*/
def newClientAgent(): ClientAgent = new ClientAgent(this)
def newClientAgent(): ClientAgent = {
val mutator = microVM.memoryManager.heap.makeMutator() // This may trigger GC
val ca = new ClientAgent(mutator)
clientAgents.add(ca)
ca
}
/**
* Given a name, get the ID of an identified entity.
*/
......
......@@ -9,6 +9,7 @@ import uvm.ssavariables.MemoryOrder._
import uvm.ssavariables.AtomicRMWOptr._
import uvm.refimpl.mem._
import uvm.ssavariables.HasKeepAliveClause
import scala.collection.mutable.ArrayBuffer
case class Handle(ty: Type, vb: ValueBox)
......@@ -26,17 +27,11 @@ trait UndefinedFunctionHandler {
def handleUndefinedFunction(functionID: Int): Unit
}
class ClientAgent(microVM: MicroVM) {
// Injectable resources (used by memory access operations)
private implicit val microVM_ = microVM
private implicit val memorySupport = microVM.memoryManager.memorySupport
class ClientAgent(mutator: Mutator)(
implicit microVM: MicroVM, memorySupport: MemorySupport) extends ObjectPinner {
val handles = new HashSet[Handle]()
microVM.clientAgents.add(this)
val mutator = microVM.memoryManager.heap.makeMutator()
val pinSet = new ArrayBuffer[Word]
def close(): Unit = {
handles.clear()
......@@ -492,6 +487,42 @@ class ClientAgent(microVM: MicroVM) {
microVM.trapManager.disableWatchPoint(wpID)
}
def ptrcast(handle: Handle, newType: Type): Handle = {
require(handle.ty.isInstanceOf[AbstractPointerType] || handle.ty.isInstanceOf[TypeInt], "handle must have type int, ptr or funcptr. %s found".format(handle.ty.repr))
require(newType.isInstanceOf[AbstractPointerType] || newType.isInstanceOf[TypeInt], "can only convert to int, ptr or funcptr. %s found".format(newType.repr))
val addr = handle.ty match {
case TypeInt(n) => OpHelper.trunc(handle.vb.asInstanceOf[BoxInt].value, 64).toLong
case _: AbstractPointerType => handle.vb.asInstanceOf[BoxPointer].addr
}
val box = newType match {
case TypeInt(n) => new BoxInt(OpHelper.trunc(BigInt(addr), n))
case _: AbstractPointerType => new BoxPointer(addr)
}
newHandle(newType, box)
}
def pin(handle: Handle): Handle = {
val (objTy, objRef) = handle.ty match {
case TypeRef(t) => (t, handle.vb.asInstanceOf[BoxRef].objRef)
case TypeIRef(t) => (t, handle.vb.asInstanceOf[BoxIRef].objRef)
}
pin(objRef)
val ptrTy = InternalTypePool.ptrOf(objTy)
val box = new BoxPointer(objRef)
newHandle(ptrTy, box)
}
def unpin(handle: Handle): Unit = {
val (objTy, objRef) = handle.ty match {
case TypeRef(t) => (t, handle.vb.asInstanceOf[BoxRef].objRef)
case TypeIRef(t) => (t, handle.vb.asInstanceOf[BoxIRef].objRef)
}
unpin(objRef)
}
// Internal methods for the micro VM
def putThread(thr: Option[InterpreterThread]): Handle = {
......
......@@ -138,6 +138,14 @@ object TypeInferer {
case "@uvm.futex.wake" => I32
case "@uvm.futex.cmp_requeue" => I32
case "@uvm.kill_dependency" => i.typeList(0)
case "@uvm.native.pin" => i.typeList(0) match{
case TypeRef(t) => ptrOf(t)
case TypeIRef(t) => ptrOf(t)
}
case "@uvm.native.unpin" => VOID
case "@uvm.native.expose" => funcPtrOf(i.funcSigList(0))
case "@uvm.native.unexpose" => VOID
case "@uvm.native.get_cookie" => I64
}
}
}
\ No newline at end of file
package uvm.refimpl.itpr
import scala.annotation.tailrec
import scala.collection.mutable.ArrayBuffer
import org.slf4j.LoggerFactory
import com.typesafe.scalalogging.Logger
import uvm._
import uvm.types._
import uvm.ssavariables._
import uvm.comminsts._
import uvm.refimpl._
import uvm.refimpl.mem._
import TypeSizes.Word
import scala.annotation.tailrec
import org.slf4j.LoggerFactory
import com.typesafe.scalalogging.Logger
import uvm.refimpl.mem.TypeSizes.Word
import uvm.ssavariables._
import uvm.types._
object InterpreterThread {
val logger = Logger(LoggerFactory.getLogger(getClass.getName))
}
class InterpreterThread(val id: Int, implicit private val microVM: MicroVM, initialStack: InterpreterStack, val mutator: Mutator) {
class InterpreterThread(val id: Int, initialStack: InterpreterStack, val mutator: Mutator)(
implicit microVM: MicroVM) extends ObjectPinner {
import InterpreterThread._
// Injectable resources (used by memory access instructions)
......@@ -32,6 +37,9 @@ class InterpreterThread(val id: Int, implicit private val microVM: MicroVM, init
/** True if the thread is waiting in a Futex waiting queue. */
var isFutexWaiting: Boolean = false
/** Object-pinnning multiset. */
val pinSet = new ArrayBuffer[Word]
// Initialisation
rebindPassVoid(initialStack)
......@@ -1053,6 +1061,34 @@ class InterpreterThread(val id: Int, implicit private val microVM: MicroVM, init
continueNormally()
}
case "@uvm.native.pin" => {
val Seq(ty) = typeList
val Seq(r) = argList
val addr = ty match {
case TypeRef(_) => boxOf(r).asInstanceOf[BoxRef].objRef
case TypeIRef(_) => boxOf(r).asInstanceOf[BoxIRef].objRef
}
pin(addr)
boxOf(i).asInstanceOf[BoxPointer].addr = addr
continueNormally()
}
case "@uvm.native.unpin" => {
val Seq(ty) = typeList
val Seq(r) = argList
val addr = ty match {
case TypeRef(_) => boxOf(r).asInstanceOf[BoxRef].objRef
case TypeIRef(_) => boxOf(r).asInstanceOf[BoxIRef].objRef
}
unpin(addr)
continueNormally()
}
// Insert more CommInsts here.
case ciName => {
......
package uvm.refimpl.itpr
import scala.collection.mutable.ArrayBuffer
import uvm.refimpl.mem.Mutator
import uvm.refimpl.mem.TypeSizes.Word
import uvm.refimpl.UvmRuntimeException
/**
* Trait for entities (threads and client agents) that can pin objects. GC can also scan such entities.
*/
trait ObjectPinner {
/** Multi-set of pinned object references. May contain NULL. */
def pinSet: ArrayBuffer[Word]
def pin(addr: Word) {
pinSet += addr
}
def unpin(addr: Word) {
val index = pinSet.lastIndexOf(addr)
if (index == -1) {
throw new UvmRuntimeException("Attempt to unpin object at %d (0x%x), which is not pinned.".format(addr, addr))
}
pinSet.remove(index)
}
}
\ No newline at end of file
......@@ -59,7 +59,7 @@ class ThreadStackManager(implicit microVM: MicroVM) {
def newThread(stack: InterpreterStack): InterpreterThread = {
val mutator = microVM.memoryManager.makeMutator()
val id = makeThreadID()
val thr = new InterpreterThread(id, microVM, stack, mutator)
val thr = new InterpreterThread(id, stack, mutator)
threadRegistry.put(id, thr)
thr
}
......
......@@ -4,15 +4,10 @@ import com.typesafe.scalalogging.StrictLogging
import TypeSizes._
object MemUtils extends StrictLogging {
def zeroRegion(start: Word, length: Word)(implicit memorySupport: MemorySupport) {
val end = start + length
logger.debug("Zeroing [0x%x -> 0x%x] %d bytes".format(start, end, length))
var a = start
while (a < end) {
memorySupport.storeLong(a, 0L)
a += WORD_SIZE_BYTES
}
memorySupport.memset(start, length, 0)
}
def memcpy(src: Word, dst: Word, length: Word)(implicit memorySupport: MemorySupport) {
......
......@@ -128,4 +128,8 @@ class MemorySupport(val muMemorySize: Word) {
storeI128(addr, desired, inMu)
return oldVal
}
def memset(addr: Word, size: Word, value: Byte): Unit = {
theMemory.setMemory(addr, size, value)
}
}
......@@ -28,6 +28,8 @@ class AllScanner(val handler: RefFieldHandler)(
}
private def traceRoots() {
logger.debug("Tracing pin sets...")
tracePinSets()
logger.debug("Tracing external roots...")
traceClientAgents()
logger.debug("Tracing globals...")
......@@ -36,6 +38,23 @@ class AllScanner(val handler: RefFieldHandler)(
traceThreads()
}
private def tracePinSets() {
logger.debug(s"Tracing client agents for pinned objects")
for (ca <- microVM.clientAgents) {
assert(ca != null)
assert(ca.pinSet != null)
for (addr <- ca.pinSet) {
this.pinSetToMem(addr)
}
}
for (thr <- microVM.threadStackManager.iterateAllLiveThreads) {
logger.debug(s"Tracing live thread ${thr.id} for pined objects")
for (addr <- thr.pinSet) {
this.pinSetToMem(addr)
}
}
}
private def traceClientAgents() {
for (ca <- microVM.clientAgents; h <- ca.handles) {
h.vb match {
......@@ -131,4 +150,10 @@ class AllScanner(val handler: RefFieldHandler)(
rv
}
override def pinSetToMem(toObj: Word): Option[Word] = {
val rv = handler.pinSetToMem(toObj)
rv.foreach(addrQueue.add)
rv
}
}
\ No newline at end of file
......@@ -32,6 +32,8 @@ trait RefFieldHandler {
def stackToStackMem(stack: InterpreterStack, toObj: Word): Option[Word]
/** An InterpreterThread referring to its stack. GC cannot rebind stacks. */
def threadToStack(thread: InterpreterThread, toStack: Option[InterpreterStack]): Option[InterpreterStack]
/** Pin set referring to the memory. Pinned objects cannot be moved. */
def pinSetToMem(toObj: Word): Option[Word]
}
object RefFieldUpdater {
......
......@@ -81,6 +81,9 @@ class SimpleImmixCollector(val heap: SimpleImmixHeap, val space: SimpleImmixSpac
maybeMarkStackIfSome(toStack)
}
override def pinSetToMem(toObj: Word): Option[Word] = {
maybeMarkAndStatIfNotNull(toObj)
}
})
s1.scanAll()
......@@ -220,6 +223,14 @@ class SimpleImmixCollector(val heap: SimpleImmixHeap, val space: SimpleImmixSpac
maybeMarkStackIfSome(toStack)
}
override def pinSetToMem(toObj: Word): Option[Word] = {
if (space.isInSpace(toObj)) {
logger.debug("Object 0x%x is in small object space and is pinned. Marking block %d".format(toObj, space.objRefToBlockIndex(toObj)))
space.markBlockByObjRef(toObj, true)
}
maybeMoveIfNotNull(toObj, _ => throw new UvmRefImplException("Pinned object cannot move."))
}
private def maybeMoveIfNotNull(toObj: Word, updateFunc: Word => Unit): Option[Word] = {
if (toObj != 0L) maybeMove(toObj, updateFunc) else None
}
......@@ -243,8 +254,13 @@ class SimpleImmixCollector(val heap: SimpleImmixHeap, val space: SimpleImmixSpac
val isMovable = if (isInSmallObjectSpace) {
val pageNum = space.objRefToBlockIndex(toObj)
if (space.isPinned(pageNum)) {
logger.debug("Object 0x%x is in pinned page %d, cannot move.".format(toObj, pageNum))
false
} else {
val stat = space.getStat(pageNum)
if (stat < threshold) true else false
}
} else {
false
}
......@@ -351,6 +367,10 @@ class SimpleImmixCollector(val heap: SimpleImmixHeap, val space: SimpleImmixSpac
override def threadToStack(thread: InterpreterThread, toStack: Option[InterpreterStack]): Option[InterpreterStack] = {
clearStackMarkIfSome(toStack)
}
override def pinSetToMem(toObj: Word): Option[Word] = {
clearMarkIfNotNull(toObj)
}
}
private def clearMarkIfNotNull(objRef: Long): Option[Word] = {
......
......@@ -13,6 +13,7 @@ object SimpleImmixSpace {
val BLOCK_MARKED = 0x1
val BLOCK_RESERVED = 0x2
val BLOCK_PINNED = 0x4
private val LINE_SIZE = 128L
......@@ -124,16 +125,19 @@ class SimpleImmixSpace(val heap: SimpleImmixHeap, name: String, begin: Word, ext
((blockAddr - begin) / BLOCK_SIZE).toInt
}
def markBlockByIndex(index: Int) {
blockFlags(index) |= BLOCK_MARKED
def markBlockByIndex(index: Int, pin: Boolean = false) {
val addMark = if (pin) BLOCK_MARKED | BLOCK_PINNED else BLOCK_MARKED
blockFlags(index) |= addMark
}
def markBlockByObjRef(objRef: Word) {
def markBlockByObjRef(objRef: Word, pin: Boolean = false) {
val blockIndex = objRefToBlockIndex(objRef)
markBlockByIndex(blockIndex)
logger.debug(s"Marked block ${blockIndex}")
markBlockByIndex(blockIndex, pin)
logger.debug(s"Marked block ${blockIndex}. pin=${pin}")
}
def isPinned(pageNum: Int): Boolean = (blockFlags(pageNum) & BLOCK_MARKED) != 0
def collectBlocks(): Boolean = {
// Shift defrag reserved blocks to the beginning;
for (i <- nextResv until defragResvFree) {
......@@ -144,7 +148,7 @@ class SimpleImmixSpace(val heap: SimpleImmixHeap, name: String, begin: Word, ext
var newNFree = 0
for (i <- 0 until nBlocks) {
var flag = blockFlags(i)
val bits = (flag & (BLOCK_MARKED | BLOCK_RESERVED))
val bits = (flag & (BLOCK_MARKED | BLOCK_RESERVED | BLOCK_PINNED))
if (bits == 0) {
if (newDefragResvFree < nReserved) {
defragResv(newDefragResvFree) = i
......@@ -158,7 +162,7 @@ class SimpleImmixSpace(val heap: SimpleImmixHeap, name: String, begin: Word, ext
} else {
logger.debug(s"Block ${i} is not freed because flag bits is ${bits}")
}
flag &= ~BLOCK_MARKED
flag &= ~(BLOCK_MARKED | BLOCK_PINNED)
blockFlags(i) = flag
}
defragResvFree = newDefragResvFree
......
......@@ -1389,4 +1389,33 @@ class UvmInterpreterSpec extends UvmBundleTesterBase {
ca.close()
}
"COMMINST @uvm.native.pin and @uvm.native.unpin" should "expose ref/iref as ptr for access" in {
val ca = microVM.newClientAgent()
val func = ca.putFunction("@objectpinning")
testFunc(ca, func, Seq()) { (ca, th, st, wp) =>
val Seq(a, b, c, d, e, f) = ca.dumpKeepalives(st, 0)
val aAddr = a.vb.asRef
val (bObj, bOff) = b.vb.asIRef
val cAddr = c.vb.asPointer
val dAddr = d.vb.asPointer
aAddr shouldEqual cAddr
bObj shouldEqual dAddr
cAddr shouldEqual dAddr
e.vb.asSInt(64) shouldEqual 42
f.vb.asSInt(64) shouldEqual 42
val thr = th.vb.asThread.get
thr.pinSet should contain(cAddr)
TrapRebindPassVoid(st)
}
ca.close()
}
}
\ No newline at end of file
package uvm.refimpl.mem
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 scala.collection.mutable.ArrayBuffer
import uvm.refimpl.mem.TypeSizes.Word
import ch.qos.logback.classic.Level._
class ObjectPinningTest extends UvmBundleTesterBase {
setLogLevels(
ROOT_LOGGER_NAME -> INFO,
"uvm.refimpl.itpr" -> INFO,
"uvm.refimpl.mem" -> INFO,
"uvm.refimpl.mem.simpleimmix.SimpleImmixCollector$" -> DEBUG)
override def makeMicroVM() = new MicroVM(heapSize = 512L * 1024L)
def gc() = microVM.memoryManager.heap.mutatorTriggerAndWaitForGCEnd(false)
microVM.memoryManager.heap.space.debugLogBlockStates()
preloadBundles("tests/uvm-refimpl-test/uvm-mem-test-bundle.uir")
microVM.memoryManager.heap.space.debugLogBlockStates()
behavior of "The garbage collector"
it should "not collect pinned objects." in {
val ca = microVM.newClientAgent()
val hRefsKeep = new ArrayBuffer[Handle]
val hRefsPin = new ArrayBuffer[Handle]
val hPtrsPin = new ArrayBuffer[Handle]
val addrsPin = new ArrayBuffer[Word]
val keepPer = 1000
val pinPer = 6000
for (i <- 0 until 20000) {
val hRef = ca.newFixed("@i64")
if (i % keepPer == 0) {
hRefsKeep += hRef
if (i % pinPer == 0) {
val hPtr = ca.pin(hRef)
val addr = ca.toPointer(hPtr)
hRefsPin += hRef
hPtrsPin += hPtr
addrsPin += addr
}
} else {
ca.deleteHandle(hRef)
}
}
gc()
val addrs2Pin = hRefsPin.map { h =>
val hPtr2 = ca.pin(h)
val addr2 = ca.toPointer(hPtr2)
addr2
}
addrsPin shouldEqual addrs2Pin
for (hRef <- hRefsPin) {
ca.unpin(hRef)
ca.unpin(hRef)
}
gc()
for (hRef <- hRefsKeep) {
ca.deleteHandle(hRef)
}
gc()
ca.close()
}
}
\ No newline at end of file
......@@ -1014,3 +1014,24 @@
TRAP <@void> KEEPALIVE (%b)
COMMINST @uvm.thread_exit
}
.funcdef @objectpinning VERSION @objectpinning_v1 <@noparamsnoret> () {
%entry:
%a = NEW <@i64>
%b = GETIREF <@i64> %a
%c = COMMINST @uvm.native.pin <@refi64> (%a)
%d = COMMINST @uvm.native.pin <@irefi64> (%b)
STORE PTR <@i64> %c @I64_42
%e = LOAD PTR <@i64> %d
%f = LOAD <@i64> %b
TRAP <@void> KEEPALIVE (%a %b %c %d %e %f)
COMMINST @uvm.native.unpin <@refi64> (%a)
COMMINST @uvm.native.unpin <@irefi64> (%b)
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