stacks.scala 19 KB
Newer Older
1 2
package uvm.refimpl.itpr

3 4 5 6 7 8 9 10
import scala.collection.AbstractIterator
import scala.collection.mutable.HashMap
import scala.collection.mutable.HashSet

import org.slf4j.LoggerFactory

import com.typesafe.scalalogging.Logger

11 12
import uvm._
import uvm.refimpl._
13
import uvm.refimpl.integerize.HasID
14
import uvm.refimpl.mem._
15
import uvm.refimpl.nat.NativeCallHelper
16 17 18 19
import uvm.refimpl.nat.NativeCallResult
import uvm.refimpl.nat.NativeStackKeeper
import uvm.ssavariables._
import uvm.types._
20

21 22 23 24 25 26 27 28 29 30
/** The state of a frame. */
abstract class FrameState

object FrameState {
  /** If values of ts are given, it is ready to resume. */
  case class Ready(ts: Seq[Type]) extends FrameState
  /** A thread is executing it. */
  case object Running extends FrameState
  /** Killed. */
  case object Dead extends FrameState
31 32
}

33 34 35
/**
 * Implements a Mu Stack. Contains both Mu frames and native frames.
 */
36
class InterpreterStack(val id: MuInternalID, val stackMemory: StackMemory, stackBottomFunc: Function)(
37 38 39 40
    implicit nativeCallHelper: NativeCallHelper, microVM: MicroVM) extends HasID with AutoCloseable {
  
  override def close(): Unit = kill()
  
41 42
  var gcMark: Boolean = false // Mark for GC.

43 44
  private var _top: InterpreterFrame = InterpreterFrame.forMuFunc(stackMemory.top, stackBottomFunc, 0L, None)
  /** The top frame */
45 46
  def top = _top
  private def top_=(f: InterpreterFrame) = _top = f
47

48 49 50 51 52 53 54 55 56 57
  /** A list of frame cursors on this stack for sanity check. */
  var frameCursors = new HashSet[FrameCursor]()

  private def ensureNoCursors(action: String): Unit = {
    if (!frameCursors.isEmpty) {
      throw new UvmRuntimeException("Attempt to %s when there are frame cursors not closed. Stack: %d, Cursors: %s".format(
        action, id, frameCursors.map(_.id).mkString(" ")))
    }
  }

58 59 60 61
  /** The state of the stack (i.e. state of the top frame) */
  def state = top.state

  /** A lazily created native stack keeper. */
62 63
  var maybeNSK: Option[NativeStackKeeper] = None

64
  /** Lazily create the nsk. */
65 66 67 68 69 70 71
  private def ensureNSK(): Unit = {
    if (maybeNSK == None) {
      val nsk = new NativeStackKeeper()
      maybeNSK = Some(nsk)
    }
  }

72
  /** Iterate through all frames. */
73 74 75 76 77 78 79 80 81
  def frames: Iterator[InterpreterFrame] = new AbstractIterator[InterpreterFrame] {
    var curFrame: Option[InterpreterFrame] = Some(top)
    def hasNext = curFrame.isDefined
    def next = {
      val res = curFrame.get
      curFrame = res.prev
      res
    }
  }
82

83
  /** Iterate through Mu frames only. */
84
  def muFrames: Iterator[MuFrame] = frames.filter(_.isInstanceOf[MuFrame]).map(_.asInstanceOf[MuFrame])
85

86 87 88 89 90 91
  ///////// frame manipulations

  /** Create a new Mu frame, usually for Mu calling Mu, or OSR. */
  private def pushMuFrame(func: Function): Unit = {
    assert(top.isInstanceOf[MuFrame])
    val newFrame = InterpreterFrame.forMuFunc(stackMemory.top, func, 0L, Some(top))
Kunshan Wang's avatar
Kunshan Wang committed
92 93
    top = newFrame
  }
94

95 96 97 98
  /** Create a new Mu frame for calling from native. */
  private def pushMuFrameForCallBack(func: Function, cookie: Long): Unit = {
    assert(top.isInstanceOf[NativeFrame])
    val newFrame = InterpreterFrame.forMuFunc(stackMemory.top, func, cookie, Some(top))
99 100 101
    top = newFrame
  }

102 103 104 105 106 107 108
  /** Replace the top Mu frame with a frame of a different function. Usually used by tailcall. */
  private def replaceTopMuFrame(func: Function): Unit = {
    assert(top.isInstanceOf[MuFrame])
    if (top.isInstanceOf[DefinedMuFrame]) {
      stackMemory.rewind(top.asInstanceOf[DefinedMuFrame].savedStackPointer)
    }
    val newFrame = InterpreterFrame.forMuFunc(stackMemory.top, func, 0L, top.prev)
109 110
    top = newFrame
  }
111

112 113 114
  /** Push a (conceptual) native frame. The "real native frame" is held by the NSK slave, of course. */
  private def pushNativeFrame(sig: FuncSig, func: Word): Unit = {
    assert(top.isInstanceOf[MuFrame])
115
    val newFrame = InterpreterFrame.forNativeFunc(func, Some(top))
116 117 118
    top = newFrame
  }

119 120 121 122 123 124 125 126 127
  /** Pop a Mu frame or a native frame. Throw error when popping the bottom frame. */
  private def _popFrame(): Unit = {
    top = top.prev.getOrElse {
      throw new UvmRuntimeException("Attemting to pop the last frame of a stack. Stack ID: %d.".format(id))
    }
  }

  /** Pop a Mu frame */
  private def popMuFrame(): Unit = {
128
    assert(top.isInstanceOf[MuFrame])
129 130 131 132 133 134 135 136 137 138
    if (top.isInstanceOf[DefinedMuFrame]) {
      stackMemory.rewind(top.asInstanceOf[DefinedMuFrame].savedStackPointer)
    }
    _popFrame()
  }

  /** Pop a native frame */
  private def popNativeFrame(): Unit = {
    assert(top.isInstanceOf[NativeFrame])
    _popFrame()
139 140
  }

141 142 143 144 145
  /**
   * Return from a Mu frame to a native frame. This will eventually resume in Mu.
   * @return true if the interpreter needs to increment the PC.
   */
  private def returnToNative(maybeRvb: Option[ValueBox]): Boolean = {
146
    assert(top.isInstanceOf[NativeFrame])
147
    val result = maybeNSK.get.returnToNative(maybeRvb)
148 149 150
    handleNativeCallResult(result)
  }

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
  /**
   * Handle native call result. This will eventually resume in Mu.
   * @return true if the interpreter needs to increment the PC.
   */
  private def handleNativeCallResult(ncr: NativeCallResult): Boolean = {
    ncr match {
      case r @ NativeCallResult.ReturnToMu(maybeRvb) => {
        assert(top.isInstanceOf[NativeFrame])
        popNativeFrame()
        assert(top.isInstanceOf[MuFrame])
        top.asInstanceOf[MuFrame].resumeNormally(maybeRvb.toSeq)
      }
      case r @ NativeCallResult.CallMu(func, cookie, args) => {
        assert(top.isInstanceOf[NativeFrame])
        pushMuFrameForCallBack(func, cookie)
        top.asInstanceOf[MuFrame].resumeNormally(args.toSeq)
      }
    }
169
  }
170

171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
  ///////// interpreter/client-visible stack state transitions

  /**
   * Mu calling a Mu function.
   */
  def callMu(func: Function, args: Seq[ValueBox]): Boolean = {
    assert(top.isInstanceOf[MuFrame])
    top.state = FrameState.Ready(func.sig.retTys)
    pushMuFrame(func)
    top.asInstanceOf[MuFrame].resumeNormally(args)
  }

  /** Mu tail-calling a Mu function. */
  def tailCallMu(func: Function, args: Seq[ValueBox]): Boolean = {
    assert(top.isInstanceOf[MuFrame])
    replaceTopMuFrame(func)
    top.asInstanceOf[MuFrame].resumeNormally(args)
  }

  /** Returning from a Mu frame. Will eventually resume in Mu.*/
  def retFromMu(rvs: Seq[ValueBox]): Boolean = {
    assert(top.isInstanceOf[MuFrame])
    popMuFrame()

    top match {
      case f: MuFrame => {
        f.resumeNormally(rvs)
      }
      case f: NativeFrame => {
        val maybeRvb = rvs match {
          case Seq()  => None
          case Seq(b) => Some(b)
          case bs => throw new UvmRefImplException(
            "Cannot return multiple values to native functions. Native func: 0x%x".format(f.func))
        }

        returnToNative(maybeRvb)
      }
209 210
    }
  }
211

212
  /** Mu calling a native function. */
213
  def callNative(sig: FuncSig, func: Word, args: Seq[ValueBox], errnoBox: ErrnoBox): Boolean = {
214 215 216
    assert(top.isInstanceOf[MuFrame])
    ensureNSK()
    pushNativeFrame(sig, func)
217
    val result = maybeNSK.get.callNative(sig, func, args, errnoBox)
218
    handleNativeCallResult(result)
219 220
  }

221
  /** Unwind to a Mu frame. The interpreter will handle the resumption. */
222 223
  def unwindTo(f: InterpreterFrame): Unit = {
    top = f
224 225 226 227 228 229
    top match {
      case mf: DefinedMuFrame => {
        stackMemory.rewind(mf.savedStackPointer)
        top.state = FrameState.Running
      }
      case mf: UndefinedMuFrame => {
230
        throw new UvmInternalException("Unwound to a frame for undefined Mu function %s. Stack ID: %d.".format(mf.func.repr, id))
231 232
      }
      case nf: NativeFrame => {
233
        throw new UvmUnimplementedOperationException("Cannot unwind to native frames. Stack ID: %d.".format(id))
234 235
      }
    }
236
  }
237

238 239 240 241
  /** Unbind the stack from a thread, returning with values. */
  def unbindRetWith(retTys: Seq[Type]): Unit = {
    assert(top.isInstanceOf[MuFrame])
    top.state = FrameState.Ready(retTys)
242 243
  }

244 245 246 247 248
  /**
   * Rebind to this stack, passing values.
   * @return true if the interpreter should increment the PC by continueNormally().
   */
  def rebindPassValues(args: Seq[ValueBox]): Boolean = {
249
    if (!state.isInstanceOf[FrameState.Ready]) {
250 251
      throw new UvmRuntimeException("Attempt to bind to a stack not in the ready state. Actual state: %s".format(state))
    }
252 253 254

    ensureNoCursors("bind to a stack")

255 256 257 258 259 260 261 262 263 264 265 266 267 268
    top match {
      case mf: MuFrame => {
        mf.resumeNormally(args)
      }
      case nf: NativeFrame => {
        val maybeRvb = args match {
          case Seq()   => None
          case Seq(vb) => Some(vb)
          case bs => throw new UvmRuntimeException("Attempted to return multiple values to a native frame. " +
            "Stack ID: %d, Native function: 0x%x".format(id, nf.func))
        }
        returnToNative(maybeRvb)
      }
    }
269 270
  }

271
  /** Kill the stack */
272
  def kill(): Unit = {
273 274
    ensureNoCursors("kill a stack")

275 276 277
    for (f <- frames) {
      f.state = FrameState.Dead
    }
278 279 280 281
    maybeNSK.foreach { nsk =>
      nsk.close()
    }
  }
282

283
  /**
284 285
   * Ensure there is no native frame from the top frame until the frame cursor (exclusive), or any frames until the
   *  cursor happen to be referred by other frame cursors.
286
   */
287 288 289 290
  private def ensurePoppableUntil(cursor: FrameCursor): Unit = {
    val toFrame = cursor.frame
    for ((curFrame, i) <- frames.takeWhile(_ != toFrame).zipWithIndex) {
      if (curFrame.isInstanceOf[NativeFrame]) {
291
        throw new UvmUnimplementedOperationException(
292 293
          "Popping native frames is not supported. Found native frame at depth %d in stack %d".format(i, id))
      }
294

295 296
      for (c2 <- frameCursors if c2 != cursor) {
        if (c2.frame == curFrame) {
297
          throw new UvmUnimplementedOperationException(
298 299 300
            "Frame %s at depth %d in stack %d is referred by another frame cursor %d. Current cursor: %d".format(
                c2.frame.toString, i, id, c2.id, cursor.id))
        }
301
      }
302 303 304 305 306 307 308 309
    }
  }
  
  /** Pop the current Mu frame. Used by THROW. */
  def popMuFrameForThrow(): Unit = {
    popMuFrame()
  }
  
310

311 312 313 314 315 316 317 318 319 320 321 322 323
  /** Pop all frames until cursor. Part of the API. */
  def popFramesTo(cursor: FrameCursor): Unit = {
    if (cursor.stack != this) {
      throw new UvmRefImplException(
        "Frame cursor %d refers to frame %s in a different stack %d. Current stack: %d".format(
          cursor.frame.toString, cursor.stack.id, this.id))
    }
    ensurePoppableUntil(cursor)
    
    val toFrame = cursor.frame
    
    for (f <- frames.takeWhile(_ != toFrame) if f.isInstanceOf[DefinedMuFrame]) {
      stackMemory.rewind(f.asInstanceOf[DefinedMuFrame].savedStackPointer)
324
    }
325 326

    top = cursor.frame
327 328 329 330 331 332
  }

  /** Push a Mu frame. Part of the API. */
  def pushFrame(func: Function) {
    pushMuFrame(func)
  }
333 334
}

335 336
abstract class InterpreterFrame(val prev: Option[InterpreterFrame]) {
  /**
337
   * The state of the frame. Must be set when creating the frame.
338
   */
339
  var state: FrameState = _
340 341

  /** ID of the current function. Return 0 for native frames. */
342
  def curFuncID: MuID
343 344

  /** ID of the current function version Return 0 for native frames or undefined Mu frames. */
345
  def curFuncVerID: MuID
346 347

  /** ID of the current instruction. Return 0 for native frames, undefined Mu frames, or a just-created frame. */
348
  def curInstID: MuID
349

350 351 352
}

object InterpreterFrame {
Kunshan Wang's avatar
Kunshan Wang committed
353
  def forMuFunc(savedStackPointer: Word, func: Function, cookie: Long, prev: Option[InterpreterFrame])(implicit microVM: MicroVM): MuFrame = {
Kunshan Wang's avatar
Kunshan Wang committed
354
    val frm = microVM.globalBundle.funcToVers(func).headOption match {
355 356
      case None          => new UndefinedMuFrame(func, prev)
      case Some(funcVer) => new DefinedMuFrame(savedStackPointer, funcVer, cookie, prev)
357 358
    }

359 360
    frm.state = FrameState.Ready(func.sig.retTys)

361 362
    frm
  }
363

364 365
  def forNativeFunc(func: Word, prev: Option[InterpreterFrame]): NativeFrame = {
    val frm = new NativeFrame(func, prev)
366 367 368 369

    // A white lie. A native frame cannot be created until actualy calling a native function.
    frm.state = FrameState.Running

370 371 372 373
    frm
  }
}

374
class NativeFrame(val func: Word, prev: Option[InterpreterFrame]) extends InterpreterFrame(prev) {
375 376 377
  override def curFuncID: MuID = 0
  override def curFuncVerID: MuID = 0
  override def curInstID: MuID = 0
378

Kunshan Wang's avatar
Kunshan Wang committed
379
  override def toString(): String = "NativeFrame(func=0x%x)".format(func)
380
}
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402

abstract class MuFrame(val func: Function, prev: Option[InterpreterFrame]) extends InterpreterFrame(prev) {
  /**
   * Resume a Mu frame.
   * @return true if the interpreter should increment the PC
   */
  def resumeNormally(args: Seq[ValueBox]): Boolean

  /**
   * true if the frame is just created (push_frame or new_stack). Binding a thread to the stack or executing an
   *  instruction will make it false.
   */
  var justCreated: Boolean = true

  /**
   * Traverse over all local variable boxes. For GC.
   */
  def scannableBoxes: TraversableOnce[ValueBox]
}

object UndefinedMuFrame {
  val logger = Logger(LoggerFactory.getLogger(getClass.getName))
403 404 405 406

  val VIRT_INST_NOT_STARTED = 0
  val VIRT_INST_TRAP = 1
  val VIRT_INST_TAILCALL = 2
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
}

/**
 * A Mu frame for undefined Mu frames. Such frames can still be resumed, but will trap as soon as being executed.
 */
class UndefinedMuFrame(func: Function, prev: Option[InterpreterFrame]) extends MuFrame(func, prev) {
  import UndefinedMuFrame._

  val boxes = func.sig.paramTys.map { ty =>
    try {
      ValueBox.makeBoxForType(ty)
    } catch {
      case e: UvmRefImplException => {
        logger.error("Having problem creating box for parameter type: %s".format(ty))
        throw e
      }
    }
  }

426 427
  var virtInst = VIRT_INST_NOT_STARTED

428
  override def scannableBoxes = boxes
429

430 431 432
  override def curFuncID: MuID = func.id
  override def curFuncVerID: MuID = 0
  override def curInstID: MuID = 0
433 434 435 436 437 438 439 440 441 442 443 444 445

  def resumeNormally(args: Seq[ValueBox]): Boolean = {
    if (justCreated) {
      if (boxes.length != args.length) {
        throw new UvmRefImplException("Function %s (not undefined yet) expects %d params, got %d args.".format(
          func.repr, boxes.length, args.length))
      }

      for ((dst, src) <- boxes zip args) {
        dst.copyFrom(src)
      }

      justCreated = false
446
      virtInst = VIRT_INST_TRAP
447
      state = FrameState.Running
448

449 450
      false
    } else {
451 452 453 454 455 456 457 458 459 460 461
      assert(virtInst != VIRT_INST_NOT_STARTED, "The previous if should handle justCreated UndefinedMuFrame")
      assert(virtInst != VIRT_INST_TAILCALL, "TAILCALL is not an OSR point")
      assert(virtInst == VIRT_INST_TRAP)

      if (args.length != 0) {
        throw new UvmRefImplException("Undefined function %s expects no param on its first virtual trap, got %d args.".format(
          func.repr, args.length))
      }

      virtInst = VIRT_INST_TAILCALL
      false
462 463 464
    }
  }

Kunshan Wang's avatar
Kunshan Wang committed
465
  override def toString(): String = "UndefinedMuFrame(func=%s)".format(func.repr)
466 467 468 469 470
}

object DefinedMuFrame {
  val logger = Logger(LoggerFactory.getLogger(getClass.getName))
}
Kunshan Wang's avatar
Kunshan Wang committed
471
/**
472
 * A Mu frame for defined Mu frames.
473
 *
474 475
 * @param savedStackPointer: The stack pointer to restore to when unwinding THE CURRENT FRAME. In other word, this value is the stackMemory.top
 * of the stack when the current frame is pushed.
Kunshan Wang's avatar
Kunshan Wang committed
476 477
 * @param cookie: The cookie in the native interface. When called by another Mu function, cookie can be any value.
 */
Kunshan Wang's avatar
Kunshan Wang committed
478 479 480 481 482
class DefinedMuFrame(
    val savedStackPointer: Word,
    val funcVer: FuncVer,
    val cookie: Long,
    prev: Option[InterpreterFrame])
483 484 485 486
    extends MuFrame(funcVer.func, prev) {
  import DefinedMuFrame._

  /** Boxes for all local variables. */
487
  val boxes = new HashMap[LocalVariable, ValueBox]()
488

489 490
  override def scannableBoxes = boxes.values

491
  /** Edge-assigned instructions take values determined at look backedges */
492
  val edgeAssignedBoxes = new HashMap[Parameter, ValueBox]()
493

Kunshan Wang's avatar
Kunshan Wang committed
494
  /** Current basic block */
495 496
  var curBB: BasicBlock = funcVer.entry

Kunshan Wang's avatar
Kunshan Wang committed
497
  /** Current instruction index within the current basic block */
498
  var curInstIndex: Int = 0
Kunshan Wang's avatar
Kunshan Wang committed
499

500 501 502
  makeBoxes()

  private def makeBoxes() {
503 504 505 506 507 508 509 510
    for (bb <- funcVer.bbs) {
      for (p <- bb.norParams) {
        putBox(p)
      }
      for (p <- bb.excParam) {
        putBox(p)
      }

Kunshan Wang's avatar
Kunshan Wang committed
511 512
      for (inst <- bb.insts; res <- inst.results) {
        putBox(res)
513
      }
514 515 516 517
    }
  }

  private def putBox(lv: LocalVariable) {
518
    val ty = lv.inferredType
Kunshan Wang's avatar
Kunshan Wang committed
519 520 521 522 523 524 525 526
    try {
      boxes.put(lv, ValueBox.makeBoxForType(ty))
    } catch {
      case e: UvmRefImplException => {
        logger.error("Having problem creating box for lv: %s".format(lv))
        throw e
      }
    }
527 528
    if (lv.isInstanceOf[Parameter]) {
      edgeAssignedBoxes.put(lv.asInstanceOf[Parameter], ValueBox.makeBoxForType(ty))
529
    }
530
  }
531

532 533 534
  override def curFuncID: MuID = func.id
  override def curFuncVerID: MuID = funcVer.id
  override def curInstID: MuID = if (justCreated) 0 else curInst.id
535

536 537 538 539
  def curInst: Instruction = try {
    curBB.insts(curInstIndex)
  } catch {
    case e: IndexOutOfBoundsException =>
Kunshan Wang's avatar
Kunshan Wang committed
540 541 542 543 544 545 546
      if (curInstIndex == -1) {
        throw new UvmRefImplException(("The current stack has never been bound to a thread." +
          "FuncVer: %s, BasicBlock: %s").format(funcVer.repr, curBB.repr))
      } else {
        throw new UvmRefImplException(("Current instruction %d beyond the last instruction of a basic block. " +
          "FuncVer: %s, BasicBlock: %s").format(curInstIndex, funcVer.repr, curBB.repr))
      }
547
  }
548 549 550 551 552 553 554 555 556 557 558 559

  def incPC() {
    curInstIndex += 1
  }

  def jump(bb: BasicBlock, ix: Int) {
    curBB = bb
    curInstIndex = ix
  }

  def keepaliveBoxes(): Seq[ValueBox] = {
    curInst match {
Kunshan Wang's avatar
Kunshan Wang committed
560 561
      case hka: HasKeepaliveClause =>
        val kas = hka.keepalives
562 563 564 565 566
        val kaBoxes = kas.map(boxes.apply)
        kaBoxes
      case i => throw new UvmRefImplException("Instruction does not have keepalives: " + i.repr)
    }
  }
Kunshan Wang's avatar
Kunshan Wang committed
567

568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
  /** Resume with values. May be used by any OSR point, including call, tailcall, ret, trap, wp, swapstack */
  def resumeNormally(args: Seq[ValueBox]): Boolean = {
    val destBoxes = if (justCreated) {
      // Just created. args go to the arguments of the entry block.
      // May be extended to resume from any basic block.
      val bb = curBB
      val norParams = bb.norParams

      if (norParams.length != args.length) {
        throw new UvmRefImplException("Basic block %s expects %d params, got %d args.".format(
          bb.repr, norParams.length, args.length))
      }

      norParams.map(np => boxes(np))

    } else {
      // Resuming to an instruction. args go to the results of the current instruction.
      val inst = curInst
      val results = inst.results

      if (results.length != args.length) {
        throw new UvmRefImplException("Instruction %s expects %d params, got %d args.".format(
          inst.repr, results.length, args.length))
      }

      results.map(r => boxes(r))
    }

    for ((dst, src) <- destBoxes zip args) {
      dst.copyFrom(src)
    }

    val wasJustCreated = justCreated
601

602 603
    justCreated = false
    state = FrameState.Running
604

605 606
    !wasJustCreated
  }
Kunshan Wang's avatar
Kunshan Wang committed
607

Kunshan Wang's avatar
Kunshan Wang committed
608
  override def toString(): String = "DefinedMuFrame(func=%s, funcVer=%s)".format(func.repr, funcVer.repr)
609
}
610

Kunshan Wang's avatar
Kunshan Wang committed
611 612 613
/**
 * A mutable cursor that iterates through stack frames.
 */
614
class FrameCursor(val id: MuInternalID, val stack: InterpreterStack, var frame: InterpreterFrame) extends HasID {
Kunshan Wang's avatar
Kunshan Wang committed
615
  def nextFrame(): Unit = {
616
    frame = frame.prev.getOrElse {
Kunshan Wang's avatar
Kunshan Wang committed
617
      throw new UvmRuntimeException("Attempt to go below the stack-bottom frame. Stack id: %d, Frame: %s".format(
618
        stack.id, frame.toString))
Kunshan Wang's avatar
Kunshan Wang committed
619 620 621
    }
  }
}