Commit 217ca466 authored by Kunshan Wang's avatar Kunshan Wang

Fixed a bug in object scanner. Partially implementing WATCHPOINT.

When an object is moved, the new address should be enqueued, not the old
address.
parent 64867984
......@@ -438,6 +438,14 @@ class ClientAgent(microVM: MicroVM) {
newHandle(InternalTypes.TAGREF64, box)
}
def enableWatchPoint(wpID: Int): Unit = {
microVM.trapManager.enableWatchPoint(wpID)
}
def disableWatchPoint(wpID: Int): Unit = {
microVM.trapManager.disableWatchPoint(wpID)
}
// Internal methods for µVM
def putThread(thr: Option[InterpreterThread]): Handle = {
......
......@@ -638,8 +638,6 @@ class InterpreterThread(val id: Int, microVM: MicroVM, initialStack: Interpreter
continueNormally()
}
// Indentation guide: Insert more instructions here.
case i @ InstTrap(retTy, excClause, keepAlives) => {
val ca = microVM.newClientAgent()
......@@ -668,6 +666,39 @@ class InterpreterThread(val id: Int, microVM: MicroVM, initialStack: Interpreter
ca.close()
}
case i @ InstWatchPoint(wpID, retTy, dis, ena, exc, keepAlives) => {
val isEnabled = microVM.trapManager.isWatchPointEnabled(wpID)
if (isEnabled) {
val ca = microVM.newClientAgent()
val hThread = ca.putThread(Some(this))
val hStack = ca.putStack(Some(curStack))
unbind(retTy)
val res = microVM.trapManager.trapHandler.handleTrap(ca, hThread, hStack, wpID)
res match {
case TrapExit() => {
isRunning = false
}
case TrapRebindPassValue(newStack, value) => {
rebindPassValue(newStack.vb.asInstanceOf[BoxStack].stack, value.vb)
}
case TrapRebindPassVoid(newStack) => {
rebindPassVoid(newStack.vb.asInstanceOf[BoxStack].stack)
}
case TrapRebindThrowExc(newStack, exc) => {
rebindThrowExc(newStack.vb.asInstanceOf[BoxStack].stack, exc.vb)
}
}
ca.close()
} else {
branchAndMovePC(dis)
}
}
// Indentation guide: Insert more instructions (after TRAP) here.
case i @ InstCommInst(ci, typeList, argList, excClause, keepAlives) => {
......@@ -726,7 +757,11 @@ class InterpreterThread(val id: Int, microVM: MicroVM, initialStack: Interpreter
def continueNormally(): Unit = {
curInst match {
case wp: InstWatchPoint => {
throw new UvmRefImplException("Not Implemented")
branchAndMovePC(wp.ena)
// NOTE: WatchPoint only "continue normally" when the current stack is rebound with value or void.
// This includes executing a watch point. In any case, this watch point must have been enabled. If the watch
// point is disabled during the course the stack is unbound, this watch point should still continue from the
// destination determined WHEN THIS INSTRUCTION IS EXECUTED.
}
case h: HasExcClause => h.excClause match {
case None => incPC()
......@@ -798,6 +833,10 @@ class InterpreterThread(val id: Int, microVM: MicroVM, initialStack: Interpreter
val (newFrame, newBB) = unwindUntilCatchable(f)
s.top = newFrame
if (exc != 0L) {
logger.debug("Catching exception %d".format(exc))
}
branchAndMovePC(newBB, exc)
}
......
......@@ -8,14 +8,16 @@ import scala.collection.mutable.ArrayBuffer
class ThreadStackManager(microVM: MicroVM) {
val stackRegistry = new HashMap[Int, InterpreterStack]()
private val stackRegistry = new HashMap[Int, InterpreterStack]()
val threadRegistry = new HashMap[Int, InterpreterThread]()
private val threadRegistry = new HashMap[Int, InterpreterThread]()
def getStackByID(id: Int): Option[InterpreterStack] = stackRegistry.get(id)
def getThreadByID(id: Int): Option[InterpreterThread] = threadRegistry.get(id)
def iterateAllLiveStacks: Iterable[InterpreterStack] = stackRegistry.values.filter(_.state != StackState.Dead)
private var nextStackID: Int = 1
private def makeStackID(): Int = { val id = nextStackID; nextStackID += 1; id }
......
package uvm.refimpl.itpr
import uvm.refimpl._
import scala.collection.mutable.HashSet
class TrapManager(microVM: MicroVM) {
var trapHandler: TrapHandler = DefaultTrapHandler
var undefinedFunctionHandler: UndefinedFunctionHandler = DefaultUndefinedFunctionHandler
private val enabledWatchPoints = new HashSet[Int]()
def isWatchPointEnabled(wpID: Int): Boolean = enabledWatchPoints.contains(wpID)
def enableWatchPoint(wpID: Int): Unit = enabledWatchPoints.add(wpID)
def disableWatchPoint(wpID: Int): Unit = enabledWatchPoints.remove(wpID)
object DefaultTrapHandler extends TrapHandler {
def handleTrap(ca: ClientAgent, thread: Handle, stack: Handle, watchPointID: Int): TrapHandlerResult = {
val thr = thread.vb.asInstanceOf[BoxThread].thread.get
......
......@@ -7,7 +7,7 @@ object MemUtils extends StrictLogging {
def zeroRegion(start: Word, length: Word) {
val end = start + length
logger.debug(s"Zeroing [${start} -> ${end}] ${length} bytes")
logger.debug("Zeroing [0x%x -> 0x%x] %d bytes".format(start, end, length))
var a = start
while (a < end) {
MemorySupport.storeLong(a, 0)
......@@ -16,7 +16,7 @@ object MemUtils extends StrictLogging {
}
def memcpy(src: Word, dst: Word, length: Word) {
logger.debug("Copying [${src} -> ${dst}] ${length} bytes")
logger.debug("Copying [0x%x -> 0x%x] %d bytes".format(src, dst, length))
var a: Word = 0
while (a < length) {
val oldWord = MemorySupport.loadLong(src + a)
......
......@@ -57,6 +57,10 @@ object TypeSizes {
val GC_HEADER_OFFSET_TAG: Word = -8L;
val GC_HEADER_OFFSET_HYBRID_LENGTH: Word = -16L;
val MARK_MASK = 0x4000000000000000L
val MOVE_MASK = 0x8000000000000000L
def sizeOf(ty: Type): Word = ty match {
case TypeInt(l) => intBitsToBytes(l)
case _:TypeFloat => 4L
......
......@@ -48,8 +48,7 @@ class AllScanner(val microVM: MicroVM, val handler: RefFieldHandler) extends Ref
}
private def traceStacks() {
val sr = microVM.threadStackManager.stackRegistry
for (sta <- sr.values if sta.state != StackState.Dead) {
for (sta <- microVM.threadStackManager.iterateAllLiveStacks) {
logger.debug(s"Tracing stack ${sta.id} for registers...")
for (fra <- sta.frames; vb <- fra.boxes.values if vb.isInstanceOf[HasObjRef]) {
......@@ -70,32 +69,27 @@ class AllScanner(val microVM: MicroVM, val handler: RefFieldHandler) extends Ref
private def doTransitiveClosure() {
while (!queue.isEmpty) {
val objRef = queue.pollFirst()
logger.debug("Scanning heap object 0x%x...".format(objRef))
MemoryDataScanner.scanAllocUnit(objRef, objRef, microVM, this)
}
}
override def fromBox(box: HasObjRef): Boolean = {
val toEnqueue = handler.fromBox(box)
if (toEnqueue) {
queue.add(box.getObjRef())
}
toEnqueue
override def fromBox(box: HasObjRef): Option[Word] = {
val rv = handler.fromBox(box)
rv.foreach(queue.add)
rv
}
override def fromMem(objRef: Word, iRef: Word, toObj: Word, isWeak: Boolean, isTR64: Boolean): Boolean = {
val toEnqueue = handler.fromMem(objRef, iRef, toObj, isWeak, isTR64)
if (toEnqueue) {
queue.add(toObj)
}
toEnqueue
override def fromMem(objRef: Word, iRef: Word, toObj: Word, isWeak: Boolean, isTR64: Boolean): Option[Word] = {
val rv = handler.fromMem(objRef, iRef, toObj, isWeak, isTR64)
rv.foreach(queue.add)
rv
}
override def fromInternal(toObj: Word): Boolean = {
val toEnqueue = handler.fromInternal(toObj)
if (toEnqueue) {
queue.add(toObj)
}
toEnqueue
override def fromInternal(toObj: Word): Option[Word] = {
val rv = handler.fromInternal(toObj)
rv.foreach(queue.add)
rv
}
}
\ No newline at end of file
......@@ -19,6 +19,7 @@ object MemoryDataScanner extends StrictLogging {
*/
def scanAllocUnit(objRef: Word, iRef: Word, microVM: MicroVM, handler: RefFieldHandler) {
val tag = HeaderUtils.getTag(objRef)
logger.debug("Obj 0x%x, tag 0x%x".format(objRef, tag))
val ty = HeaderUtils.getType(microVM, tag)
scanField(ty, objRef, objRef, handler)
}
......
......@@ -8,22 +8,23 @@ import uvm.refimpl.itpr.OpHelper
/**
* Handle reference fields or references in boxes.
* <p>
* Both fromBox and fromMem method return true if and only if the scanner should follow the reference.
* Both fromBox and fromMem method return Some(addr) if addr is to be enqueued by the scanner, or None otherwise. If an
* object is moved when scanning an object, the returned addr must be the new address.
* <p>
* The caller invokes the methods on all boxes/locations it finds. The Callee checks if the box/location
* actually contain references or non-null references.
*/
trait RefFieldHandler {
/** Scan a box. */
def fromBox(box: HasObjRef): Boolean
def fromBox(box: HasObjRef): Option[Word]
/** Scan a memory location. */
def fromMem(objRef: Word, iRef: Word, toObj: Word, isWeak: Boolean, isTR64: Boolean): Boolean
def fromMem(objRef: Word, iRef: Word, toObj: Word, isWeak: Boolean, isTR64: Boolean): Option[Word]
/**
* A reference from somewhere internal to the µVM.
* For example, from the StackMemory to the memory byte array;
* from a finaliser table to a finalisable object (to be added).
*/
def fromInternal(toObj: Word): Boolean
def fromInternal(toObj: Word): Option[Word]
}
object RefFieldUpdater {
......
......@@ -5,7 +5,7 @@ import uvm.refimpl.itpr._
import uvm.refimpl.mem._
import uvm.refimpl.mem.los.LargeObjectSpace
import uvm.refimpl.mem.scanning._
import TypeSizes.Word
import TypeSizes._
import uvm.types._
import SimpleImmixCollector._
import org.slf4j.LoggerFactory
......@@ -15,10 +15,6 @@ import uvm.refimpl.UvmRefImplException
object SimpleImmixCollector {
val logger = Logger(LoggerFactory.getLogger(getClass.getName))
private val MARK_MASK = 0x4000000000000000L
private val MOVE_MASK = 0x8000000000000000L
}
class SimpleImmixCollector(val heap: SimpleImmixHeap,
......@@ -52,30 +48,43 @@ class SimpleImmixCollector(val heap: SimpleImmixHeap,
logger.debug("Marking and getting statistics....")
val s1 = new AllScanner(microVM, new RefFieldHandler() {
override def fromBox(box: HasObjRef): Boolean = box match {
override def fromBox(box: HasObjRef): Option[Word] = box match {
case HasNonZeroRef(toObj) => maybeMarkAndStat(toObj)
case _ => false
case _ => None
}
override def fromMem(objRef: Word, iRef: Word, toObj: Word, isWeak: Boolean, isTR64: Boolean): Boolean = {
override def fromMem(objRef: Word, iRef: Word, toObj: Word, isWeak: Boolean, isTR64: Boolean): Option[Word] = {
if (toObj != 0L) {
if (isWeak) {
logger.debug(s"Enqueued weak reference ${iRef} to ${toObj}")
weakRefs.append(iRef)
false
None
} else {
maybeMarkAndStat(toObj)
}
} else {
false
None
}
}
override def fromInternal(toObj: Word): Boolean = if (toObj != 0L) maybeMarkAndStat(toObj) else false
override def fromInternal(toObj: Word): Option[Word] = if (toObj != 0L) maybeMarkAndStat(toObj) else None
})
s1.scanAll()
logger.debug("Visit and clear weak references...")
for (iRefWR <- weakRefs) {
val toObj = MemorySupport.loadLong(iRefWR)
val tag = HeaderUtils.getTag(toObj)
val isMarked = (tag & MARK_MASK) != 0
if (!isMarked) {
logger.debug("WeakRef %d whose value was %d is zeroed.".format(iRefWR, toObj))
MemorySupport.storeLong(iRefWR, 0)
} else {
logger.debug("WeakRef %d whose value was %d is still marked. Do not zero.".format(iRefWR, toObj))
}
}
logger.debug("Stat finished. Unmarking....")
val s2 = new AllScanner(microVM, clearMarkHandler)
s2.scanAll()
......@@ -92,19 +101,6 @@ class SimpleImmixCollector(val heap: SimpleImmixHeap,
defragMutator.close()
logger.debug("Visit and clear weak references...")
for (iRefWR <- weakRefs) {
val toObj = MemorySupport.loadLong(iRefWR)
val tag = HeaderUtils.getTag(toObj)
val isMarked = (tag & MARK_MASK) != 0
if (!isMarked) {
logger.debug("WeakRef %d whose value was %d is zeroed.".format(iRefWR, toObj))
MemorySupport.storeLong(iRefWR, 0)
} else {
logger.debug("WeakRef %d whose value was %d is still marked. Do not zero.".format(iRefWR, toObj))
}
}
logger.debug("Marked. Collecting blocks....")
val anyMemoryRecycled = collectBlocks()
if (!anyMemoryRecycled && heap.getMustFreeSpace) {
......@@ -119,7 +115,7 @@ class SimpleImmixCollector(val heap: SimpleImmixHeap,
heap.untriggerGC()
}
private def maybeMarkAndStat(addr: Word): Boolean = {
private def maybeMarkAndStat(addr: Word): Option[Word] = {
assert(addr != 0L, "addr should be non-zero before calling this function")
val oldHeader = HeaderUtils.getTag(addr)
logger.debug("GC header of 0x%x is 0x%x".format(addr, oldHeader))
......@@ -148,36 +144,32 @@ class SimpleImmixCollector(val heap: SimpleImmixHeap,
} else {
throw new UvmRefImplException("Object ref %d not in any space".format(addr))
}
true
Some(addr)
} else {
false
None
}
}
private val markMover = new RefFieldHandler {
override def fromBox(box: HasObjRef): Boolean = box match {
override def fromBox(box: HasObjRef): Option[Word] = box match {
case HasNonZeroRef(toObj) => maybeMove(toObj, newObjRef => RefFieldUpdater.updateBox(box, newObjRef))
case _ => false
case _ => None
}
override def fromMem(objRef: Word, iRef: Word, toObj: Word, isWeak: Boolean, isTR64: Boolean): Boolean = {
override def fromMem(objRef: Word, iRef: Word, toObj: Word, isWeak: Boolean, isTR64: Boolean): Option[Word] = {
if (toObj != 0L) {
if (isWeak) {
throw new UvmRefImplException("BUG: Weak references should have been cleared now.")
} else {
maybeMove(toObj, newObjRef => RefFieldUpdater.updateMemory(iRef, isTR64, newObjRef))
}
} else {
false
None
}
}
// Currently internally referenced objects (only the byte array for stacks) cannot move and does not transitively
// refer to other objects.
override def fromInternal(toObj: Word): Boolean = false
override def fromInternal(toObj: Word): Option[Word] = None
private def maybeMove(toObj: Word, updateFunc: Word => Unit): Boolean = {
private def maybeMove(toObj: Word, updateFunc: Word => Unit): Option[Word] = {
val oldHeader = HeaderUtils.getTag(toObj)
logger.debug("GC header of 0x%x is 0x%x".format(toObj, oldHeader))
val markBit = oldHeader & MARK_MASK
......@@ -187,10 +179,10 @@ class SimpleImmixCollector(val heap: SimpleImmixHeap,
if (wasMoved) {
val dest = HeaderUtils.getForwardedDest(oldHeader)
updateFunc(dest)
false
None
} else {
if (wasMarked) {
false
None
} else {
val isInSmallObjectSpace = space.isInSpace(toObj)
......@@ -223,7 +215,7 @@ class SimpleImmixCollector(val heap: SimpleImmixHeap,
} else {
throw new UvmRefImplException("Object ref %x not in any space".format(actualObj))
}
true
Some(actualObj)
}
}
}
......@@ -281,28 +273,28 @@ class SimpleImmixCollector(val heap: SimpleImmixHeap,
}
private val clearMarkHandler = new RefFieldHandler() {
override def fromBox(box: HasObjRef): Boolean = box match {
override def fromBox(box: HasObjRef): Option[Word] = box match {
case HasNonZeroRef(toObj) => clearMark(toObj)
case _ => false
case _ => None
}
override def fromMem(objRef: Word, iRef: Word, toObj: Word, isWeak: Boolean, isTR64: Boolean): Boolean = {
if (toObj != 0L) clearMark(toObj) else false
override def fromMem(objRef: Word, iRef: Word, toObj: Word, isWeak: Boolean, isTR64: Boolean): Option[Word] = {
if (toObj != 0L) clearMark(toObj) else None
}
override def fromInternal(toObj: Word): Boolean = if (toObj != 0L) clearMark(toObj) else false
override def fromInternal(toObj: Word): Option[Word] = if (toObj != 0L) clearMark(toObj) else None
}
private def clearMark(objRef: Long): Boolean = {
private def clearMark(objRef: Long): Option[Word] = {
val oldHeader = HeaderUtils.getTag(objRef)
logger.debug("GC header of 0x%x is 0x%x".format(objRef, oldHeader))
val markBit = oldHeader & MARK_MASK
if (markBit != 0) {
val newHeader = oldHeader & ~(MARK_MASK | MOVE_MASK)
HeaderUtils.setTag(objRef, newHeader)
true
Some(objRef)
} else {
false
None
}
}
......
......@@ -18,6 +18,7 @@ class UvmInterpreterSpec extends UvmBundleTesterBase {
setLogLevels(
ROOT_LOGGER_NAME -> INFO,
"uvm.refimpl.mem" -> DEBUG,
"uvm.refimpl.itpr" -> DEBUG)
preloadBundles("tests/uvm-refimpl-test/basic-tests.uir")
......@@ -918,14 +919,107 @@ class UvmInterpreterSpec extends UvmBundleTesterBase {
testFunc(ca, func, Seq()) { (ca, th, st, wp) =>
nameOf(ca.currentInstruction(st, 0)) match {
case "@memAccessingNull_v1.trap_exit" => {}
case "@memAccessingNull_v1.trap_unreachable" => fail("Reached %trap_unreachable")
case "@memAccessingNull_v1.trap_exit" => TrapRebindPassVoid(st)
case n => fail("Unexpected trap " + n)
}
}
ca.close()
}
"TRAP" should "work with all supported destinations" in {
val ca = microVM.newClientAgent()
val exc = ca.newFixed("@void")
val fortyTwo = ca.putInt("@i64", 42L)
val fortyTwoPointZero = ca.putDouble("@double", 42.0d)
val func = ca.putFunction("@traptest")
testFunc(ca, func, Seq()) { (ca, th, st, wp) =>
nameOf(ca.currentInstruction(st, 0)) match {
case "@traptest_v1.t1" => {
TrapRebindPassValue(st, fortyTwo)
}
case "@traptest_v1.t2" => {
TrapRebindPassValue(st, fortyTwoPointZero)
}
case "@traptest_v1.t3" => {
TrapRebindThrowExc(st, exc)
}
case "@traptest_v1.trap_exit" => {
val Seq(t1, t2, lp) = ca.dumpKeepalives(st, 0)
t1.vb.asSInt(64) shouldBe 42L
t2.vb.asDouble shouldBe 42.0d
lp.vb.asRef shouldBe exc.vb.asRef
TrapRebindPassVoid(st)
}
case n => fail("Unexpected trap " + n)
}
}
ca.close()
}
"WATCHPOINT" should "do nothing when disabled" in {
val ca = microVM.newClientAgent()
ca.disableWatchPoint(1)
val func = ca.putFunction("@wptest")
testFunc(ca, func, Seq()) { (ca, th, st, wp) =>
nameOf(ca.currentInstruction(st, 0)) match {
case "@wptest_v1.trap_dis" => {
TrapRebindPassVoid(st)
}
case n => fail("Unexpected trap " + n)
}
}
ca.close()
}
"WATCHPOINT" should "work with all supported destinations when enabled" ignore {
val ca = microVM.newClientAgent()
ca.enableWatchPoint(1)
val exc = ca.newFixed("@i32")
val fortyTwo = ca.putInt("@i64", 42L)
val fortyTwoPointZero = ca.putDouble("@double", 42.0d)
val func = ca.putFunction("@wptest")
testFunc(ca, func, Seq()) { (ca, th, st, wp) =>
nameOf(ca.currentInstruction(st, 0)) match {
case "@wptest_v1.w1" => {
wp shouldBe 1
TrapRebindPassValue(st, fortyTwo)
}
case "@wptest_v1.w2" => {
wp shouldBe 1
TrapRebindPassValue(st, fortyTwoPointZero)
}
case "@wptest_v1.w3" => {
wp shouldBe 1
TrapRebindThrowExc(st, exc)
}
case "@wptest_v1.trap_exit" => {
wp shouldBe 0
val Seq(w1, w2, lp) = ca.dumpKeepalives(st, 0)
w1.vb.asSInt(64) shouldBe 42L
w2.vb.asDouble shouldBe 42.0d
lp.vb.asRef shouldBe exc.vb.asRef
TrapRebindPassVoid(st)
}
case n => fail("Unexpected trap " + n)
}
}
ca.close()
}
}
\ No newline at end of file
......@@ -667,6 +667,48 @@
COMMINST @uvm.thread_exit
}
.funcdef @traptest VERSION @traptest_v1 <@noparamsnoret> () {
%entry:
%t1 = TRAP <@i64>
%t2 = TRAP <@double> EXC(%bb2 %unreachable)
%bb2:
%t3 = TRAP <@void> EXC(%unreachable %exit)
%exit:
%lp = LANDINGPAD
%trap_exit = TRAP <@void> KEEPALIVE (%t1 %t2 %lp)
COMMINST @uvm.thread_exit
%unreachable:
%trap_unreachable = TRAP <@void>
COMMINST @uvm.thread_exit
}
.funcdef @wptest VERSION @wptest_v1 <@noparamsnoret> () {
%entry:
%w1 = WATCHPOINT 1 <@i64> %dis %bb2
%bb2:
%w2 = WATCHPOINT 1 <@double> %dis %bb3 WPEXC(%unreachable)
%bb3:
%w3 = WATCHPOINT 1 <@void> %dis %unreachable WPEXC(%exit)
%exit:
%lp = LANDINGPAD
%trap_exit = TRAP <@void> KEEPALIVE (%w1 %w2 %lp)
COMMINST @uvm.thread_exit
%unreachable:
%trap_unreachable = TRAP <@void>
COMMINST @uvm.thread_exit
%dis:
%trap_dis = TRAP <@void>
COMMINST @uvm.thread_exit
}
//
// .funcsig @watchpointtest_sig = @noparamsnoret
// .funcdef @watchpointtest VERSION @watchpointtest_v1 <@watchpointtest_sig> () {
......
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