Commit feefa204 authored by Kunshan Wang's avatar Kunshan Wang

Implemented and tested Futex.

parent 51d3bbbf
......@@ -3,11 +3,17 @@ package uvm.refimpl.itpr
import uvm.refimpl.mem.TypeSizes.Word
import scala.collection.mutable.{ HashSet, HashMap, MultiMap, ListBuffer, Set, TreeSet }
import scala.math.Ordering
import org.slf4j.LoggerFactory
import com.typesafe.scalalogging.Logger
object FutexManager {
val logger = Logger(LoggerFactory.getLogger(getClass.getName))
}
/**
* This class manages Futexes.
*/
class FutexManager {
import FutexManager._
case class WaitRecord(val objRef: Word, val offset: Word, val loc: Word, val thread: InterpreterThread, val autoWakeup: Option[Long])
type WaitingQueueType = ListBuffer[WaitRecord]
......@@ -42,63 +48,124 @@ class FutexManager {
}
}
def futexWake(objRef: Word, offset: Word, nThread: Int): Unit = {
def futexWake(objRef: Word, offset: Word, nThread: Int): Int = {
val loc = objRef + offset
waitingQueues.get(loc).foreach { q =>
val wrs = q.take(nThread)
for (wr <- wrs) {
wr.thread.isFutexWaiting = false
timeoutSet.remove(wr)
}
if (wrs.size <= q.size) {
waitingQueues.remove(loc)
objIndex.removeBinding(objRef, loc)
} else {
q.remove(0, wrs.size)
waitingQueues.get(loc) match {
case Some(q) => {
val wrs = q.take(nThread)
for (wr <- wrs) {
wakeThread(wr, 0)
}
if (wrs.size <= q.size) {
waitingQueues.remove(loc)
objIndex.removeBinding(objRef, loc)
} else {
q.remove(0, wrs.size)
}
wrs.size
}
case None => 0
}
}
def futexWakeTimeout(): Unit = {
val now = System.currentTimeMillis() * 1000000L
def futexRequeue(objRefSrc: Word, offsetSrc: Word, objRefDst: Word, offsetDst: Word, nThread: Int): Int = {
val locSrc = objRefSrc + offsetSrc
val locDst = objRefDst + offsetDst
waitingQueues.get(locSrc) match {
case Some(q) => {
waitingQueues.remove(locSrc)
objIndex.removeBinding(objRefSrc, locSrc)
val wakes = q.take(nThread)
for (wr <- wakes) {
wakeThread(wr, 0)
}
val moves = q.drop(nThread)
if (!moves.isEmpty) {
val newMoves = moves.map {
case WaitRecord(or, of, lo, th, aw) => WaitRecord(objRefDst, of, locDst, th, aw)
}
moves.foreach { wr => if (wr.autoWakeup.isDefined) timeoutSet.remove(wr) }
newMoves.foreach { wr => if (wr.autoWakeup.isDefined) timeoutSet.add(wr) }
objIndex.addBinding(objRefDst, locDst)
waitingQueues.get(locDst) match {
case None => {
waitingQueues(locDst) = newMoves
}
case Some(qDst) => {
qDst.appendAll(newMoves)
}
}
}
while (!timeoutSet.isEmpty) {
val first = timeoutSet.firstKey
if (first.autoWakeup.get <= now) {
first.thread.isFutexWaiting = false
timeoutSet.remove(first)
wakes.length
}
case None => 0
}
}
def futexRequeue(objRefSrc: Word, offsetSrc: Word, objRefDst: Word, offsetDst: Word, nThread: Int): Unit = {
val locSrc = objRefSrc + offsetSrc
val locDst = objRefDst + offsetDst
/** Wake up all threads with their timeout expired. */
def futexWakeTimeout(): Unit = {
val now = System.currentTimeMillis() * 1000000L
waitingQueues.get(locSrc).foreach { q =>
waitingQueues.remove(locSrc)
objIndex.removeBinding(objRefSrc, locSrc)
for (wr <- timeoutSet.takeWhile(_.autoWakeup.get <= now)) {
wakeThread(wr, -3)
waitingQueues(wr.loc) -= wr
}
}
val wakes = q.take(nThread)
for (wr <- wakes) {
wr.thread.isFutexWaiting = false
timeoutSet.remove(wr)
}
/** Return the time the next thread wakes up (in the unit of currentTimeMillis * 1000000L) */
def nextWakeup(): Option[Long] = {
if (!timeoutSet.isEmpty) {
Some(timeoutSet.firstKey.autoWakeup.get)
} else {
None
}
}
val moves = q.drop(nThread)
if (!moves.isEmpty) {
objIndex.addBinding(objRefDst, locDst)
waitingQueues.get(locDst) match {
case None => {
waitingQueues(locDst) = moves
}
case Some(qDst) => {
qDst.appendAll(moves)
/**
* Adjust waiting queues affected by garbage collection.
* <p>
* @param getMovement A function that maps an object address to its new address if it is moved. Return None otherwise.
* It assumes the new address is not previously occupied. (This shows why the µVM must be tightly coupled.)
*/
def afterGCAdjust(getMovement: Word => Option[Word]): Unit = {
val oldKeys = objIndex.keySet.toList
for (oor <- oldKeys) {
getMovement(oor) match {
case None => {
logger.debug("Object 0x%x is not moved.".format(oor))
}
case Some(nor) => {
logger.debug("Object 0x%x is moved to 0x%x.".format(oor, nor))
val delta = nor - oor
val locs = objIndex(oor)
val nlocs = for (loc <- locs) yield {
val nloc = loc + delta
logger.debug("Moving queue 0x%x to 0x%x".format(loc, nloc))
val q = waitingQueues(loc)
val nq = q.map {
case WaitRecord(or, of, lo, th, aw) => WaitRecord(nor, of, nloc, th, aw)
}
waitingQueues.remove(loc)
waitingQueues(nloc) = nq
nloc
}
objIndex.remove(oor)
objIndex(nor) = nlocs
}
}
}
}
private def wakeThread(wr: WaitRecord, rv: Int): Unit = {
wr.thread.futexReturn(rv)
wr.thread.isFutexWaiting = false
timeoutSet.remove(wr)
}
}
\ No newline at end of file
......@@ -5,8 +5,15 @@ import uvm.refimpl.MicroVM
import uvm.refimpl.mem._
import scala.collection.mutable.HashMap
import scala.collection.mutable.ArrayBuffer
import org.slf4j.LoggerFactory
import com.typesafe.scalalogging.Logger
object ThreadStackManager {
val logger = Logger(LoggerFactory.getLogger(getClass.getName))
}
class ThreadStackManager(microVM: MicroVM) {
import ThreadStackManager._
private val stackRegistry = new HashMap[Int, InterpreterStack]()
......@@ -15,7 +22,7 @@ class ThreadStackManager(microVM: MicroVM) {
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
......@@ -26,10 +33,12 @@ class ThreadStackManager(microVM: MicroVM) {
private def makeThreadID(): Int = { val id = nextThreadID; nextThreadID += 1; id }
val futexManager = new FutexManager
/**
* Create a new stack with function and args as the stack-bottom function and its arguments.
* <p>
* About mutator: "Bring your own mutator!" A mutator object is needed to allocate the stack memory. This means all
* About mutator: "Bring your own mutator!" A mutator object is needed to allocate the stack memory. This means all
* callers of the newStack function must have a mutator. Currently they are either ClientAgents which can create stack
* via the "new_stack" message or µVM threads (the InterpreterThread class) which can execute the NEWSTACK
* instruction.
......@@ -55,14 +64,50 @@ class ThreadStackManager(microVM: MicroVM) {
def joinAll() {
var someRunning: Boolean = false
var someWaiting: Boolean = false
var continue: Boolean = false
do {
futexManager.futexWakeTimeout()
someRunning = false
someWaiting = false
val curThreads = threadRegistry.values.toList
for (thr2 <- curThreads if thr2.isRunning) {
thr2.step()
someRunning = thr2.isRunning || someRunning
for (thr2 <- curThreads) {
if (thr2.isRunning)
if (thr2.isFutexWaiting) {
someWaiting = thr2.isFutexWaiting || someWaiting
} else {
thr2.step()
someRunning = thr2.isRunning || someRunning
}
}
continue = if (someRunning) {
true
} else {
if (someWaiting) {
futexManager.nextWakeup match {
case Some(nextWakeup) => {
val now = System.currentTimeMillis() * 1000000L
val sleep = nextWakeup - now
val sleepMillis = sleep / 1000000L
val sleepNanos = sleep % 1000000L
logger.debug("Waiting for futex. Now: %d, next wake up: %d, sleep: %d".format(now, nextWakeup, sleep))
Thread.sleep(sleepMillis, sleepNanos.toInt)
true
}
case None => {
logger.error("No threads are running. No threads are waiting for futex with timer. This is a deadlock situation.")
false
}
}
} else {
false
}
}
} while (someRunning)
} while (continue)
}
def joinThread(thr: InterpreterThread) {
......
......@@ -173,20 +173,20 @@ object PrimOpHelpers {
}
up(op match {
case BinOptr.ADD => pu(op1v) + pu(op2v)
case BinOptr.SUB => pu(op1v) - pu(op2v)
case BinOptr.MUL => pu(op1v) * pu(op2v)
case BinOptr.ADD => pu(op1v) + pu(op2v)
case BinOptr.SUB => pu(op1v) - pu(op2v)
case BinOptr.MUL => pu(op1v) * pu(op2v)
case BinOptr.UDIV => { checkDivByZero(); pu(op1v) / pu(op2v) }
case BinOptr.SDIV => { checkDivByZero(); ps(op1v) / ps(op2v) }
case BinOptr.UREM => { checkDivByZero(); pu(op1v) % pu(op2v) }
case BinOptr.SREM => { checkDivByZero(); ps(op1v) % ps(op2v) }
case BinOptr.SHL => pu(op1v) << (op2v.intValue & shiftMask)
case BinOptr.SHL => pu(op1v) << (op2v.intValue & shiftMask)
case BinOptr.LSHR => pu(op1v) >> (op2v.intValue & shiftMask)
case BinOptr.ASHR => ps(op1v) >> (op2v.intValue & shiftMask)
case BinOptr.AND => pu(op1v) & pu(op2v)
case BinOptr.OR => pu(op1v) | pu(op2v)
case BinOptr.XOR => pu(op1v) ^ pu(op2v)
case _ => throw new UvmRuntimeException(ctx + "Binary operator %s is not suitable for integer.".format(op))
case BinOptr.AND => pu(op1v) & pu(op2v)
case BinOptr.OR => pu(op1v) | pu(op2v)
case BinOptr.XOR => pu(op1v) ^ pu(op2v)
case _ => throw new UvmRuntimeException(ctx + "Binary operator %s is not suitable for integer.".format(op))
})
}
......@@ -197,7 +197,7 @@ object PrimOpHelpers {
case BinOptr.FMUL => op1v * op2v
case BinOptr.FDIV => op1v / op2v
case BinOptr.FREM => Math.IEEEremainder(op1v, op2v).toFloat
case _ => throw new UvmRuntimeException(ctx + "Binary operator %s is not suitable for float.".format(op))
case _ => throw new UvmRuntimeException(ctx + "Binary operator %s is not suitable for float.".format(op))
}
}
......@@ -208,7 +208,7 @@ object PrimOpHelpers {
case BinOptr.FMUL => op1v * op2v
case BinOptr.FDIV => op1v / op2v
case BinOptr.FREM => Math.IEEEremainder(op1v, op2v)
case _ => throw new UvmRuntimeException(ctx + "Binary operator %s is not suitable for double.".format(op))
case _ => throw new UvmRuntimeException(ctx + "Binary operator %s is not suitable for double.".format(op))
}
}
......@@ -217,8 +217,8 @@ object PrimOpHelpers {
def ps(v: BigInt): BigInt = OpHelper.prepareSigned(v, l)
op match {
case CmpOptr.EQ => pu(op1v) == pu(op2v)
case CmpOptr.NE => pu(op1v) != pu(op2v)
case CmpOptr.EQ => pu(op1v) == pu(op2v)
case CmpOptr.NE => pu(op1v) != pu(op2v)
case CmpOptr.UGT => pu(op1v) > pu(op2v)
case CmpOptr.UGE => pu(op1v) >= pu(op2v)
case CmpOptr.ULT => pu(op1v) < pu(op2v)
......@@ -227,7 +227,7 @@ object PrimOpHelpers {
case CmpOptr.SGE => ps(op1v) >= ps(op2v)
case CmpOptr.SLT => ps(op1v) < ps(op2v)
case CmpOptr.SLE => ps(op1v) <= ps(op2v)
case _ => throw new UvmRuntimeException(ctx + "Comparison operator %s not suitable for integers".format(op))
case _ => throw new UvmRuntimeException(ctx + "Comparison operator %s not suitable for integers".format(op))
}
}
......@@ -236,23 +236,23 @@ object PrimOpHelpers {
def ord = !isNaN(op1v) && !isNaN(op2v)
def uno = isNaN(op1v) || isNaN(op2v)
op match {
case CmpOptr.FTRUE => true
case CmpOptr.FTRUE => true
case CmpOptr.FFALSE => false
case CmpOptr.FOEQ => ord && op1v == op2v
case CmpOptr.FONE => ord && op1v != op2v
case CmpOptr.FOGT => ord && op1v > op2v
case CmpOptr.FOGE => ord && op1v >= op2v
case CmpOptr.FOLT => ord && op1v < op2v
case CmpOptr.FOLE => ord && op1v <= op2v
case CmpOptr.FORD => ord
case CmpOptr.FUEQ => uno || op1v == op2v
case CmpOptr.FUNE => uno || op1v != op2v
case CmpOptr.FUGT => uno || op1v > op2v
case CmpOptr.FUGE => uno || op1v >= op2v
case CmpOptr.FULT => uno || op1v < op2v
case CmpOptr.FULE => uno || op1v <= op2v
case CmpOptr.FUNO => uno
case _ => throw new UvmRuntimeException(ctx + "Comparison operator %s is not suitable for float.".format(op))
case CmpOptr.FOEQ => ord && op1v == op2v
case CmpOptr.FONE => ord && op1v != op2v
case CmpOptr.FOGT => ord && op1v > op2v
case CmpOptr.FOGE => ord && op1v >= op2v
case CmpOptr.FOLT => ord && op1v < op2v
case CmpOptr.FOLE => ord && op1v <= op2v
case CmpOptr.FORD => ord
case CmpOptr.FUEQ => uno || op1v == op2v
case CmpOptr.FUNE => uno || op1v != op2v
case CmpOptr.FUGT => uno || op1v > op2v
case CmpOptr.FUGE => uno || op1v >= op2v
case CmpOptr.FULT => uno || op1v < op2v
case CmpOptr.FULE => uno || op1v <= op2v
case CmpOptr.FUNO => uno
case _ => throw new UvmRuntimeException(ctx + "Comparison operator %s is not suitable for float.".format(op))
}
}
......@@ -261,23 +261,23 @@ object PrimOpHelpers {
def ord = !isNaN(op1v) && !isNaN(op2v)
def uno = isNaN(op1v) || isNaN(op2v)
op match {
case CmpOptr.FTRUE => true
case CmpOptr.FTRUE => true
case CmpOptr.FFALSE => false
case CmpOptr.FOEQ => ord && op1v == op2v
case CmpOptr.FONE => ord && op1v != op2v
case CmpOptr.FOGT => ord && op1v > op2v
case CmpOptr.FOGE => ord && op1v >= op2v
case CmpOptr.FOLT => ord && op1v < op2v
case CmpOptr.FOLE => ord && op1v <= op2v
case CmpOptr.FORD => ord
case CmpOptr.FUEQ => uno || op1v == op2v
case CmpOptr.FUNE => uno || op1v != op2v
case CmpOptr.FUGT => uno || op1v > op2v
case CmpOptr.FUGE => uno || op1v >= op2v
case CmpOptr.FULT => uno || op1v < op2v
case CmpOptr.FULE => uno || op1v <= op2v
case CmpOptr.FUNO => uno
case _ => throw new UvmRuntimeException(ctx + "Comparison operator %s is not suitable for double.".format(op))
case CmpOptr.FOEQ => ord && op1v == op2v
case CmpOptr.FONE => ord && op1v != op2v
case CmpOptr.FOGT => ord && op1v > op2v
case CmpOptr.FOGE => ord && op1v >= op2v
case CmpOptr.FOLT => ord && op1v < op2v
case CmpOptr.FOLE => ord && op1v <= op2v
case CmpOptr.FORD => ord
case CmpOptr.FUEQ => uno || op1v == op2v
case CmpOptr.FUNE => uno || op1v != op2v
case CmpOptr.FUGT => uno || op1v > op2v
case CmpOptr.FUGE => uno || op1v >= op2v
case CmpOptr.FULT => uno || op1v < op2v
case CmpOptr.FULE => uno || op1v <= op2v
case CmpOptr.FUNO => uno
case _ => throw new UvmRuntimeException(ctx + "Comparison operator %s is not suitable for double.".format(op))
}
}
}
......@@ -287,11 +287,11 @@ object MemoryOperations {
def loadScalar(ty: Type, loc: Word, br: ValueBox): Unit = ty match {
case TypeInt(l) =>
val bi: BigInt = l match {
case 8 => MemorySupport.loadByte(loc)
case 8 => MemorySupport.loadByte(loc)
case 16 => MemorySupport.loadShort(loc)
case 32 => MemorySupport.loadInt(loc)
case 64 => MemorySupport.loadLong(loc)
case _ => throw new UnimplementedOprationException("Loading int of length %d is not supported".format(l))
case _ => throw new UnimplementedOprationException("Loading int of length %d is not supported".format(l))
}
br.asInstanceOf[BoxInt].value = OpHelper.unprepare(bi, l)
case _: TypeFloat =>
......@@ -341,11 +341,11 @@ object MemoryOperations {
case TypeInt(l) =>
val bi = nvb.asInstanceOf[BoxInt].value
l match {
case 8 => MemorySupport.storeByte(loc, bi.byteValue)
case 8 => MemorySupport.storeByte(loc, bi.byteValue)
case 16 => MemorySupport.storeShort(loc, bi.shortValue)
case 32 => MemorySupport.storeInt(loc, bi.intValue)
case 64 => MemorySupport.storeLong(loc, bi.longValue)
case _ => throw new UnimplementedOprationException("Storing int of length %d is not supported".format(l))
case _ => throw new UnimplementedOprationException("Storing int of length %d is not supported".format(l))
}
case _: TypeFloat =>
val fv = nvb.asInstanceOf[BoxFloat].value
......@@ -452,7 +452,7 @@ object MemoryOperations {
val rbi: BigInt = l match {
case 32 => MemorySupport.atomicRMWInt(op, loc, obi.intValue)
case 64 => MemorySupport.atomicRMWLong(op, loc, obi.longValue)
case _ => throw new UnimplementedOprationException("AtomicRMW on int of length %d is not supported".format(l))
case _ => throw new UnimplementedOprationException("AtomicRMW on int of length %d is not supported".format(l))
}
br.asInstanceOf[BoxInt].value = OpHelper.unprepare(rbi, l)
case _ =>
......@@ -493,5 +493,22 @@ object MemoryOperations {
}
}
}
/**
* Check if a memory location still holds a particular value. Used by futex.
*/
def cmpInt(len: Int, loc: Word, expected: BoxInt): Boolean = len match {
case 64 => {
val expNum = OpHelper.prepareSigned(expected.value, len).longValue
val actualNum = MemorySupport.loadLong(loc)
expNum == actualNum
}
case 32 => {
val expNum = OpHelper.prepareSigned(expected.value, len).intValue
val actualNum = MemorySupport.loadInt(loc)
expNum == actualNum
}
case _ => throw new UnimplementedOprationException("Futex of %d bit int is not supported".format(len))
}
}
......@@ -107,6 +107,8 @@ class SimpleImmixCollector(val heap: SimpleImmixHeap,
throw new UvmRefImplException("Out of memory because the GC failed to recycle any memory.")
}
notifyMovedObjectsToFutex()
logger.debug("Blocks collected. Unmarking....")
val s4 = new AllScanner(microVM, clearMarkHandler)
s4.scanAll()
......@@ -298,6 +300,24 @@ class SimpleImmixCollector(val heap: SimpleImmixHeap,
}
}
private def getMovement(objRef: Word): Option[Word] = {
val tag = HeaderUtils.getTag(objRef)
logger.debug("Inspecting header for Futex. Obj 0x%x, tag 0x%x".format(objRef, tag))
val moveBit = tag & MOVE_MASK
if (moveBit != 0) {
val newAddr = HeaderUtils.getForwardedDest(tag)
Some(newAddr)
} else {
None
}
}
private def notifyMovedObjectsToFutex(): Unit = {
microVM.threadStackManager.futexManager.afterGCAdjust(getMovement)
}
private object HasNonZeroRef {
def unapply(obj: Any): Option[Word] = obj match {
case hor: HasObjRef => if (hor.hasObjRef) {
......
package uvm.refimpl.itpr
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 uvm.refimpl.mem.TypeSizes.Word
import ch.qos.logback.classic.Level._
import uvm.refimpl.UvmBundleTesterBase
class UvmInterpreterFutexTests extends UvmBundleTesterBase {
setLogLevels(
ROOT_LOGGER_NAME -> INFO,
"uvm.refimpl.itpr" -> DEBUG)
preloadBundles("tests/uvm-refimpl-test/primitives.uir",
"tests/uvm-refimpl-test/futex-tests.uir")
"Futex" should "wake up the waiting thread" in {
val ca = microVM.newClientAgent()
val func = ca.putFunction("@futex_setter")
var trapWaiterReached = false
var trapSetterReached = false
testFunc(ca, func, Seq()) { (ca, th, st, wp) =>
val trapName = nameOf(ca.currentInstruction(st, 0))
trapName match {
case "@futex_waiter_v1.trap_waiter" => {
val Seq(rv, sv) = ca.dumpKeepalives(st, 0)
ca.toInt(rv, signExt = true) shouldBe 0
ca.toInt(sv, signExt = true) shouldBe 42
trapWaiterReached = true
TrapRebindPassVoid(st)
}
case "@futex_setter_v1.trap_setter" => {
trapSetterReached = true
TrapRebindPassVoid(st)
}
case _ => fail("Should not hit " + trapName)
}
}
ca.close()
trapWaiterReached shouldBe true
trapSetterReached shouldBe true
}
"Futex" should "wake up if not explicitly woken within the give timeout" in {
val ca = microVM.newClientAgent()
val func = ca.putFunction("@futex_delayer")
var trapDelayerReached = false
testFunc(ca, func, Seq()) { (ca, th, st, wp) =>
val trapName = nameOf(ca.currentInstruction(st, 0))
trapName match {
case "@futex_delayer_v1.trap_delayer" => {
val Seq(rv) = ca.dumpKeepalives(st, 0)
ca.toInt(rv, signExt = true) shouldBe -3
trapDelayerReached = true
TrapRebindPassVoid(st)
}
case _ => fail("Should not hit " + trapName)
}
}
ca.close()
trapDelayerReached shouldBe true
}
"Futex" should "not sleep if the memory location does not hold the expected value" in {
val ca = microVM.newClientAgent()
val func = ca.putFunction("@futex_no_sleep")
var trapNoSleepReached = false
testFunc(ca, func, Seq()) { (ca, th, st, wp) =>
val trapName = nameOf(ca.currentInstruction(st, 0))
trapName match {
case "@futex_no_sleep_v1.trap_no_sleep" => {
val Seq(rv) = ca.dumpKeepalives(st, 0)
ca.toInt(rv, signExt = true) shouldBe -1
trapNoSleepReached = true
TrapRebindPassVoid(st)
}
case _ => fail("Should not hit " + trapName)
}
}
ca.close()
trapNoSleepReached shouldBe true
}
"Futex" should "wake up requeued threads" in {
val ca = microVM.newClientAgent()
val func = ca.putFunction("@futex_requeue_test")
var mainExit = false
var subExit = 0
testFunc(ca, func, Seq()) { (ca, th, st, wp) =>
val trapName = nameOf(ca.currentInstruction(st, 0))
trapName match {
case "@futex_requeue_waiter_v1.trap_requeue_waiter" => {
val Seq(rv) = ca.dumpKeepalives(st, 0)
ca.toInt(rv, signExt = true) shouldBe 0
subExit += 1
TrapRebindPassVoid(st)
}
case "@futex_requeue_test_v1.trap_wait" => {
val Seq(nt, nt2) = ca.dumpKeepalives(st, 0)
val nthr = nt.vb.asThread.get
val nthr2 = nt2.vb.asThread.get
if (nthr.isFutexWaiting && nthr2.isFutexWaiting) {
val one = ca.putInt("@i32", 1)
TrapRebindPassValue(st, one)
} else {
val zero = ca.putInt("@i32", 0)
TrapRebindPassValue(st, zero)
}
}
case "@futex_requeue_test_v1.trap_setter" => {
val Seq(nwakes, nwakes2) = ca.dumpKeepalives(st, 0)
ca.toInt(nwakes, signExt = true) shouldBe 1
ca.toInt(nwakes2, signExt = true) shouldBe 1
mainExit = true