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

Object pinning

parent b2e215ec
......@@ -15,21 +15,24 @@ object MicroVM {
}
class MicroVM(heapSize: Word = MicroVM.DEFAULT_HEAP_SIZE,
globalSize: Word = MicroVM.DEFAULT_GLOBAL_SIZE,
stackSize: Word = MicroVM.DEFAULT_STACK_SIZE) {
globalSize: Word = MicroVM.DEFAULT_GLOBAL_SIZE,
stackSize: Word = MicroVM.DEFAULT_STACK_SIZE) {
// implicitly injected resources
private implicit val microVM = this
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]()
val irReader = new UIRTextReader(new IDFactory())
{
// The micro VM allocates stacks on the heap in the large object space. It is represented as a bug chunk of byte array.
// So the GC must know about this type because the GC looks up the globalBundle for types.
......@@ -54,17 +57,21 @@ class MicroVM(heapSize: Word = MicroVM.DEFAULT_HEAP_SIZE,
constantPool.addGlobalVar(g)
}
}
/**
* 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.
*/
def idOf(name: String): Int = globalBundle.allNs(name).id
/**
* Given an ID, get the name 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()
......@@ -48,7 +43,7 @@ class ClientAgent(microVM: MicroVM) {
val bundle = microVM.irReader.read(r, microVM.globalBundle)
microVM.addBundle(bundle)
}
def loadBundle(s: String): Unit = {
val bundle = microVM.irReader.read(s, microVM.globalBundle)
microVM.addBundle(bundle)
......@@ -94,7 +89,7 @@ class ClientAgent(microVM: MicroVM) {
val et = t.elemTy.asInstanceOf[TypeDouble]
newHandle(t, BoxVector(vs.map(BoxDouble)))
}
def putPointer(typeID: Int, v: Word): Handle = {
val t = microVM.globalBundle.typeNs(typeID)
newHandle(t, BoxPointer(v))
......@@ -157,7 +152,7 @@ class ClientAgent(microVM: MicroVM) {
def toDoubleVec(h: Handle): Seq[Double] = {
h.vb.asInstanceOf[BoxVector].values.map(b => b.asInstanceOf[BoxDouble].value)
}
def toPointer(h: Handle): Word = {
h.vb.asInstanceOf[BoxPointer].addr
}
......@@ -262,12 +257,12 @@ class ClientAgent(microVM: MicroVM) {
def load(ord: MemoryOrder, loc: Handle): Handle = {
val (ptr, ty) = loc.ty match {
case TypeIRef(t) => (false, t)
case TypePtr(t) => (true, t)
case TypePtr(t) => (true, t)
}
val uty = InternalTypePool.unmarkedOf(ty)
val addr = MemoryOperations.addressOf(ptr, loc.vb)
val nb = ValueBox.makeBoxForType(uty)
val nb = ValueBox.makeBoxForType(uty)
MemoryOperations.load(ptr, uty, addr, nb)
newHandle(uty, nb)
......@@ -276,11 +271,11 @@ class ClientAgent(microVM: MicroVM) {
def store(ord: MemoryOrder, loc: Handle, newVal: Handle): Unit = {
val (ptr, ty) = loc.ty match {
case TypeIRef(t) => (false, t)
case TypePtr(t) => (true, t)
case TypePtr(t) => (true, t)
}
val uty = InternalTypePool.unmarkedOf(ty)
val addr = MemoryOperations.addressOf(ptr, loc.vb)
val nvb = newVal.vb
val nvb = newVal.vb
val nb = ValueBox.makeBoxForType(uty)
MemoryOperations.store(ptr, uty, addr, nvb, nb)
......@@ -289,7 +284,7 @@ class ClientAgent(microVM: MicroVM) {
def cmpXchg(ordSucc: MemoryOrder, ordFail: MemoryOrder, weak: Boolean, loc: Handle, expected: Handle, desired: Handle): (Boolean, Handle) = {
val (ptr, ty) = loc.ty match {
case TypeIRef(t) => (false, t)
case TypePtr(t) => (true, t)
case TypePtr(t) => (true, t)
}
val uty = InternalTypePool.unmarkedOf(ty)
val addr = MemoryOperations.addressOf(ptr, loc.vb)
......@@ -303,7 +298,7 @@ class ClientAgent(microVM: MicroVM) {
def atomicRMW(ord: MemoryOrder, op: AtomicRMWOptr, loc: Handle, opnd: Handle): Handle = {
val (ptr, ty) = loc.ty match {
case TypeIRef(t) => (false, t)
case TypePtr(t) => (true, t)
case TypePtr(t) => (true, t)
}
val uty = InternalTypePool.unmarkedOf(ty)
val addr = MemoryOperations.addressOf(ptr, loc.vb)
......@@ -320,7 +315,7 @@ class ClientAgent(microVM: MicroVM) {
val funcVal = func.vb.asInstanceOf[BoxFunc].func.getOrElse {
throw new UvmRuntimeException("Stack-bottom function must not be NULL")
}
val funcVer = funcVal.versions.headOption.getOrElse {
throw new UvmRuntimeException("Stack-bottom function %s is not defined.".format(funcVal.repr))
}
......@@ -406,12 +401,12 @@ class ClientAgent(microVM: MicroVM) {
val st = getStackNotNull(stack)
val top = st.top
top.prev match {
case None => throw new UvmRuntimeException("Attempting to pop the last frame of a stack.")
case None => throw new UvmRuntimeException("Attempting to pop the last frame of a stack.")
case Some(prev) => st.top = prev
}
}
def pushFrame(stack: Handle, func: Handle, argList: Seq[Handle]): Unit = {
def pushFrame(stack: Handle, func: Handle, argList: Seq[Handle]): Unit = {
val sta = stack.vb.asInstanceOf[BoxStack].stack.getOrElse {
throw new UvmRuntimeException("Stack must not be NULL")
}
......@@ -419,13 +414,13 @@ class ClientAgent(microVM: MicroVM) {
val funcVal = func.vb.asInstanceOf[BoxFunc].func.getOrElse {
throw new UvmRuntimeException("Stack-bottom function must not be NULL")
}
val funcVer = funcVal.versions.headOption.getOrElse {
throw new UvmRuntimeException("Stack-bottom function %s is not defined.".format(funcVal.repr))
}
val argBoxes = argList.map(_.vb)
sta.pushFrame(funcVer, argBoxes)
}
......@@ -483,7 +478,7 @@ class ClientAgent(microVM: MicroVM) {
val box = new BoxTagRef64(OpHelper.refToTr64(refv, tagv.longValue))
newHandle(InternalTypes.TAGREF64, box)
}
def enableWatchPoint(wpID: Int): Unit = {
microVM.trapManager.enableWatchPoint(wpID)
}
......@@ -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)
val stat = space.getStat(pageNum)
if (stat < threshold) true else false
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,15 +125,18 @@ 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;
......@@ -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
......
......@@ -1088,7 +1088,7 @@ class UvmInterpreterSpec extends UvmBundleTesterBase {
}
ca.close()
}
"CMPXCHG and ATOMICRMW" should "work with pointer in good cases" in {
val ca = microVM.newClientAgent()
val func = ca.putFunction("@memAccessingAtomicPtr")
......@@ -1146,7 +1146,7 @@ class UvmInterpreterSpec extends UvmBundleTesterBase {
}
ca.close()
}
"LOAD, STORE, CMPXCHG and ATOMICRMW" should "jump to the exceptional destination on NULL ref access" in {
val ca = microVM.newClientAgent()
val func = ca.putFunction("@memAccessingNull")
......@@ -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()