diff --git a/benchmarks/wasm/ack.wat b/benchmarks/wasm/ack.wat index 18895b42..e28608ef 100644 --- a/benchmarks/wasm/ack.wat +++ b/benchmarks/wasm/ack.wat @@ -51,7 +51,7 @@ (global (;1;) i32 (i32.const 1048576)) (global (;2;) i32 (i32.const 1048576)) (export "memory" (memory 0)) - (export "ack" (func $ack)) - (export "real_main" (func $real_main)) + (export "ack" (func 0)) + (export "real_main" (func 1)) (export "__data_end" (global 1)) (export "__heap_base" (global 2))) diff --git a/benchmarks/wasm/pow.wat b/benchmarks/wasm/pow.wat index 67ceb383..cd537b75 100644 --- a/benchmarks/wasm/pow.wat +++ b/benchmarks/wasm/pow.wat @@ -37,7 +37,7 @@ (global (;1;) i32 (i32.const 1048576)) (global (;2;) i32 (i32.const 1048576)) (export "memory" (memory 0)) - (export "power" (func $power)) - (export "real_main" (func $real_main)) + (export "power" (func 0)) + (export "real_main" (func 1)) (export "__data_end" (global 1)) (export "__heap_base" (global 2))) diff --git a/benchmarks/wasm/return.wat b/benchmarks/wasm/return.wat index 5f8deacd..f62890a9 100644 --- a/benchmarks/wasm/return.wat +++ b/benchmarks/wasm/return.wat @@ -12,6 +12,5 @@ call 0 unreachable ) - ;; (start 1) (export "$real_main" (func 1)) ) diff --git a/grammar/gen-wat-parser.sh b/grammar/gen-wat-parser.sh old mode 100644 new mode 100755 index 7f5348bb..42805b60 --- a/grammar/gen-wat-parser.sh +++ b/grammar/gen-wat-parser.sh @@ -1,15 +1,22 @@ #! /usr/bin/env bash # -ANTLR4=antlr-4.13.0-complete.jar +ANTLR4=antlr-4.13.2-complete.jar -java20 -jar $ANTLR4 WatLexer.g4 -java20 -jar $ANTLR4 -visitor WatParser.g4 +java -jar $ANTLR4 WatLexer.g4 +java -jar $ANTLR4 -visitor WatParser.g4 DST=../src/main/java/wasm/ echo "Copy WAT parsers into $DST" for file in "WatLexer.java" "WatParserBaseVisitor.java" "WatParserListener.java" "WatParserBaseListener.java" "WatParser.java" "WatParserVisitor.java" do - sed -i "1ipackage gensym.wasm;$line" $file - cp $file $DST/$file + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' $'1i\\\npackage gensym.wasm;\n' $file + else + sed -i "1ipackage gensym.wasm;$line" $file + fi + cp $file "$DST/$file" + rm $file done + +rm *.tokens *.interp diff --git a/src/main/scala/wasm/MiniWasm.scala b/src/main/scala/wasm/MiniWasm.scala index bedfcffb..b91c5f35 100644 --- a/src/main/scala/wasm/MiniWasm.scala +++ b/src/main/scala/wasm/MiniWasm.scala @@ -14,6 +14,7 @@ case class ModuleInstance( funcs: HashMap[Int, WIR], memory: List[RTMemory] = List(RTMemory()), globals: List[RTGlobal] = List(), + exports: List[Export] = List() ) object Primtives { @@ -202,7 +203,12 @@ object Evaluator { frame.locals(i) = value eval(rest, stack, frame, kont, trail, ret) case GlobalGet(i) => - eval(rest, frame.module.globals(i).value :: stack, frame, kont, trail, ret) + eval(rest, + frame.module.globals(i).value :: stack, + frame, + kont, + trail, + ret) case GlobalSet(i) => val value :: newStack = stack frame.module.globals(i).ty match { @@ -213,14 +219,21 @@ object Evaluator { } eval(rest, newStack, frame, kont, trail, ret) case MemorySize => - eval(rest, I32V(frame.module.memory.head.size) :: stack, frame, kont, trail, ret) + eval(rest, + I32V(frame.module.memory.head.size) :: stack, + frame, + kont, + trail, + ret) case MemoryGrow => val I32V(delta) :: newStack = stack val mem = frame.module.memory.head val oldSize = mem.size mem.grow(delta) match { - case Some(e) => eval(rest, I32V(-1) :: newStack, frame, kont, trail, ret) - case _ => eval(rest, I32V(oldSize) :: newStack, frame, kont, trail, ret) + case Some(e) => + eval(rest, I32V(-1) :: newStack, frame, kont, trail, ret) + case _ => + eval(rest, I32V(oldSize) :: newStack, frame, kont, trail, ret) } case MemoryFill => val I32V(value) :: I32V(offset) :: I32V(size) :: newStack = stack @@ -264,25 +277,41 @@ object Evaluator { case Unreachable => throw Trap() case Block(ty, inner) => val k: Cont[Ans] = (retStack) => - eval(rest, retStack.take(ty.toList.size) ++ stack, frame, kont, trail, ret) + eval(rest, + retStack.take(ty.toList.size) ++ stack, + frame, + kont, + trail, + ret) // TODO: block can take inputs too - eval(inner, List(), frame, k, k :: trail, ret+1) + eval(inner, List(), frame, k, k :: trail, ret + 1) 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 restK: Cont[Ans] = (retStack) => eval(rest, retStack.take(ty.toList.size) ++ stack, frame, kont, trail, ret) + val restK: Cont[Ans] = (retStack) => + eval(rest, + retStack.take(ty.toList.size) ++ stack, + frame, + kont, + trail, + ret) def loop(stack: List[Value]): Ans = { val k: Cont[Ans] = (retStack) => loop(retStack.take(ty.toList.size)) - eval(inner, stack, frame, restK, k :: trail, ret+1) + eval(inner, stack, frame, restK, k :: trail, ret + 1) } loop(List()) case If(ty, thn, els) => val I32V(cond) :: newStack = stack val inner = if (cond != 0) thn else els val k: Cont[Ans] = (retStack) => - eval(rest, retStack.take(ty.toList.size) ++ newStack, frame, kont, trail, ret) - eval(inner, List(), frame, k, k :: trail, ret+1) + eval(rest, + retStack.take(ty.toList.size) ++ newStack, + frame, + kont, + trail, + ret) + eval(inner, List(), frame, k, k :: trail, ret + 1) case Br(label) => trail(label)(stack) case BrIf(label) => @@ -301,7 +330,12 @@ object Evaluator { val frameLocals = args ++ locals.map(_ => I32V(0)) // GW: always I32? or depending on their types? val newFrame = Frame(frame.module, ArrayBuffer(frameLocals: _*)) val newK: Cont[Ans] = (retStack) => - eval(rest, retStack.take(ty.out.size) ++ newStack, frame, kont, trail, ret) + eval(rest, + retStack.take(ty.out.size) ++ newStack, + frame, + kont, + trail, + ret) // We push newK on the trail since function creates a new block to escape // (more or less like `return`) eval(body, List(), newFrame, newK, List(newK), 0) @@ -322,23 +356,36 @@ object Evaluator { // 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](module: Module, halt: Cont[Ans], main: Option[String] = None): Ans = { + def evalTop[Ans](module: Module, + halt: Cont[Ans], + main: Option[String] = None): Ans = { val instrs = main match { - case Some(_) => module.definitions.flatMap({ - case FuncDef(`main`, FuncBodyDef(_, _, _, body)) => + case Some(func_name) => + module.definitions.flatMap({ + case Export(`func_name`, ExportFunc(fid)) => println(s"Entering function $main") - body + module.funcEnv(fid) match { + case FuncDef(_, FuncBodyDef(_, _, _, body)) => body + case _ => + throw new Exception("Entry function has no concrete body") + } case _ => List() }) - case None => module.definitions.flatMap({ - case Start(id) => module.funcEnv(id) match { - case FuncDef(_, FuncBodyDef(_, _, _, body)) => + case None => + module.definitions.flatMap({ + case Start(id) => println(s"Entering unnamed function $id") - body - case _ => throw new Exception("Start function has no concrete definition") - } - case _ => List() - }) + module.funcEnv(id) match { + case FuncDef(_, FuncBodyDef(_, _, _, body)) => body + case _ => + throw new Exception("Entry function has no concrete body") + } + case _ => List() + }) + } + + if (instrs.isEmpty) { + println("Warning: nothing is executed") } val types = List() @@ -354,7 +401,7 @@ object Evaluator { (e.head) match { case Const(c) => RTGlobal(ty, c) // Q: What is the default behavior if case in non-exhaustive - case _ => ??? + case _ => ??? } }) .toList @@ -369,7 +416,12 @@ object Evaluator { val moduleInst = ModuleInstance(types, module.funcEnv, memory, globals) - Evaluator.eval(instrs, List(), Frame(moduleInst, ArrayBuffer(I32V(0))), halt, List(halt), 0) + Evaluator.eval(instrs, + List(), + Frame(moduleInst, ArrayBuffer(I32V(0))), + halt, + List(halt), + 0) } def evalTop(m: Module): Unit = evalTop(m, stack => ()) diff --git a/src/main/scala/wasm/Parser.scala b/src/main/scala/wasm/Parser.scala index 25fef381..789107ab 100644 --- a/src/main/scala/wasm/Parser.scala +++ b/src/main/scala/wasm/Parser.scala @@ -567,16 +567,25 @@ class GSWasmVisitor extends WatParserBaseVisitor[WIR] { Global(name, field) } - override def visitExport(ctx: ExportContext): WIR = { - val name = ctx.name + override def visitExport_(ctx: Export_Context): WIR = { + val name: String = ctx.name.getText.substring(1).dropRight(1) val desc = visitExportDesc(ctx.exportDesc).asInstanceOf[ExportDesc] Export(name, desc) } override def visitExportDesc(ctx: ExportDescContext): WIR = { - val fid = ctx.idx + val id = if (ctx.idx.VAR() != null) { + println(s"Warning: we don't support labeling yet") + throw new RuntimeException("Unsupported") + } else { + getVar(ctx.idx()).toInt + } + if (ctx.FUNC != null) ExportFunc(id) + else if (ctx.TABLE != null) ExportTable(id) + else if (ctx.MEMORY != null) ExportMemory(id) + else if (ctx.GLOBAL != null) ExportGlobal(id) + else error - ExportFunc(getVar(fid).toInt) } } diff --git a/src/test/scala/genwasym/TestEval.scala b/src/test/scala/genwasym/TestEval.scala index 55125294..0face1ff 100644 --- a/src/test/scala/genwasym/TestEval.scala +++ b/src/test/scala/genwasym/TestEval.scala @@ -32,16 +32,16 @@ class TestEval extends FunSuite { // TODO: the power test can be used to test the stack // For now: 2^10 works, 2^100 results in 0 (TODO: why?), // and 2^1000 results in a stack overflow - // test("ack") { testFile("./benchmarks/wasm/ack.wat", Some("$real_main"), Some(7)) } - // test("power") { testFile("./benchmarks/wasm/pow.wat", Some("$real_main"), Some(1024)) } - // test("start") { testFile("./benchmarks/wasm/start.wat") } - // test("fact") { testFile("./benchmarks/wasm/fact.wat", None, Some(120)) } - // test("loop") { testFile("./benchmarks/wasm/loop.wat", None, Some(10)) } - // test("even-odd") { testFile("./benchmarks/wasm/even_odd.wat", None, Some(1)) } - // test("load") { testFile("./benchmarks/wasm/load.wat", None, Some(1)) } - // test("btree") { testFile("./benchmarks/wasm/btree/2o1u-unlabeled.wat") } - // test("fib") { testFile("./benchmarks/wasm/fib.wat", None, Some(144)) } - // test("tribonacci") { testFile("./benchmarks/wasm/tribonacci.wat", None, Some(504)) } + test("ack") { testFile("./benchmarks/wasm/ack.wat", Some("real_main"), Some(7)) } + test("power") { testFile("./benchmarks/wasm/pow.wat", Some("real_main"), Some(1024)) } + test("start") { testFile("./benchmarks/wasm/start.wat") } + test("fact") { testFile("./benchmarks/wasm/fact.wat", None, Some(120)) } + test("loop") { testFile("./benchmarks/wasm/loop.wat", None, Some(10)) } + test("even-odd") { testFile("./benchmarks/wasm/even_odd.wat", None, Some(1)) } + test("load") { testFile("./benchmarks/wasm/load.wat", None, Some(1)) } + test("btree") { testFile("./benchmarks/wasm/btree/2o1u-unlabeled.wat") } + test("fib") { testFile("./benchmarks/wasm/fib.wat", None, Some(144)) } + test("tribonacci") { testFile("./benchmarks/wasm/tribonacci.wat", None, Some(504)) } test("return") { intercept[gensym.wasm.miniwasm.Trap] {