From 55593e79d3a0e3bcbcec2ad2fe1c6c8121b2ffe5 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Thu, 16 Jan 2025 00:38:58 +0800 Subject: [PATCH 1/4] remove mcont --- src/main/scala/wasm/MiniWasmFX.scala | 142 ++++++++++++----------- src/main/scala/wasm/MiniWasmScript.scala | 4 +- src/test/scala/genwasym/TestFx.scala | 5 +- 3 files changed, 76 insertions(+), 75 deletions(-) diff --git a/src/main/scala/wasm/MiniWasmFX.scala b/src/main/scala/wasm/MiniWasmFX.scala index 1a01de468..4d6b85add 100644 --- a/src/main/scala/wasm/MiniWasmFX.scala +++ b/src/main/scala/wasm/MiniWasmFX.scala @@ -15,7 +15,7 @@ case class EvaluatorFX(module: ModuleInstance) { type Stack = List[Value] trait Cont[A] { - def apply(stack: Stack, trail: Trail[A], mcont: MCont[A], handler: Handlers[A]): A + def apply(stack: Stack, trail: Trail[A], handler: Handlers[A]): A } type Trail[A] = List[(Cont[A], List[Int])] // trail items are pairs of continuation and tags type MCont[A] = Stack => A @@ -23,41 +23,40 @@ case class EvaluatorFX(module: ModuleInstance) { type Handler[A] = Stack => A type Handlers[A] = List[(Int, Handler[A])] - case class ContV[A](k: (Stack, Cont[A], Trail[A], MCont[A], Handlers[A]) => A) extends Value { + case class ContV[A](k: (Stack, Cont[A], Trail[A], Handlers[A]) => A) extends Value { def tipe(implicit m: ModuleInstance): ValueType = ??? // override def toString: String = "ContV" } // initK is a continuation that simply returns the inputed stack - def initK[Ans](s: Stack, trail: Trail[Ans], mkont: MCont[Ans], hs: Handlers[Ans]): Ans = + def initK[Ans](s: Stack, trail: Trail[Ans], hs: Handlers[Ans]): Ans = trail match { - case (k1, _) :: trail => k1(s, trail, mkont, hs) - case Nil => mkont(s) + case (k1, _) :: trail => k1(s, trail, hs) + case Nil => throw new Exception("No halting continuation in trail") } - def eval1[Ans](inst: Instr, stack: Stack, frame: Frame, - kont: Cont[Ans], trail: Trail[Ans], mkont: MCont[Ans], - brTable: List[Cont[Ans]], hs: Handlers[Ans]): Ans = { + def eval1[Ans](inst: Instr, stack: Stack, frame: Frame, kont: Cont[Ans], + trail: Trail[Ans], brTable: List[Cont[Ans]], hs: Handlers[Ans]): Ans = { // System.err.println(f"[DEBUG] ${inst} | ${frame} | ${stack.reverse} | handlers: ${hs}"); inst match { - case Drop => kont(stack.tail, trail, mkont, hs) + case Drop => kont(stack.tail, trail, hs) case Select(_) => val I32V(cond) :: v2 :: v1 :: newStack = stack val value = if (cond == 0) v1 else v2 - kont(value :: newStack, trail, mkont, hs) + kont(value :: newStack, trail, hs) case LocalGet(i) => - kont(frame.locals(i) :: stack, trail, mkont, hs) + kont(frame.locals(i) :: stack, trail, hs) case LocalSet(i) => val value :: newStack = stack frame.locals(i) = value - kont(newStack, trail, mkont, hs) + kont(newStack, trail, hs) case LocalTee(i) => val value :: newStack = stack frame.locals(i) = value - kont(stack, trail, mkont, hs) + kont(stack, trail, hs) case GlobalGet(i) => - kont(module.globals(i).value :: stack, trail, mkont, hs) + kont(module.globals(i).value :: stack, trail, hs) case GlobalSet(i) => val value :: newStack = stack module.globals(i).ty match { @@ -66,16 +65,16 @@ case class EvaluatorFX(module: ModuleInstance) { case GlobalType(_, true) => throw new Exception("Invalid type") case _ => throw new Exception("Cannot set immutable global") } - kont(newStack, trail, mkont, hs) + kont(newStack, trail, hs) case MemorySize => - kont(I32V(module.memory.head.size) :: stack, trail, mkont, hs) + kont(I32V(module.memory.head.size) :: stack, trail, hs) case MemoryGrow => val I32V(delta) :: newStack = stack val mem = module.memory.head val oldSize = mem.size mem.grow(delta) match { - case Some(e) => kont(I32V(-1) :: newStack, trail, mkont, hs) - case _ => kont(I32V(oldSize) :: newStack, trail, mkont, hs) + case Some(e) => kont(I32V(-1) :: newStack, trail, hs) + case _ => kont(I32V(oldSize) :: newStack, trail, hs) } case MemoryFill => val I32V(value) :: I32V(offset) :: I32V(size) :: newStack = stack @@ -83,7 +82,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) - kont(newStack, trail, mkont, hs) + kont(newStack, trail, hs) } case MemoryCopy => val I32V(n) :: I32V(src) :: I32V(dest) :: newStack = stack @@ -91,82 +90,82 @@ case class EvaluatorFX(module: ModuleInstance) { throw new Exception("Out of bounds memory access") else { module.memory.head.copy(dest, src, n) - kont(newStack, trail, mkont, hs) + kont(newStack, trail, hs) } - case Const(n) => kont(n :: stack, trail, mkont, hs) + case Const(n) => kont(n :: stack, trail, hs) case Binary(op) => val v2 :: v1 :: newStack = stack - kont(evalBinOp(op, v1, v2) :: newStack, trail, mkont, hs) + kont(evalBinOp(op, v1, v2) :: newStack, trail, hs) case Unary(op) => val v :: newStack = stack - kont(evalUnaryOp(op, v) :: newStack, trail, mkont, hs) + kont(evalUnaryOp(op, v) :: newStack, trail, hs) case Compare(op) => val v2 :: v1 :: newStack = stack - kont(evalRelOp(op, v1, v2) :: newStack, trail, mkont, hs) + kont(evalRelOp(op, v1, v2) :: newStack, trail, hs) case Test(op) => val v :: newStack = stack - kont(evalTestOp(op, v) :: newStack, trail, mkont, hs) + kont(evalTestOp(op, v) :: newStack, trail, hs) case Store(StoreOp(align, offset, ty, None)) => val I32V(v) :: I32V(addr) :: newStack = stack module.memory(0).storeInt(addr + offset, v) - kont(newStack, trail, mkont, hs) + kont(newStack, trail, hs) case Load(LoadOp(align, offset, ty, None, None)) => val I32V(addr) :: newStack = stack val value = module.memory(0).loadInt(addr + offset) - kont(I32V(value) :: newStack, trail, mkont, hs) - case Nop => kont(stack, trail, mkont, hs) + kont(I32V(value) :: newStack, trail, hs) + case Nop => kont(stack, trail, hs) case Unreachable => throw Trap() case Block(ty, inner) => val funcTy = getFuncType(ty) val (inputs, restStack) = stack.splitAt(funcTy.inps.size) - val escape: Cont[Ans] = (s1, t1, m1, h1) => kont(s1.take(funcTy.out.size) ++ restStack, t1, m1, h1) - evalList(inner, inputs, frame, escape, trail, mkont, escape::brTable, hs) + val escape: Cont[Ans] = (s1, t1, h1) => kont(s1.take(funcTy.out.size) ++ restStack, t1, h1) + evalList(inner, inputs, frame, escape, trail, escape::brTable, hs) case Loop(ty, inner) => val funcTy = getFuncType(ty) val (inputs, restStack) = stack.splitAt(funcTy.inps.size) - val escape: Cont[Ans] = (s1, t1, m1, h1) => kont(s1.take(funcTy.out.size) ++ restStack, t1, m1, h1) - def loop(retStack: List[Value], trail1: Trail[Ans], mkont: MCont[Ans], h1: Handlers[Ans]): Ans = - evalList(inner, retStack.take(funcTy.inps.size), frame, escape, trail, mkont, (loop _ : Cont[Ans])::brTable, h1) - loop(inputs, trail, mkont, hs) + val escape: Cont[Ans] = (s1, t1, h1) => kont(s1.take(funcTy.out.size) ++ restStack, t1, h1) + def loop(retStack: List[Value], trail1: Trail[Ans], h1: Handlers[Ans]): Ans = + evalList(inner, retStack.take(funcTy.inps.size), frame, escape, trail, (loop _ : Cont[Ans])::brTable, h1) + loop(inputs, trail, hs) 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 escape: Cont[Ans] = (s1, t1, m1, h1) => kont(s1.take(funcTy.out.size) ++ restStack, t1, m1, h1) - evalList(inner, inputs, frame, escape, trail, mkont, escape::brTable, hs) + val escape: Cont[Ans] = (s1, t1, h1) => kont(s1.take(funcTy.out.size) ++ restStack, t1, h1) + evalList(inner, inputs, frame, escape, trail, escape::brTable, hs) case Br(label) => - brTable(label)(stack, trail, mkont, hs) + brTable(label)(stack, trail, hs) case BrIf(label) => val I32V(cond) :: newStack = stack - if (cond != 0) brTable(label)(newStack, trail, mkont, hs) - else kont(newStack, trail, mkont, hs) + if (cond != 0) brTable(label)(newStack, trail, hs) + else kont(newStack, trail, hs) case BrTable(labels, default) => val I32V(cond) :: newStack = stack val goto = if (cond < labels.length) labels(cond) else default - brTable(goto)(newStack, trail, mkont, hs) + brTable(goto)(newStack, trail, hs) case Return => - brTable.last(stack, trail, mkont, hs) - case Call(f) => evalCall1(f, stack, frame, kont, trail, mkont, brTable, hs, false) + brTable.last(stack, trail, hs) + case Call(f) => evalCall1(f, stack, frame, kont, trail, brTable, hs, false) case ReturnCall(f) => // System.err.println(s"[DEBUG] return call: $f") - evalCall1(f, stack, frame, kont, trail, mkont, brTable, hs, true) + evalCall1(f, stack, frame, kont, trail, brTable, hs, true) case RefFunc(f) => // TODO: RefFuncV stores an applicable function, instead of a syntactic structure - kont(RefFuncV(f) :: stack, trail, mkont, hs) + kont(RefFuncV(f) :: stack, trail, hs) // WasmFX effect handlers: case ContNew(ty) => val RefFuncV(f) :: newStack = stack - def kr(s: Stack, k1: Cont[Ans], t1: Trail[Ans], m1: MCont[Ans], hs: Handlers[Ans]): Ans = { - evalCall1(f, s, frame/*?*/, k1, t1, m1, List(), hs, false) + def kr(s: Stack, k1: Cont[Ans], t1: Trail[Ans], hs: Handlers[Ans]): Ans = { + evalCall1(f, s, frame/*?*/, k1, t1, List(), hs, false) } - kont(ContV(kr) :: newStack, trail, mkont, hs) + kont(ContV(kr) :: newStack, trail, hs) case Suspend(tagId) => val FuncType(_, inps, out) = module.tags(tagId) val (inputs, restStack) = stack.splitAt(inps.size) // System.err.println(s"[DEBUG] handlers: $hs") // System.err.println(s"[DEBUG] trail: $trail") - val kr = (s: Stack, _: Cont[Ans], t1: Trail[Ans], m1: MCont[Ans], hs1: Handlers[Ans]) => { + val kr = (s: Stack, _: Cont[Ans], t1: Trail[Ans], hs1: Handlers[Ans]) => { // construct a new trail by ignoring the default handler val index = trail.indexWhere { case (_, tags) => tags.contains(tagId) } val newTrail = if (index >= 0) trail.take(index) else trail @@ -175,7 +174,7 @@ case class EvaluatorFX(module: ModuleInstance) { // Q: Should we clear tags in the `newTrail`? Is that possible suspend target tag in hs1 but also in newTrail? // A: Yes, we should maintain the consistency between `hs1` and `newTrail + t1`. // mkont lost here, and it's safe if we never modify it - kont(s ++ restStack, newTrail.map({ case (c, _) => (c, List()) }) ++ t1, m1, hs1) + kont(s ++ restStack, newTrail.map({ case (c, _) => (c, List()) }) ++ t1, hs1) } val newStack = ContV(kr) :: inputs hs.find(_._1 == tagId) match { @@ -191,12 +190,12 @@ case class EvaluatorFX(module: ModuleInstance) { val (inputs, restStack) = newStack.splitAt(inps.size) val newHs: List[(Int, Handler[Ans])] = handler.map { case Handler(tagId, labelId) => - val hh: Handler[Ans] = s1 => brTable(labelId)(s1, trail, mkont/*???*/, hs) + val hh: Handler[Ans] = s1 => brTable(labelId)(s1, trail, hs) (tagId, hh) } val tags = handler.map(_.tag) // rather than push `kont` to meta-continuation, maybe we can push it to `trail`? - f.k(inputs, initK, List((kont,tags)) ++ trail, mkont, newHs ++ hs) + f.k(inputs, initK, List((kont,tags)) ++ trail, newHs ++ hs) case ContBind(oldContTyId, newConTyId) => val (f: ContV[Ans]) :: newStack = stack @@ -209,14 +208,14 @@ case class EvaluatorFX(module: ModuleInstance) { val inputSize = oldParamTy.size - newParamTy.size val (inputs, restStack) = newStack.splitAt(inputSize) // partially apply the old continuation - def kr(s: Stack, k1: Cont[Ans], t1: Trail[Ans], mk: MCont[Ans], handlers: Handlers[Ans]): Ans = { - f.k(s ++ inputs, k1, t1, mk, handlers) + def kr(s: Stack, k1: Cont[Ans], t1: Trail[Ans], handlers: Handlers[Ans]): Ans = { + f.k(s ++ inputs, k1, t1, handlers) } - kont(ContV(kr) :: restStack, trail, mkont, hs) + kont(ContV(kr) :: restStack, trail, hs) case CallRef(ty) => val RefFuncV(f) :: newStack = stack - evalCall1(f, newStack, frame, kont, trail, mkont, brTable, hs, false) + evalCall1(f, newStack, frame, kont, trail, brTable, hs, false) case _ => println(inst) @@ -224,14 +223,13 @@ case class EvaluatorFX(module: ModuleInstance) { } } - def evalList[Ans](insts: List[Instr], stack: Stack, frame: Frame, - kont: Cont[Ans], trail1: Trail[Ans], mkont: MCont[Ans], - brTable: List[Cont[Ans]], hs: Handlers[Ans]): Ans = { + def evalList[Ans](insts: List[Instr], stack: Stack, frame: Frame, kont: Cont[Ans], + trail1: Trail[Ans], brTable: List[Cont[Ans]], hs: Handlers[Ans]): Ans = { insts match { - case Nil => kont(stack, trail1, mkont, hs) + case Nil => kont(stack, trail1, hs) case inst :: rest => - val newKont: Cont[Ans] = (s1, t1, m1, h1) => evalList(rest, s1, frame, kont, t1, m1, brTable, h1) - eval1(inst, stack, frame, newKont, trail1, mkont, brTable, hs) + val newKont: Cont[Ans] = (s1, t1, h1) => evalList(rest, s1, frame, kont, t1, brTable, h1) + eval1(inst, stack, frame, newKont, trail1, brTable, hs) } } @@ -240,7 +238,6 @@ case class EvaluatorFX(module: ModuleInstance) { frame: Frame, kont: Cont[Ans], trail: Trail[Ans], - mkont: MCont[Ans], brTable: List[Cont[Ans]], // can be removed h: Handlers[Ans], isTail: Boolean): Ans = @@ -252,31 +249,31 @@ case class EvaluatorFX(module: ModuleInstance) { val newFrame = Frame(ArrayBuffer(frameLocals: _*)) if (isTail) { // when tail call, share the continuation for returning with the callee - evalList(body, List(), newFrame, brTable.last, trail, mkont, List(brTable.last), h) + evalList(body, List(), newFrame, brTable.last, trail, List(brTable.last), h) } else { - val restK: Cont[Ans] = (s1, t1, m1, h1) => kont(s1.take(ty.out.size) ++ newStack, t1, m1, h1) + val restK: Cont[Ans] = (s1, t1, h1) => kont(s1.take(ty.out.size) ++ newStack, t1, h1) // We make a new brTable by `restK`, since function creates a new block to escape // (more or less like `return`) - evalList(body, List(), newFrame, restK, trail, mkont, List(restK), h) + evalList(body, List(), newFrame, restK, trail, List(restK), h) } case Import("console", "log", _) => // println(s"[DEBUG] current stack: $stack") val I32V(v) :: newStack = stack println(v) - kont(newStack, trail, mkont, h) + kont(newStack, trail, h) case Import("spectest", "print_i32", _) => // println(s"[DEBUG] current stack: $stack") val I32V(v) :: newStack = stack println(v) - kont(newStack, trail, mkont, h) + kont(newStack, trail, h) case Import(_, _, _) => throw new Exception(s"Unknown import at $funcIndex") case _ => throw new Exception(s"Definition at $funcIndex is not callable") } // If `main` is given, then we use that function as the entry point of the program; // otherwise, we look up the top-level `start` instruction to locate the entry point. - def evalTop[Ans](halt: Cont[Ans], mhalt: MCont[Ans], main: Option[String] = None): Ans = { + def evalTop[Ans](halt: Cont[Ans], main: Option[String] = None): Ans = { val instrs = main match { case Some(func_name) => module.defs.flatMap({ @@ -326,9 +323,14 @@ case class EvaluatorFX(module: ModuleInstance) { if (instrs.isEmpty) println("Warning: nothing is executed") // initialized locals val frame = Frame(ArrayBuffer(locals.map(zero(_)): _*)) - evalList(instrs, List(), frame, halt, List(), mhalt, List(halt), List()) + evalList(instrs, List(), frame, initK[Ans], List((halt, List())), List(halt), List()) } - def evalTop(m: ModuleInstance): Unit = evalTop(initK[Unit], stack => ()) + def evalTop(m: ModuleInstance): Unit = + evalTop(((stack, trail, _hs) => { + if (!trail.isEmpty) { + throw new Exception("Composing something after halt continuation") + } + }): Cont[Unit]) } diff --git a/src/main/scala/wasm/MiniWasmScript.scala b/src/main/scala/wasm/MiniWasmScript.scala index 8edba68d0..5cc9de3fa 100644 --- a/src/main/scala/wasm/MiniWasmScript.scala +++ b/src/main/scala/wasm/MiniWasmScript.scala @@ -42,9 +42,9 @@ sealed class ScriptRunner { type MCont = evaluator.MCont[evaluator.Stack] type Handler = evaluator.Handler[evaluator.Stack] val k: Cont = evaluator.initK - val mk: MCont = (retStack) => retStack + val halt: Cont = (retStack, _, _) => retStack // Note: change this back to Evaluator if we are just testing original stuff - evaluator.evalList(instrs, List(), Frame(ArrayBuffer(args: _*)), k, List(), mk, List(k), List()) + evaluator.evalList(instrs, List(), Frame(ArrayBuffer(args: _*)), k, List((halt, List())), List(k), List()) } def runCmd(cmd: Cmd): Unit = { diff --git a/src/test/scala/genwasym/TestFx.scala b/src/test/scala/genwasym/TestFx.scala index 3781762c2..e0407f2e2 100644 --- a/src/test/scala/genwasym/TestFx.scala +++ b/src/test/scala/genwasym/TestFx.scala @@ -28,8 +28,7 @@ class TestFx extends FunSuite { val evaluator = EvaluatorFX(ModuleInstance(module)) type Cont = evaluator.Cont[Unit] type MCont = evaluator.MCont[Unit] - val haltK: Cont = evaluator.initK - val haltMK: MCont = (stack) => { + val haltK: Cont = (stack, _trail, _hs) => { // println(s"halt cont: $stack") expected match { case ExpInt(e) => assert(stack(0) == I32V(e)) @@ -37,7 +36,7 @@ class TestFx extends FunSuite { case Ignore => () } } - evaluator.evalTop(haltK, haltMK, main) + evaluator.evalTop(haltK, main) } // So far it assumes that the output is multi-line integers From b513e03e9ad828ec14732baf83d056e551021162 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Thu, 16 Jan 2025 01:06:46 +0800 Subject: [PATCH 2/4] trail compostion is different with list concat when haltK is there --- src/main/scala/wasm/MiniWasmFX.scala | 1 + src/test/scala/genwasym/TestFx.scala | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/scala/wasm/MiniWasmFX.scala b/src/main/scala/wasm/MiniWasmFX.scala index 4d6b85add..deedd824d 100644 --- a/src/main/scala/wasm/MiniWasmFX.scala +++ b/src/main/scala/wasm/MiniWasmFX.scala @@ -329,6 +329,7 @@ case class EvaluatorFX(module: ModuleInstance) { def evalTop(m: ModuleInstance): Unit = evalTop(((stack, trail, _hs) => { if (!trail.isEmpty) { + // this assertion is wrong throw new Exception("Composing something after halt continuation") } }): Cont[Unit]) diff --git a/src/test/scala/genwasym/TestFx.scala b/src/test/scala/genwasym/TestFx.scala index e0407f2e2..91ab8a49d 100644 --- a/src/test/scala/genwasym/TestFx.scala +++ b/src/test/scala/genwasym/TestFx.scala @@ -28,7 +28,12 @@ class TestFx extends FunSuite { val evaluator = EvaluatorFX(ModuleInstance(module)) type Cont = evaluator.Cont[Unit] type MCont = evaluator.MCont[Unit] - val haltK: Cont = (stack, _trail, _hs) => { + val haltK: Cont = (stack, trail, _hs) => { + if (!trail.isEmpty) { + // TODO: this throw will fail the test, we should redesign our trail composition rather than list append + System.err.println(s"[Debug]: $trail") + throw new Exception("Trail is not empty") + } // println(s"halt cont: $stack") expected match { case ExpInt(e) => assert(stack(0) == I32V(e)) From 0c99725f3d79a1f90b3e881531b5280abfc83351 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Thu, 16 Jan 2025 18:54:07 +0800 Subject: [PATCH 3/4] when start evaluation: store initK in brtable and haltK in trail --- src/main/scala/wasm/MiniWasmFX.scala | 3 +-- src/test/scala/genwasym/TestFx.scala | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/scala/wasm/MiniWasmFX.scala b/src/main/scala/wasm/MiniWasmFX.scala index deedd824d..9689c7501 100644 --- a/src/main/scala/wasm/MiniWasmFX.scala +++ b/src/main/scala/wasm/MiniWasmFX.scala @@ -323,13 +323,12 @@ case class EvaluatorFX(module: ModuleInstance) { if (instrs.isEmpty) println("Warning: nothing is executed") // initialized locals val frame = Frame(ArrayBuffer(locals.map(zero(_)): _*)) - evalList(instrs, List(), frame, initK[Ans], List((halt, List())), List(halt), List()) + evalList(instrs, List(), frame, initK[Ans], List((halt, List())), List(initK: Cont[Ans]), List()) } def evalTop(m: ModuleInstance): Unit = evalTop(((stack, trail, _hs) => { if (!trail.isEmpty) { - // this assertion is wrong throw new Exception("Composing something after halt continuation") } }): Cont[Unit]) diff --git a/src/test/scala/genwasym/TestFx.scala b/src/test/scala/genwasym/TestFx.scala index 91ab8a49d..5054c100f 100644 --- a/src/test/scala/genwasym/TestFx.scala +++ b/src/test/scala/genwasym/TestFx.scala @@ -30,7 +30,7 @@ class TestFx extends FunSuite { type MCont = evaluator.MCont[Unit] val haltK: Cont = (stack, trail, _hs) => { if (!trail.isEmpty) { - // TODO: this throw will fail the test, we should redesign our trail composition rather than list append + // this throw will never reach, trail will never been appended System.err.println(s"[Debug]: $trail") throw new Exception("Trail is not empty") } From d5293daa01c6e8c3ed5e54c9c329f04284892154 Mon Sep 17 00:00:00 2001 From: ahuoguo Date: Wed, 29 Jan 2025 16:22:55 +0100 Subject: [PATCH 4/4] comment on initK --- src/main/scala/wasm/MiniWasmFX.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/scala/wasm/MiniWasmFX.scala b/src/main/scala/wasm/MiniWasmFX.scala index 9689c7501..5bde4f6d5 100644 --- a/src/main/scala/wasm/MiniWasmFX.scala +++ b/src/main/scala/wasm/MiniWasmFX.scala @@ -32,6 +32,8 @@ case class EvaluatorFX(module: ModuleInstance) { // initK is a continuation that simply returns the inputed stack def initK[Ans](s: Stack, trail: Trail[Ans], hs: Handlers[Ans]): Ans = trail match { + // Currently, the last element of the Trail is the halt continuation + // the exception will never be thrown case (k1, _) :: trail => k1(s, trail, hs) case Nil => throw new Exception("No halting continuation in trail") }