diff --git a/benchmarks/wasm/trycatch/throw_twice.wat b/benchmarks/wasm/trycatch/throw_twice.wat new file mode 100644 index 000000000..935506449 --- /dev/null +++ b/benchmarks/wasm/trycatch/throw_twice.wat @@ -0,0 +1,38 @@ +;; kept in delimited continuation example +(module + ;; output: 1, 2, 6, 2, 3, 4, 4, 5 + (type (;0;) (func (param i32))) + (type (;1;) (func)) + (import "console" "log" (func (;0;) (type 0))) + (func (;1;) (type 1) + (local i32) + try + i32.const 1 + call 0 + block + block + i32.const 42 + ;; [42] + throw + i32.const 6 + call 0 + i32.const 42 + ;; [42] + throw + end + end + i32.const 3 + call 0 + catch + ;; [42, resume] + i32.const 2 + call 0 + drop + resume0 + i32.const 4 + call 0 + end + i32.const 5 + call 0 + ) + (start 1)) \ No newline at end of file diff --git a/benchmarks/wasm/trycatch/throw_twice2.wat b/benchmarks/wasm/trycatch/throw_twice2.wat new file mode 100644 index 000000000..2fe3297db --- /dev/null +++ b/benchmarks/wasm/trycatch/throw_twice2.wat @@ -0,0 +1,38 @@ +;; pushed to meta continuation example +(module + ;; output: 1, 2, 6, 2, 3, 4, 5 + (type (;0;) (func (param i32))) + (type (;1;) (func)) + (import "console" "log" (func (;0;) (type 0))) + (func (;1;) (type 1) + (local i32) + try + i32.const 1 + call 0 + block + block + i32.const 42 + ;; [42] + throw + end + i32.const 6 + call 0 + i32.const 42 + ;; [42] + throw + end + i32.const 3 + call 0 + catch + ;; [42, resume] + i32.const 2 + call 0 + drop + resume0 + i32.const 4 ;; |---> adk + call 0 ;; | + end + i32.const 5 + call 0 + ) + (start 1)) diff --git a/benchmarks/wasm/trycatch/try_catch_br3.wat b/benchmarks/wasm/trycatch/try_catch_br3.wat new file mode 100644 index 000000000..f6bd0256a --- /dev/null +++ b/benchmarks/wasm/trycatch/try_catch_br3.wat @@ -0,0 +1,36 @@ +;; ignored example +(module + ;; output: 1, 2, 3, 4, 5 + ;; 4 is printed, because the delimited continuation is kept when breaking out of the block, + ;; it's inside the trail1 + (type (;0;) (func (param i32))) + (type (;1;) (func)) + (import "console" "log" (func (;0;) (type 0))) + (func (;1;) (type 1) + (local i32) + try + i32.const 1 + call 0 + block + block + i32.const 42 + ;; [42] + throw + br 0 + end + end + i32.const 3 + call 0 + catch + ;; [42, resume] + i32.const 2 + call 0 + drop + resume0 + i32.const 4 + call 0 + end + i32.const 5 + call 0 + ) + (start 1)) diff --git a/benchmarks/wasm/trycatch/try_catch_br4.wat b/benchmarks/wasm/trycatch/try_catch_br4.wat new file mode 100644 index 000000000..9b10ef361 --- /dev/null +++ b/benchmarks/wasm/trycatch/try_catch_br4.wat @@ -0,0 +1,52 @@ +;; pushed to meta continuation example +(module + (type (;0;) (func (param i32))) + (type (;1;) (func)) + (import "console" "log" (func (;0;) (type 0))) + (func (;1;) (type 1) + (local i32 i32) + i32.const 0 + local.set 1 + try + i32.const 1 + call 0 + block + block + i32.const 42 + ;; [42] + throw + end + i32.const 6 + call 0 + i32.const 42 + ;; [42] + throw + end + i32.const 3 + call 0 + catch + ;; increment local 1 + i32.const 1 + local.get 1 + i32.add + local.set 1 + ;; [42, resume] + i32.const 2 + call 0 + drop + local.get 1 + i32.const 1 + i32.eq + if (param i32 (; input cont actually ;)) + resume0 + else + i32.const 7 + call 0 + end + i32.const 4 + call 0 + end + i32.const 5 + call 0 + ) + (start 1)) diff --git a/benchmarks/wasm/trycatch/try_catch_catch_br.wat b/benchmarks/wasm/trycatch/try_catch_catch_br.wat new file mode 100644 index 000000000..d515df3e5 --- /dev/null +++ b/benchmarks/wasm/trycatch/try_catch_catch_br.wat @@ -0,0 +1,39 @@ +(module + (type (;0;) (func (param i32))) + (type (;1;) (func)) + (import "console" "log" (func (;0;) (type 0))) + (func (;1;) (type 1) + (local i32) + try + i32.const 1 + call 0 + block + i32.const 42 + ;; [42] + throw + br 0 + i32.const 3 + call 0 + end + i32.const 6 + call 0 + catch + ;; [42, resume] + drop + local.set 0 ;; abusing the type system + local.get 0 ;; + block (param i32) ;; + i32.const 2 + call 0 + resume0 + br 0 + end + i32.const 4 + call 0 + local.get 0 + resume0 + end + i32.const 5 + call 0 + ) + (start 1)) diff --git a/src/main/scala/wasm/MiniWasmFX.scala b/src/main/scala/wasm/MiniWasmFX.scala index bdc71d739..1aee2fdb9 100644 --- a/src/main/scala/wasm/MiniWasmFX.scala +++ b/src/main/scala/wasm/MiniWasmFX.scala @@ -12,17 +12,29 @@ case class EvaluatorFX(module: ModuleInstance) { import Primtives._ implicit val m: ModuleInstance = module + trait ContTrait[A] { + def apply(stack: Stack, trail1: List[ContTrait[A]], mcont: MCont[A]): A + } + type Stack = List[Value] - type Cont[A] = (Stack, MCont[A]) => A + type Cont[A] = ContTrait[A] type MCont[A] = Stack => A type Handler[A] = Stack => A - case class ContV[A](k: (Stack, Cont[A], MCont[A], Handler[A]) => A) extends Value { + case class ContV[A](k: (Stack, Cont[A], List[Cont[A]], MCont[A], Handler[A]) => A) extends Value { def tipe(implicit m: ModuleInstance): ValueType = ??? } + // init is a continuation that simply returns the inputed stack + def init[Ans](s: Stack, trail1: List[Cont[Ans]], mkont: MCont[Ans]): Ans = { + trail1 match { + case k1 :: trail1 => k1(s, trail1, mkont) + case Nil => mkont(s) + } + } + // Only used for resumable try-catch (need refactoring): - case class TCContV[A](k: (Stack, Cont[A], MCont[A]) => A) extends Value { + case class TCContV[A](k: (Stack, Cont[A], List[Cont[A]], MCont[A]) => A) extends Value { def tipe(implicit m: ModuleInstance): ValueType = ??? } @@ -31,8 +43,9 @@ case class EvaluatorFX(module: ModuleInstance) { stack: List[Value], frame: Frame, kont: Cont[Ans], + trail1: List[Cont[Ans]], mkont: MCont[Ans], - trail: List[Cont[Ans]], + trail2: List[Cont[Ans]], h: Handler[Ans], isTail: Boolean): Ans = { module.funcs(funcIndex) match { @@ -43,24 +56,24 @@ case class EvaluatorFX(module: ModuleInstance) { val newFrame = Frame(ArrayBuffer(frameLocals: _*)) if (isTail) // when tail call, share the continuation for returning with the callee - eval(body, List(), newFrame, kont, mkont, List(kont), h) + eval(body, List(), newFrame, kont, trail1, mkont, List(kont), h) else { - val restK: Cont[Ans] = (retStack, mkont) => - eval(rest, retStack.take(ty.out.size) ++ newStack, frame, kont, mkont, trail, h) - // We make a new trail by `restK`, since function creates a new block to escape + val restK: Cont[Ans] = (retStack, trail1, mkont) => + eval(rest, retStack.take(ty.out.size) ++ newStack, frame, kont, trail1, mkont, trail2, h) + // We make a new trail2 by `restK`, since function creates a new block to escape // (more or less like `return`) - eval(body, List(), newFrame, restK, mkont, List(restK), h) + eval(body, List(), newFrame, restK, trail1, mkont, List(restK), h) } case Import("console", "log", _) => // println(s"[DEBUG] current stack: $stack") val I32V(v) :: newStack = stack println(v) - eval(rest, newStack, frame, kont, mkont, trail, h) + eval(rest, newStack, frame, kont, trail1, mkont, trail2, h) case Import("spectest", "print_i32", _) => // println(s"[DEBUG] current stack: $stack") val I32V(v) :: newStack = stack println(v) - eval(rest, newStack, frame, kont, mkont, trail, h) + eval(rest, newStack, frame, kont, trail1, mkont, trail2, h) case Import(_, _, _) => throw new Exception(s"Unknown import at $funcIndex") case _ => throw new Exception(s"Definition at $funcIndex is not callable") } @@ -70,11 +83,12 @@ case class EvaluatorFX(module: ModuleInstance) { stack: List[Value], frame: Frame, kont: Cont[Ans], + trail1: List[Cont[Ans]], mkont: MCont[Ans], - trail: List[Cont[Ans]], + trail2: List[Cont[Ans]], h: Handler[Ans]): Ans = { // Note kont is only used in the base case, or when we are appending k and k1 - if (insts.isEmpty) return kont(stack, mkont) + if (insts.isEmpty) return kont(stack, trail1, mkont) val inst = insts.head val rest = insts.tail @@ -83,23 +97,23 @@ case class EvaluatorFX(module: ModuleInstance) { // println(s"inst: ${inst} \t | ${frame.locals} | ${stack.reverse}" ) inst match { - case Drop => eval(rest, stack.tail, frame, kont, mkont, trail, h) + case Drop => eval(rest, stack.tail, frame, kont, trail1, mkont, trail2, h) case Select(_) => val I32V(cond) :: v2 :: v1 :: newStack = stack val value = if (cond == 0) v1 else v2 - eval(rest, value :: newStack, frame, kont, mkont, trail, h) + eval(rest, value :: newStack, frame, kont, trail1, mkont, trail2, h) case LocalGet(i) => - eval(rest, frame.locals(i) :: stack, frame, kont, mkont, trail, h) + eval(rest, frame.locals(i) :: stack, frame, kont, trail1, mkont, trail2, h) case LocalSet(i) => val value :: newStack = stack frame.locals(i) = value - eval(rest, newStack, frame, kont, mkont, trail, h) + eval(rest, newStack, frame, kont, trail1, mkont, trail2, h) case LocalTee(i) => val value :: newStack = stack frame.locals(i) = value - eval(rest, stack, frame, kont, mkont, trail, h) + eval(rest, stack, frame, kont, trail1, mkont, trail2, h) case GlobalGet(i) => - eval(rest, module.globals(i).value :: stack, frame, kont, mkont, trail, h) + eval(rest, module.globals(i).value :: stack, frame, kont, trail1, mkont, trail2, h) case GlobalSet(i) => val value :: newStack = stack module.globals(i).ty match { @@ -108,18 +122,18 @@ case class EvaluatorFX(module: ModuleInstance) { case GlobalType(_, true) => throw new Exception("Invalid type") case _ => throw new Exception("Cannot set immutable global") } - eval(rest, newStack, frame, kont, mkont, trail, h) + eval(rest, newStack, frame, kont, trail1, mkont, trail2, h) case MemorySize => - eval(rest, I32V(module.memory.head.size) :: stack, frame, kont, mkont, trail, h) + eval(rest, I32V(module.memory.head.size) :: stack, frame, kont, trail1, mkont, trail2, h) case MemoryGrow => val I32V(delta) :: newStack = stack val mem = module.memory.head val oldSize = mem.size mem.grow(delta) match { case Some(e) => - eval(rest, I32V(-1) :: newStack, frame, kont, mkont, trail, h) + eval(rest, I32V(-1) :: newStack, frame, kont, trail1, mkont, trail2, h) case _ => - eval(rest, I32V(oldSize) :: newStack, frame, kont, mkont, trail, h) + eval(rest, I32V(oldSize) :: newStack, frame, kont, trail1, mkont, trail2, h) } case MemoryFill => val I32V(value) :: I32V(offset) :: I32V(size) :: newStack = stack @@ -127,7 +141,7 @@ case class EvaluatorFX(module: ModuleInstance) { throw new Exception("Out of bounds memory access") // GW: turn this into a `trap`? else { module.memory.head.fill(offset, size, value.toByte) - eval(rest, newStack, frame, kont, mkont, trail, h) + eval(rest, newStack, frame, kont, trail1, mkont, trail2, h) } case MemoryCopy => val I32V(n) :: I32V(src) :: I32V(dest) :: newStack = stack @@ -135,105 +149,101 @@ case class EvaluatorFX(module: ModuleInstance) { throw new Exception("Out of bounds memory access") else { module.memory.head.copy(dest, src, n) - eval(rest, newStack, frame, kont, mkont, trail, h) + eval(rest, newStack, frame, kont, trail1, mkont, trail2, h) } - case Const(n) => eval(rest, n :: stack, frame, kont, mkont, trail, h) + case Const(n) => eval(rest, n :: stack, frame, kont, trail1, mkont, trail2, h) case Binary(op) => val v2 :: v1 :: newStack = stack - eval(rest, evalBinOp(op, v1, v2) :: newStack, frame, kont, mkont, trail, h) + eval(rest, evalBinOp(op, v1, v2) :: newStack, frame, kont, trail1, mkont, trail2, h) case Unary(op) => val v :: newStack = stack - eval(rest, evalUnaryOp(op, v) :: newStack, frame, kont, mkont, trail, h) + eval(rest, evalUnaryOp(op, v) :: newStack, frame, kont, trail1, mkont, trail2, h) case Compare(op) => val v2 :: v1 :: newStack = stack - eval(rest, evalRelOp(op, v1, v2) :: newStack, frame, kont, mkont, trail, h) + eval(rest, evalRelOp(op, v1, v2) :: newStack, frame, kont, trail1, mkont, trail2, h) case Test(op) => val v :: newStack = stack - eval(rest, evalTestOp(op, v) :: newStack, frame, kont, mkont, trail, h) + eval(rest, evalTestOp(op, v) :: newStack, frame, kont, trail1, mkont, trail2, h) case Store(StoreOp(align, offset, ty, None)) => val I32V(v) :: I32V(addr) :: newStack = stack module.memory(0).storeInt(addr + offset, v) - eval(rest, newStack, frame, kont, mkont, trail, h) + eval(rest, newStack, frame, kont, trail1, mkont, trail2, h) case Load(LoadOp(align, offset, ty, None, None)) => val I32V(addr) :: newStack = stack val value = module.memory(0).loadInt(addr + offset) - eval(rest, I32V(value) :: newStack, frame, kont, mkont, trail, h) + eval(rest, I32V(value) :: newStack, frame, kont, trail1, mkont, trail2, h) case Nop => - eval(rest, stack, frame, kont, mkont, trail, h) + eval(rest, stack, frame, kont, trail1, mkont, trail2, h) case Unreachable => throw Trap() case Block(ty, inner) => val funcTy = getFuncType(ty) val (inputs, restStack) = stack.splitAt(funcTy.inps.size) // why a block is introducing a new mknot1 // I feel like we will almost never need to change the mkont for a block - val restK: Cont[Ans] = (retStack, mkont1) => { + val restK: Cont[Ans] = (retStack, trail1, mkont1) => { // kont -> mkont -> mkont1 - // println(s"inner: $inner") - eval(rest, retStack.take(funcTy.out.size) ++ restStack, frame, kont, mkont1, trail, h) + eval(rest, retStack.take(funcTy.out.size) ++ restStack, frame, kont, trail1, mkont1, trail2, h) } - eval(inner, inputs, frame, restK, mkont, restK :: trail, h) + eval(inner, inputs, frame, restK, trail1, mkont, restK :: trail2, h) case Loop(ty, inner) => // We construct two continuations, one for the break (to the begining of the loop), // and one for fall-through to the next instruction following the syntactic structure // of the program. val funcTy = getFuncType(ty) val (inputs, restStack) = stack.splitAt(funcTy.inps.size) - val restK: Cont[Ans] = (retStack, mkont) => - eval(rest, retStack.take(funcTy.out.size) ++ restStack, frame, kont, mkont, trail, h) - def loop(retStack: List[Value], mkont: MCont[Ans]): Ans = - eval(inner, retStack.take(funcTy.inps.size), frame, restK, mkont, loop _ :: trail, h) + val restK: Cont[Ans] = (retStack, trail1, mkont) => + eval(rest, retStack.take(funcTy.out.size) ++ restStack, frame, kont, trail1, mkont, trail2, h) + def loop(retStack: List[Value], trail1: List[Cont[Ans]], mkont: MCont[Ans]): Ans = + eval(inner, retStack.take(funcTy.inps.size), frame, restK, trail1, mkont, (loop _ : Cont[Ans]):: trail2, h) // for example, loop here doesn't change the meta-continuation at all // compare with block - loop(inputs, mkont) + loop(inputs, trail1, mkont) case If(ty, thn, els) => val funcTy = getFuncType(ty) val I32V(cond) :: newStack = stack val inner = if (cond != 0) thn else els val (inputs, restStack) = newStack.splitAt(funcTy.inps.size) - val restK: Cont[Ans] = (retStack, mkont) => - eval(rest, retStack.take(funcTy.out.size) ++ restStack, frame, kont, mkont, trail, h) - eval(inner, inputs, frame, restK, mkont, restK :: trail, h) + val restK: Cont[Ans] = (retStack, trail1, mkont) => + eval(rest, retStack.take(funcTy.out.size) ++ restStack, frame, kont, trail1, mkont, trail2, h) + eval(inner, inputs, frame, restK, trail1, mkont, restK :: trail2, h) case Br(label) => - trail(label)(stack, mkont) // s => ().asInstanceOf[Ans]) //mkont) + trail2(label)(stack, trail1, mkont) // s => ().asInstanceOf[Ans]) //mkont) case BrIf(label) => val I32V(cond) :: newStack = stack - if (cond != 0) trail(label)(newStack, mkont) - else eval(rest, newStack, frame, kont, mkont, trail, h) + if (cond != 0) trail2(label)(newStack, trail1, mkont) + else eval(rest, newStack, frame, kont, trail1, mkont, trail2, h) case BrTable(labels, default) => val I32V(cond) :: newStack = stack val goto = if (cond < labels.length) labels(cond) else default - trail(goto)(newStack, mkont) - case Return => trail.last(stack, mkont) - case Call(f) => evalCall(f, rest, stack, frame, kont, mkont, trail, h, false) - case ReturnCall(f) => evalCall(f, rest, stack, frame, kont, mkont, trail, h, true) + trail2(goto)(newStack, trail1, mkont) + case Return => trail2.last(stack, trail1, mkont) + case Call(f) => evalCall(f, rest, stack, frame, kont, trail1, mkont, trail2, h, false) + case ReturnCall(f) => evalCall(f, rest, stack, frame, kont, trail1, mkont, trail2, h, true) case RefFunc(f) => // TODO: RefFuncV stores an applicable function, instead of a syntactic structure - eval(rest, RefFuncV(f) :: stack, frame, kont, mkont, trail, h) + eval(rest, RefFuncV(f) :: stack, frame, kont, trail1, mkont, trail2, h) case CallRef(ty) => val RefFuncV(f) :: newStack = stack - evalCall(f, rest, newStack, frame, kont, mkont, trail, h, false) + evalCall(f, rest, newStack, frame, kont, trail1, mkont, trail2, h, false) // resumable try-catch exception handling: case TryCatch(es1, es2) => - val join: MCont[Ans] = (newStack) => eval(rest, stack, frame, kont, mkont, trail, h) - // the `restK` for catch block (es2) is the join point - // the restK simply applies the meta-continuation, this is the same the [nil] case - // where we fall back to join point - val idK: Cont[Ans] = (s, m) => m(s) - val newHandler: Handler[Ans] = (newStack) => eval(es2, newStack, frame, idK, join, trail, h) - eval(es1, List(), frame, idK, join, trail, newHandler) + // put trail1 into join point + val join: MCont[Ans] = (newStack) => eval(rest, stack, frame, kont, trail1, mkont, trail2, h) + // here we clear the trail2, to forbid breaking out of the try-catch block + val newHandler: Handler[Ans] = (newStack) => eval(es2, newStack, frame, init: Cont[Ans], List(), join, List(), h) + eval(es1, List(), frame, init: Cont[Ans], List(), join, List(), newHandler) case Resume0() => val (resume: TCContV[Ans]) :: newStack = stack - val k: Cont[Ans] = (s, m) => eval(rest, newStack /*!*/, frame, kont, m, trail, h) - resume.k(List(), k, mkont) + val k: Cont[Ans] = (s, trail1, m) => eval(rest, newStack /*!*/, frame, kont, trail1, m, trail2, h) + resume.k(List(), k, trail1, mkont) case Throw() => val err :: newStack = stack - // kont composed with k + // kont composed with k1 and trail1 // note that kr doesn't use the stack at all // it only takes the err value - def kr(s: Stack, k1: Cont[Ans], m: MCont[Ans]): Ans = { - val kontK: Cont[Ans] = (s1, m1) => kont(s1, s2 => k1(s2, m1)) - eval(rest, newStack /*!*/, frame, kontK, m /*vs mkont?*/, trail, h) + def kr(s: Stack, k1: Cont[Ans], newTrail1: List[Cont[Ans]], m: MCont[Ans]): Ans = { + eval(rest, newStack /*!*/, frame, kont, trail1 ++ List(k1) ++ newTrail1, m /*vs mkont?*/, trail2, h) } h(List(err, TCContV(kr))) @@ -241,7 +251,7 @@ case class EvaluatorFX(module: ModuleInstance) { case ContNew(ty) => val RefFuncV(f) :: newStack = stack - def kr(s: Stack, k1: Cont[Ans], mk: MCont[Ans], handler: Handler[Ans]): Ans = { + def kr(s: Stack, k1: Cont[Ans], trail1: List[Cont[Ans]], mk: MCont[Ans], handler: Handler[Ans]): Ans = { // k1 is rest for `resume` // mk holds the handler for `suspend` @@ -250,26 +260,25 @@ case class EvaluatorFX(module: ModuleInstance) { // and it must be handled by the default handler (k1) or the handler associated // to `suspend` - // we can discard trail since it is a new function call (similar to `kont`) - val empty: Cont[Ans] = (s1, m1) => m1(s1) - evalCall(f, List(), s, frame, empty, mk, List(), handler, false) + // we can discard trail2 since it is a new function call (similar to `kont`) + evalCall(f, List(), s, frame, init: Cont[Ans], List(), mk, List(), handler, false) } - eval(rest, ContV(kr) :: newStack, frame, kont, mkont, trail, h) + eval(rest, ContV(kr) :: newStack, frame, kont, trail1, mkont, trail2, h) case Suspend(tag_id) => { // println(s"${RED}Unimplimented Suspending tag $tag_id") // add the continuation on the stack - val k = (s: Stack, k1: Cont[Ans], m: MCont[Ans], handler: Handler[Ans]) => { + val k = (s: Stack, k1: Cont[Ans], trail1: List[Cont[Ans]], m: MCont[Ans], handler: Handler[Ans]) => { // TODO: does the following work? // val kontK: Cont[Ans] = (s1, m1) => kont(s1, s2 => k1(s2, m1)) // Q: is it okay to forget `k1` and `mkont` here? // Ans: No! Because the resumable continuation might be install by // a different `resume` with a different set of handlers - val newMk: MCont[Ans] = (s) => k1(s, m) - eval(rest, s, frame, kont, newMk, trail, handler) + val newMk: MCont[Ans] = (s) => k1(s, trail1, m) + eval(rest, s, frame, kont, trail1, newMk, trail2, handler) } val newStack = ContV(k) :: stack h(newStack) @@ -285,9 +294,8 @@ case class EvaluatorFX(module: ModuleInstance) { if (handler.length == 0) { // the metacontinuation contains the default handler - val mk: MCont[Ans] = (s) => eval(rest, s, frame, kont, mkont, trail, h) - val emptyK: Cont[Ans] = (s, m) => m(s) - f.k(inputs, emptyK, mk, h) + val mk: MCont[Ans] = (s) => eval(rest, s, frame, kont, trail1, mkont, trail2, h) + f.k(inputs, init, List(), mk, h) } else { if (handler.length > 1) { throw new Exception("only single tag is supported") @@ -295,13 +303,12 @@ case class EvaluatorFX(module: ModuleInstance) { // if suspend happens, the label id holds the handler val Handler(tagId, labelId) = handler.head - val newHandler: Handler[Ans] = (newStack) => trail(labelId)(newStack, mkont) + val newHandler: Handler[Ans] = (newStack) => trail2(labelId)(newStack, trail1, mkont) // f might be handled by the default handler (namely kont), or by the // handler specified by tags (newhandler, which has the same type as meta-continuation) - val mk: MCont[Ans] = (s) => eval(rest, s, frame, kont, mkont, trail, h) - val emptyK: Cont[Ans] = (s, m) => m(s) - f.k(inputs, emptyK, mk, newHandler) + val mk: MCont[Ans] = (s) => eval(rest, s, frame, kont, trail1, mkont, trail2, h) + f.k(inputs, init, List(), mk, newHandler) } @@ -321,19 +328,19 @@ case class EvaluatorFX(module: ModuleInstance) { val (inputs, restStack) = newStack.splitAt(inputSize) // partially apply the old continuation - def kr(s: Stack, k1: Cont[Ans], mk: MCont[Ans], handler: Handler[Ans]): Ans = { - f.k(s ++ inputs, k1, mk, handler) + def kr(s: Stack, k1: Cont[Ans], trail1: List[Cont[Ans]], mk: MCont[Ans], handler: Handler[Ans]): Ans = { + f.k(s ++ inputs, k1, trail1, mk, handler) } - eval(rest, ContV(kr) :: restStack, frame, kont, mkont, trail, h) + eval(rest, ContV(kr) :: restStack, frame, kont, trail1, mkont, trail2, h) case CallRef(ty) => val RefFuncV(f) :: newStack = stack - evalCall(f, rest, newStack, frame, kont, mkont, trail, h, false) + evalCall(f, rest, newStack, frame, kont, trail1, mkont, trail2, h, false) case CallRef(ty) => val RefFuncV(f) :: newStack = stack - evalCall(f, rest, newStack, frame, kont, mkont, trail, h, false) + evalCall(f, rest, newStack, frame, kont, trail1, mkont, trail2, h, false) case _ => println(inst) @@ -394,11 +401,11 @@ case class EvaluatorFX(module: ModuleInstance) { val handler0: Handler[Ans] = stack => throw new Exception(s"Uncaught exception: $stack") // initialized locals val frame = Frame(ArrayBuffer(locals.map(zero(_)): _*)) - eval(instrs, List(), frame, halt, mhalt, List(halt), handler0) + eval(instrs, List(), frame, halt, List(), mhalt, List(halt), handler0) } def evalTop(m: ModuleInstance): Unit = { - val halt: Cont[Unit] = (stack, m) => m(stack) + val halt: Cont[Unit] = init evalTop(halt, stack => ()) } } diff --git a/src/main/scala/wasm/MiniWasmScript.scala b/src/main/scala/wasm/MiniWasmScript.scala index 010ea5f4b..7ab553d13 100644 --- a/src/main/scala/wasm/MiniWasmScript.scala +++ b/src/main/scala/wasm/MiniWasmScript.scala @@ -33,11 +33,11 @@ sealed class ScriptRunner { type Cont = evaluator.Cont[evaluator.Stack] type MCont = evaluator.MCont[evaluator.Stack] type Handler = evaluator.Handler[evaluator.Stack] - val k: Cont = (retStack, m) => m(retStack) + val k: Cont = evaluator.init; val mk: MCont = (retStack) => retStack val h0: Handler = stack => throw new Exception(s"Uncaught exception: $stack") // TODO: change this back to Evaluator if we are just testing original stuff - val actual = evaluator.eval(instrs, List(), Frame(ArrayBuffer(args: _*)), k, mk, List(k), h0) + val actual = evaluator.eval(instrs, List(), Frame(ArrayBuffer(args: _*)), k, List(), mk, List(k), h0) println(s"expect = $expect") println(s"actual = $actual") assert(actual == expect) diff --git a/src/test/scala/genwasym/TestFx.scala b/src/test/scala/genwasym/TestFx.scala index 9e6d72e7d..ef799500c 100644 --- a/src/test/scala/genwasym/TestFx.scala +++ b/src/test/scala/genwasym/TestFx.scala @@ -28,7 +28,7 @@ class TestFx extends FunSuite { val evaluator = EvaluatorFX(ModuleInstance(module)) type Cont = evaluator.Cont[Unit] type MCont = evaluator.MCont[Unit] - val haltK: Cont = (stack, m) => m(stack) + val haltK: Cont = evaluator.init; val haltMK: MCont = (stack) => { // println(s"halt cont: $stack") expected match { @@ -127,20 +127,44 @@ class TestFx extends FunSuite { test("try-catch-block") { testFileOutput("./benchmarks/wasm/trycatch/try_catch_block.wat", List(1, 2, 3, 4, 5)) } + + test("try-catch-br2") { + testFileOutput("./benchmarks/wasm/trycatch/try_catch_br2.wat", List(1, 2, 6, 4, 5)) + } - // Note: the interaction between try-catch and block is not well-defined yet - - /* test("try-catch-br") { - testFileOutput("./benchmarks/wasm/trycatch/try_catch_br.wat", List(1, 2, 6)) + // break out of try block is not allowed + assertThrows[IndexOutOfBoundsException] { + testFileOutput("./benchmarks/wasm/trycatch/try_catch_br.wat", List(1, 2, 6)) + } } - test("try-catch-br2") { - testFileOutput("./benchmarks/wasm/trycatch/try_catch_br2.wat", List(1, 2, 6, 4, 5)) + test("try-catch-throw-twice") { + testFileOutput("./benchmarks/wasm/trycatch/throw_twice.wat", List(1, 2, 6, 2, 3, 4, 4, 5)) + } + + test("try-catch-throw-twice2") { + testFileOutput("./benchmarks/wasm/trycatch/throw_twice2.wat", List(1, 2, 6, 2, 3, 4, 4, 5)) + } + + test("try-catch-br3") { + testFileOutput("./benchmarks/wasm/trycatch/try_catch_br3.wat", List(1, 2, 3, 4, 5)) + } + + test("try-catch-br4") { + // simplified version's output(#75): List(1, 2, 6, 2, 7, 4, 4, 5) + testFileOutput("./benchmarks/wasm/trycatch/try_catch_br4.wat", List(1, 2, 6, 2, 7, 4, 5)) + } + + test("try-catch-catch-br") { + testFileOutput("./benchmarks/wasm/trycatch/try_catch_catch_br.wat", List(1, 2, 6, 4, 6, 5)) } - */ /* REAL WASMFX STUFF */ + // TODO: test after implemented cont_bind3 + // test("simple script") { + // TestWastFile("./benchmarks/wasm/wasmfx/cont_bind3.bin.wast") + // } test("cont") { // testFile("./benchmarks/wasm/wasmfx/callcont.wast", None, ExpInt(11))