Skip to content

Commit b2afc58

Browse files
butterunderflowGuannan Weiahuoguo
authored
Try to support some wasm script grammar, for testing (#61)
* try support some webassembly script grammar * a tiny example of wast * fix test * add wast test to ci * cosmetic stuff * remove TestSyntax.scala --------- Co-authored-by: Guannan Wei <wei220@purdue.edu> Co-authored-by: ahuoguo <52595524+ahuoguo@users.noreply.github.com>
1 parent 28b6ad0 commit b2afc58

File tree

7 files changed

+204
-29
lines changed

7 files changed

+204
-29
lines changed

.github/workflows/scala.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,4 @@ jobs:
7171
sbt 'testOnly gensym.TestImpCPSGS_Z3'
7272
sbt 'testOnly gensym.TestLibrary'
7373
sbt 'testOnly gensym.wasm.TestEval'
74+
sbt 'testOnly gensym.wasm.TestScriptRun'
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
(module
2+
(func $one (result i32)
3+
i32.const 1)
4+
(export "one" (func 0))
5+
)
6+
7+
(assert_return (invoke "one") (i32.const 1))
8+

src/main/scala/wasm/AST.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,3 +292,17 @@ case class ExportFunc(i: Int) extends ExportDesc
292292
case class ExportTable(i: Int) extends ExportDesc
293293
case class ExportMemory(i: Int) extends ExportDesc
294294
case class ExportGlobal(i: Int) extends ExportDesc
295+
296+
case class Script(cmds: List[Cmd]) extends WIR
297+
abstract class Cmd extends WIR
298+
// TODO: can we turn abstract class sealed?
299+
case class CmdModule(module: Module) extends Cmd
300+
301+
abstract class Action extends WIR
302+
case class Invoke(instName: Option[String], name: String, args: List[Value]) extends Action
303+
304+
abstract class Assertion extends Cmd
305+
case class AssertReturn(action: Action, expect: List[Num] /* TODO: support multiple expect result type*/)
306+
extends Assertion
307+
case class AssertTrap(action: Action, message: String) extends Assertion
308+

src/main/scala/wasm/MiniWasm.scala

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,44 @@ case class ModuleInstance(
1818
exports: List[Export] = List()
1919
)
2020

21+
object ModuleInstance {
22+
def apply(module: Module): ModuleInstance = {
23+
val types = List()
24+
val funcs = module.definitions
25+
.collect({
26+
case FuncDef(_, fndef @ FuncBodyDef(_, _, _, _)) => fndef
27+
})
28+
.toList
29+
30+
val globals = module.definitions
31+
.collect({
32+
case Global(_, GlobalValue(ty, e)) =>
33+
(e.head) match {
34+
case Const(c) => RTGlobal(ty, c)
35+
// Q: What is the default behavior if case in non-exhaustive
36+
case _ => ???
37+
}
38+
})
39+
.toList
40+
41+
// TODO: correct the behavior for memory
42+
val memory = module.definitions
43+
.collect({
44+
case Memory(id, MemoryType(min, max_opt)) =>
45+
RTMemory(min, max_opt)
46+
})
47+
.toList
48+
49+
val exports = module.definitions
50+
.collect({
51+
case e @ Export(_, ExportFunc(_)) => e
52+
})
53+
.toList
54+
55+
ModuleInstance(types, module.funcEnv, memory, globals, exports)
56+
}
57+
}
58+
2159
object Primtives {
2260
def evalBinOp(op: BinOp, lhs: Value, rhs: Value): Value = op match {
2361
case Add(_) =>
@@ -412,33 +450,7 @@ object Evaluator {
412450

413451
if (instrs.isEmpty) println("Warning: nothing is executed")
414452

415-
val types = List()
416-
val funcs = module.definitions
417-
.collect({
418-
case FuncDef(_, fndef @ FuncBodyDef(_, _, _, _)) => fndef
419-
})
420-
.toList
421-
422-
val globals = module.definitions
423-
.collect({
424-
case Global(_, GlobalValue(ty, e)) =>
425-
(e.head) match {
426-
case Const(c) => RTGlobal(ty, c)
427-
// Q: What is the default behavior if case in non-exhaustive
428-
case _ => ???
429-
}
430-
})
431-
.toList
432-
433-
// TODO: correct the behavior for memory
434-
val memory = module.definitions
435-
.collect({
436-
case Memory(id, MemoryType(min, max_opt)) =>
437-
RTMemory(min, max_opt)
438-
})
439-
.toList
440-
441-
val moduleInst = ModuleInstance(types, module.funcEnv, memory, globals)
453+
val moduleInst = ModuleInstance(module)
442454

443455
Evaluator.eval(instrs, List(), Frame(moduleInst, ArrayBuffer(I32V(0))), halt, List(halt))
444456
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package gensym.wasm.miniwasmscript
2+
3+
import gensym.wasm.miniwasm._
4+
import gensym.wasm.ast._
5+
import scala.collection.mutable.{ListBuffer, Map, ArrayBuffer}
6+
7+
sealed class ScriptRunner {
8+
val instances: ListBuffer[ModuleInstance] = ListBuffer()
9+
val instanceMap: Map[String, ModuleInstance] = Map()
10+
11+
def getInstance(instName: Option[String]): ModuleInstance = {
12+
instName match {
13+
case Some(name) => instanceMap(name)
14+
case None => instances.head
15+
}
16+
}
17+
18+
def assertReturn(action: Action, expect: List[Value]): Unit = {
19+
action match {
20+
case Invoke(instName, name, args) =>
21+
val module = getInstance(instName)
22+
val func = module.exports.collectFirst({
23+
case Export(`name`, ExportFunc(index)) =>
24+
module.funcs(index)
25+
case _ => throw new RuntimeException("Not Supported")
26+
}).get
27+
val instrs = func match {
28+
case FuncDef(_, FuncBodyDef(ty, _, locals, body)) => body
29+
}
30+
val k = (retStack: List[Value]) => retStack
31+
val actual = Evaluator.eval(instrs, List(), Frame(module, ArrayBuffer(args: _*)), k, List(k))
32+
assert(actual == expect)
33+
}
34+
}
35+
36+
def runCmd(cmd: Cmd): Unit = {
37+
cmd match {
38+
case CmdModule(module) => instances += ModuleInstance(module)
39+
case AssertReturn(action, expect) => assertReturn(action, expect)
40+
case AssertTrap(action, message) => ???
41+
}
42+
}
43+
44+
def run(script: Script): Unit = {
45+
for (cmd <- script.cmds) {
46+
runCmd(cmd)
47+
}
48+
}
49+
}

src/main/scala/wasm/Parser.scala

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,59 @@ class GSWasmVisitor extends WatParserBaseVisitor[WIR] {
633633
else if (ctx.MEMORY != null) ExportMemory(id)
634634
else if (ctx.GLOBAL != null) ExportGlobal(id)
635635
else error
636+
}
637+
638+
override def visitScriptModule(ctx: ScriptModuleContext): Module = {
639+
if (ctx.module_ != null) {
640+
visitModule_(ctx.module_).asInstanceOf[Module]
641+
} else {
642+
throw new RuntimeException("Unsupported")
643+
}
644+
}
645+
646+
override def visitAction_(ctx: Action_Context): Action = {
647+
if (ctx.INVOKE != null) {
648+
val instName = if (ctx.VAR != null) Some(ctx.VAR().getText) else None
649+
var name = ctx.name.getText.substring(1).dropRight(1)
650+
var args = for (constCtx <- ctx.constList.wconst.asScala) yield {
651+
val Array(ty, _) = constCtx.CONST.getText.split("\\.")
652+
visitLiteralWithType(constCtx.literal, toNumType(ty))
653+
}
654+
Invoke(instName, name, args.toList)
655+
} else {
656+
throw new RuntimeException("Unsupported")
657+
}
658+
}
659+
660+
override def visitAssertion(ctx: AssertionContext): Assertion = {
661+
if (ctx.ASSERT_RETURN != null) {
662+
val action = visitAction_(ctx.action_)
663+
val expect = for (constCtx <- ctx.constList.wconst.asScala) yield {
664+
val Array(ty, _) = constCtx.CONST.getText.split("\\.")
665+
visitLiteralWithType(constCtx.literal, toNumType(ty))
666+
}
667+
println(s"expect = $expect")
668+
AssertReturn(action, expect.toList)
669+
} else {
670+
throw new RuntimeException("Unsupported")
671+
}
672+
}
636673

674+
override def visitCmd(ctx: CmdContext): Cmd = {
675+
if (ctx.assertion != null) {
676+
visitAssertion(ctx.assertion)
677+
} else if (ctx.scriptModule != null) {
678+
CmdModule(visitScriptModule(ctx.scriptModule))
679+
} else {
680+
throw new RuntimeException("Unsupported")
681+
}
682+
}
683+
684+
override def visitScript(ctx: ScriptContext): WIR = {
685+
val cmds = for (cmd <- ctx.cmd.asScala) yield {
686+
visitCmd(cmd)
687+
}
688+
Script(cmds.toList)
637689
}
638690

639691
override def visitTag(ctx: TagContext): WIR = {
@@ -645,15 +697,35 @@ class GSWasmVisitor extends WatParserBaseVisitor[WIR] {
645697
}
646698

647699
object Parser {
648-
def parse(input: String): Module = {
700+
private def makeWatVisitor(input: String) = {
649701
val charStream = new ANTLRInputStream(input)
650702
val lexer = new WatLexer(charStream)
651703
val tokens = new CommonTokenStream(lexer)
652-
val parser = new WatParser(tokens)
704+
new WatParser(tokens)
705+
}
706+
707+
def parse(input: String): Module = {
708+
val parser = makeWatVisitor(input)
653709
val visitor = new GSWasmVisitor()
654710
val res: Module = visitor.visit(parser.module).asInstanceOf[Module]
655711
res
656712
}
657713

658714
def parseFile(filepath: String): Module = parse(scala.io.Source.fromFile(filepath).mkString)
715+
716+
// parse extended webassembly script language
717+
def parseScript(input: String): Option[Script] = {
718+
val parser = makeWatVisitor(input)
719+
val visitor = new GSWasmVisitor()
720+
val tree = parser.script()
721+
val errorNumer = parser.getNumberOfSyntaxErrors()
722+
if (errorNumer != 0) None
723+
else {
724+
val res: Script = visitor.visitScript(tree).asInstanceOf[Script]
725+
Some(res)
726+
}
727+
}
728+
729+
def parseScriptFile(filepath: String): Option[Script] =
730+
parseScript(scala.io.Source.fromFile(filepath).mkString)
659731
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package gensym.wasm
2+
3+
import gensym.wasm.parser.Parser
4+
import gensym.wasm.miniwasmscript.ScriptRunner
5+
6+
import org.scalatest.FunSuite
7+
8+
9+
class TestScriptRun extends FunSuite {
10+
def testFile(filename: String): Unit = {
11+
val script = Parser.parseScriptFile(filename).get
12+
val runner = new ScriptRunner()
13+
runner.run(script)
14+
}
15+
16+
test("simple script") {
17+
testFile("./benchmarks/wasm/script/script_basic.wast")
18+
}
19+
}

0 commit comments

Comments
 (0)