Skip to content

Commit

Permalink
Basic (module bin ...) support (#62)
Browse files Browse the repository at this point in the history
* basic .bin.wast working

---------

Co-authored-by: butterunderflow <[email protected]>
Co-authored-by: Guannan Wei <[email protected]>
  • Loading branch information
3 people authored Nov 4, 2024
1 parent c286f04 commit 2eb36f0
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 14 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/scala.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ jobs:
make
sudo make install
sudo ldconfig
- name: Install wasmfx-tools
run: |
cd third-party/wasmfx-tools
cargo build --release
- name: Generate models
run: sbt 'runMain gensym.GenerateExternal'
- name: Run tests
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@
path = third-party/lms-clean
url = https://github.com/TiarkRompf/lms-clean
ignore = dirty
[submodule "third-party/wasmfx-tools"]
path = third-party/wasmfx-tools
url = [email protected]:wasmfx/wasmfx-tools.git
7 changes: 7 additions & 0 deletions benchmarks/wasm/script/script_basic.bin.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
(module binary
"\00\61\73\6d\01\00\00\00\01\85\80\80\80\00\01\60"
"\00\01\7f\03\82\80\80\80\00\01\00\07\87\80\80\80"
"\00\01\03\6f\6e\65\00\00\0a\8a\80\80\80\00\01\84"
"\80\80\80\00\00\41\01\0b"
)
(assert_return (invoke "one") (i32.const 0x1))
2 changes: 2 additions & 0 deletions src/main/scala/wasm/MiniWasm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ object Primtives {
(lhs, rhs) match {
case (I32V(v1), I32V(v2)) => I32V(v1 + v2)
case (I64V(v1), I64V(v2)) => I64V(v1 + v2)
case (F32V(v1), F32V(v2)) => F32V(v1 + v2)
case (F64V(v1), F64V(v2)) => F64V(v1 + v2)
case _ => throw new Exception("Invalid types")
}
case Mul(_) =>
Expand Down
126 changes: 113 additions & 13 deletions src/main/scala/wasm/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package gensym.wasm.parser
import gensym.wasm.ast._
import gensym.wasm.source._

import scala.util.Try
import scala.util.parsing.combinator._
import scala.util.parsing.input.Positional
import scala.util.matching.Regex
Expand All @@ -14,6 +15,9 @@ import scala.collection.JavaConverters._
import collection.mutable.{HashMap, ListBuffer}
import gensym.wasm._

import java.io.OutputStream


import scala.collection.mutable

class GSWasmVisitor extends WatParserBaseVisitor[WIR] {
Expand Down Expand Up @@ -188,23 +192,78 @@ class GSWasmVisitor extends WatParserBaseVisitor[WIR] {
???
}

// TODO: This doesn't seems quite correct
def parseHexFloat(text: String): Float = {
if (text.startsWith("0x") || text.startsWith("-0x") || text.startsWith("+0x")) {
// Remove optional sign and "0x" prefix
val cleanText = text.replaceFirst("^[+-]?0x", "")
// why removing the seemling irrelevant following two lines will effect
// the value being parsed?
val value: Float = BigDecimal(text).floatValue
print(f"cleanText = $cleanText, value = $value\n")

val Array(mantissa, exponent) = cleanText.split("p", 2)

// Convert mantissa and exponent
val mantissaValue = java.lang.Float.intBitsToFloat(java.lang.Integer.parseUnsignedInt(mantissa.replace(".", ""), 16))
val exponentValue = Math.pow(2, exponent.toInt).toFloat
// print(s"mantissaValue = $mantissaValue, exponentValue = $exponentValue\n")
mantissaValue * exponentValue
} else {
text.toFloat // Fall back to regular decimal parsing
}
}


def visitLiteralWithType(ctx: LiteralContext, ty: NumType): Num = {
if (ctx.NAT != null) {
ty.kind match {
case I32Type => I32V(ctx.NAT.getText.toInt)
case I64Type => I64V(ctx.NAT.getText.toLong)
case I32Type => {
if (ctx.NAT.getText.startsWith("0x")) {
I32V(Integer.parseInt(ctx.NAT.getText.substring(2), 16))
} else {
I32V(ctx.NAT.getText.toInt)
}
}
case I64Type => {
if (ctx.NAT.getText.startsWith("0x")) {
I64V(java.lang.Long.parseLong(ctx.NAT.getText.substring(2), 16))
} else {
I64V(ctx.NAT.getText.toLong)
}
}
}
} else if (ctx.INT != null) {
ty.kind match {
case I32Type => I32V(ctx.INT.getText.toInt)
case I64Type => I64V(ctx.INT.getText.toLong)
case I32Type => {
if (ctx.INT.getText.startsWith("0x")) {
I32V(Integer.parseInt(ctx.INT.getText.substring(2), 16))
} else {
I32V(ctx.INT.getText.toInt)
}
}
case I64Type => {
if (ctx.INT.getText.startsWith("0x")) {
I64V(java.lang.Long.parseLong(ctx.INT.getText.substring(2), 16))
} else {
I64V(ctx.INT.getText.toLong)
}
}
}
// TODO: parsing support for hex representation for f32/f64 not quite there yet
} else if (ctx.FLOAT != null) {
ty.kind match {
case F32Type => F32V(ctx.FLOAT.getText.toFloat)
case F64Type => F64V(ctx.FLOAT.getText.toDouble)
case F32Type =>
val parsedValue = Try(parseHexFloat(ctx.FLOAT.getText).toFloat).getOrElse(ctx.FLOAT.getText.toFloat)
F32V(parsedValue)

case F64Type =>
// TODO: not processed at all
val parsedValue = ctx.FLOAT.getText.toDouble
F64V(parsedValue)
}
} else error
}
else error
}

override def visitPlainInstr(ctx: PlainInstrContext): Instr = {
Expand Down Expand Up @@ -635,10 +694,45 @@ class GSWasmVisitor extends WatParserBaseVisitor[WIR] {
else error
}

override def visitScriptModule(ctx: ScriptModuleContext): Module = {
override def visitScriptModule(ctx: ScriptModuleContext): Module = {
if (ctx.module_ != null) {
visitModule_(ctx.module_).asInstanceOf[Module]
} else {
}
else if (ctx.BIN != null) {

val bin = ctx.STRING_
val hexString = bin.asScala.toList.map(_.getText.substring(1).dropRight(1)).mkString

val byteArray: Array[Byte] = hexStringToByteArray(hexString)

// just for fact checking
// val filePath = "temp.bin"
// Files.write(Paths.get(filePath), byteArray)

// use `wasmfx-tools` to convert the binary file to a text file
val processBuilder = new ProcessBuilder("./third-party/wasmfx-tools/target/release/wasm-tools", "print")

val process = processBuilder.start()
val outputStream: OutputStream = process.getOutputStream
try {
outputStream.write(byteArray)
outputStream.flush()
} finally {
outputStream.close() // Close the stream to signal end of input
}

val output = scala.io.Source.fromInputStream(process.getInputStream).mkString
val errorOutput = scala.io.Source.fromInputStream(process.getErrorStream).mkString
val exitCode = process.waitFor()

// println(s"Exit code: $exitCode")
// println(s"Output:\n$output")
// println(s"Error Output:\n$errorOutput")

val module = Parser.parse(output)
module
}
else {
throw new RuntimeException("Unsupported")
}
}
Expand Down Expand Up @@ -688,14 +782,20 @@ class GSWasmVisitor extends WatParserBaseVisitor[WIR] {
Script(cmds.toList)
}

override def visitTag(ctx: TagContext): WIR = {
val name = getVar(ctx.bindVar)
val ty = visitFuncType(ctx.funcType)
Tag(name, ty)
// Function to convert a hex string representation to an Array[Byte]
def hexStringToByteArray(hex: String): Array[Byte] = {
// Split the input string by '\' and filter out empty strings
val byteStrings = hex.split("\\\\").filter(_.nonEmpty)

byteStrings.map { byteStr =>
// Parse the hex value to a byte
Integer.parseInt(byteStr, 16).toByte
}
}

}


object Parser {
private def makeWatVisitor(input: String) = {
val charStream = new ANTLRInputStream(input)
Expand Down
6 changes: 5 additions & 1 deletion src/test/scala/genwasym/TestScriptRun.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import gensym.wasm.miniwasmscript.ScriptRunner

import org.scalatest.FunSuite


class TestScriptRun extends FunSuite {
def testFile(filename: String): Unit = {
val script = Parser.parseScriptFile(filename).get
Expand All @@ -16,4 +15,9 @@ class TestScriptRun extends FunSuite {
test("simple script") {
testFile("./benchmarks/wasm/script/script_basic.wast")
}

test("simple bin script") {
testFile("./benchmarks/wasm/script/script_basic.bin.wast")
}

}
17 changes: 17 additions & 0 deletions src/test/scala/genwasym/TestSyntax.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package gensym.wasm

import gensym.wasm.parser.Parser
import org.scalatest.FunSuite

class TestSyntax extends FunSuite {
def testFile(filename: String) = {
val script = Parser.parseScriptFile(filename)
println(s"script = $script")
assert(script != None, "this syntax is not defined in antlr grammar")
}

test("basic script") {
testFile("./benchmarks/wasm/script/script_basic.wabt")
}
}

1 change: 1 addition & 0 deletions third-party/wasmfx-tools
Submodule wasmfx-tools added at c9218c

0 comments on commit 2eb36f0

Please sign in to comment.