GitLab will continue to be upgraded from 11.4.5-ce.0 on November 25th 2019 at 4.00pm (AEDT) to 5.00pm (AEDT) due to Critical Security Patch Availability. During the update, GitLab and Mattermost services will not be available.

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)
}