wagon, a WebAssembly-based Go interpreter, for Go.

wagon

Build Status codecov GoDoc

wagon is a WebAssembly-based interpreter in Go, for Go.

As of 2020/05/11 Wagon is in read-only mode, and looking for a maintainer. You may want to look at https://github.com/mathetake/gasm instead.


Purpose

wagon aims to provide tools (executables+libraries) to:

  • decode wasm binary files
  • load and execute wasm modules' bytecode.

wagon doesn't concern itself with the production of the wasm binary files; these files should be produced with another tool (such as wabt or binaryen.) wagon may provide a utility to produce wasm files from wast or wat files (and vice versa.)

The primary goal of wagon is to provide the building blocks to be able to build an interpreter for Go code, that could be embedded in Jupyter or any Go program.

Contributing

See the CONTRIBUTING guide for pointers on how to contribute to go-interpreter and wagon.

Owner
Go Interpreter
Provide tools and software to build an interpreter for Go
Go Interpreter
Comments
  • wasm: when function names are present, include them

    wasm: when function names are present, include them

    LLVM generates (optional) function names, including them in a custom section on the end of the .wasm file.

    It's useful to have them handy when working with decoded wasm data, so adding them directly to the Function struct seems like a good spot.


    This PR originally included code by @mastersingh24, from the Life project. The below comment was regarding allowing that piece of code to be incorporated here with BSD licencing.

    However, it turns out to be unneeded, as Wagon already has the ability to decode Custom Name sections, so it was simpler to reuse that.

  • Return an error on Unreachable instead of panicking

    Return an error on Unreachable instead of panicking

    So far, when the vm finds an unreachable instruction, it panics. This is a problem for whoever wants to embed wagon inside their binaries, which is exactly what the Ethereum eWASM team considers at the moment.

    As a result, this PR adds an error return to each opcode function. Those opcode errors are then returned to the client code so that the caller can decide on the best course of action.


    This change is Reviewable

  • WIP: add a wast format scanner+parser

    WIP: add a wast format scanner+parser

    Open to reviews and discussion. Expect bugs and unexpected behavior. This branch is not ready for merge (yet !).

    As discussed in #34 with @sbinet here is a first take at a file scanner for .wast files. The implementation lacks proper unit tests and comments but I am open to suggestions and reviews.

    The end goal of this feature is to be able to input .wast files to wagon and directly run wasm code. In a more usual context, wast files are mostly used for unit testing implementations of a wasm virtual machine and standard compliance.

    This means that we could benchmark wagon against the wasm test suite.

    I will continue working on this in my spare time. Again, feel free to comment and contribute. All help is welcome.

    EDIT: to test the actual scanner, simply run go test in the /wast directory.


    This change is Reviewable

  • Pass the VM pointer to host go functions

    Pass the VM pointer to host go functions

    The previous PR #58 was enabling go functions, but without the VM context being passed, they can only do so much. This PR enforces that host Go functions are passed a *VM parameter as their first argument.

    Another idea would be to check for pointers in the list of arguments, and pass a *VM each time that an argument is being encountered. It has some advantages, like streamlining the code and letting the Go type system handle the panics for us. This being said, this leaves too much room for abuse since users could write confusing functions that take that pointer in the middle of their arguments list, thus disturbing the order to the WASM call.


    This change is Reviewable

  • Part 1/2 #101 - implement partial compilation to native code

    Part 1/2 #101 - implement partial compilation to native code

    This PR lays the boilerplate for supporting AOT & JIT compilation.

    • Use one of the unassigned opcodes to signal to the interpreter to execute a native code section. Modify the disassembler to throw an error if this opcode is used, so any collision years down the line is detected & can be trivially fixed.

    • Implement interfaces to abstract different stages, specifically:

      1. Scanning opcodes to determine candidates for native compilation + gathering statistics (sequenceScanner)
      2. Allocating executable pages, as such an operation is OS dependent (pageAllocator)
      3. Actually compiling the chosen sequence into instructions (instructionBuilder)
    • When a sequence of opcodes is rewritten, the beginning is set to an internal opcode that signals the interpreter should run a section of native code at a specific []asmBlock index. Any remaining opcodes are set to ops.Unreachable - as we dont plan to emit asm where there are inbound branches in the middle (yet), this should crash out in the event of any bugs that jump into the recompiled section.

    • Tests for integration between the different interfaces & structures in VM / compiledFunction.

  • Test Suite Failing on Go1.12.7 windows/amd64

    Test Suite Failing on Go1.12.7 windows/amd64

    The current Master is failing on Windows (but wasm-run builds/runs fine). it completes in my WSL (go version go1.12.7 linux/amd64)

    Haven't had a chance to look into it, looks like it is related to the AOT code

    C:\Users\sampa\Desktop\wagon>go version
    go version go1.12.7 windows/amd64
    
    C:\Users\sampa\Desktop\wagon>go test ./...
    ok      github.com/go-interpreter/wagon (cached)
    ok      github.com/go-interpreter/wagon/cmd/wasm-dump   (cached)
    ok      github.com/go-interpreter/wagon/cmd/wasm-run    (cached)
    ok      github.com/go-interpreter/wagon/disasm  (cached)
    Exception 0xc0000094 0x0 0x0 0x3a70051
    PC=0x3a70051
    
    runtime: unknown pc 0x3a70051
    stack: frame={sp:0xc000df1720, fp:0x0} stack=[0xc000dee000,0xc000df2000)
    000000c000df1620:  0000000000000001  0000000000030002
    000000c000df1630:  0000000000c28b00  0000000000000002
    000000c000df1640:  0000000000000002  0000000000000000
    000000c000df1650:  000000c000df16f0  000000000040c0c3 <runtime.mallocgc+787>
    000000c000df1660:  000000c0003a4040  0000002108421084
    000000c000df1670:  0000000000000037  0000000000000008
    000000c000df1680:  0000000000030000  0000000000002000
    000000c000df1690:  0000000000000010  0000000000000010
    000000c000df16a0:  000000c0003a4040  0000000000000008
    000000c000df16b0:  000000000040c0c3 <runtime.mallocgc+787>  000000c000008e00
    000000c000df16c0:  0000000002030003  0000000002030003
    000000c000df16d0:  0000000000030003  0000000000000020
    000000c000df16e0:  00000000007883d7  0000000000000068
    000000c000df16f0:  000000c000df1758  0000000002030003
    000000c000df1700:  0000000002030003  0000000000030003
    000000c000df1710:  000000c000df17b0  000000000040c0c3 <runtime.mallocgc+787>
    000000c000df1720: <00000000006295bc <github.com/go-interpreter/wagon/exec/internal/compile.(*asmBlock).Invoke+92>  000000c000006018
    000000c000df1730:  000000c000176900  000000c000176918
    000000c000df1740:  000000c000176978  000000c000176990
    000000c000df1750:  0000000000000020  000000c000df17a0
    000000c000df1760:  0000000000634b33 <github.com/go-interpreter/wagon/exec.(*VM).nativeCodeInvocation+131>  000000c000006018
    000000c000df1770:  000000c000176900  000000c000176918
    000000c000df1780:  000000c000176978  000000c000176990
    000000c000df1790:  0000000000464c21 <sync.(*Pool).Put+113>  000000000000000c
    000000c000df17a0:  000000c000df1908  000000000063e103 <github.com/go-interpreter/wagon/exec.(*VM).execCode+499>
    000000c000df17b0:  000000c000176900  0000000000000000
    000000c000df17c0:  000000000040c0c3 <runtime.mallocgc+787>  000000c000df1810
    000000c000df17d0:  000000000041432e <runtime.(*mspan).nextFreeIndex+302>  00000000013f5a00
    000000c000df17e0:  0000000000000010  0000000000000200
    000000c000df17f0:  0000000000000080  000000000000007f
    000000c000df1800:  000000c000df1838  000000000040bbc4 <runtime.(*mcache).nextFree+84>
    000000c000df1810:  00000000013f5a00  000000000000007f
    runtime: unknown pc 0x3a70051
    stack: frame={sp:0xc000df1720, fp:0x0} stack=[0xc000dee000,0xc000df2000)
    000000c000df1620:  0000000000000001  0000000000030002
    000000c000df1630:  0000000000c28b00  0000000000000002
    000000c000df1640:  0000000000000002  0000000000000000
    000000c000df1650:  000000c000df16f0  000000000040c0c3 <runtime.mallocgc+787>
    000000c000df1660:  000000c0003a4040  0000002108421084
    000000c000df1670:  0000000000000037  0000000000000008
    000000c000df1680:  0000000000030000  0000000000002000
    000000c000df1690:  0000000000000010  0000000000000010
    000000c000df16a0:  000000c0003a4040  0000000000000008
    000000c000df16b0:  000000000040c0c3 <runtime.mallocgc+787>  000000c000008e00
    000000c000df16c0:  0000000002030003  0000000002030003
    000000c000df16d0:  0000000000030003  0000000000000020
    000000c000df16e0:  00000000007883d7  0000000000000068
    000000c000df16f0:  000000c000df1758  0000000002030003
    000000c000df1700:  0000000002030003  0000000000030003
    000000c000df1710:  000000c000df17b0  000000000040c0c3 <runtime.mallocgc+787>
    000000c000df1720: <00000000006295bc <github.com/go-interpreter/wagon/exec/internal/compile.(*asmBlock).Invoke+92>  000000c000006018
    000000c000df1730:  000000c000176900  000000c000176918
    000000c000df1740:  000000c000176978  000000c000176990
    000000c000df1750:  0000000000000020  000000c000df17a0
    000000c000df1760:  0000000000634b33 <github.com/go-interpreter/wagon/exec.(*VM).nativeCodeInvocation+131>  000000c000006018
    000000c000df1770:  000000c000176900  000000c000176918
    000000c000df1780:  000000c000176978  000000c000176990
    000000c000df1790:  0000000000464c21 <sync.(*Pool).Put+113>  000000000000000c
    000000c000df17a0:  000000c000df1908  000000000063e103 <github.com/go-interpreter/wagon/exec.(*VM).execCode+499>
    000000c000df17b0:  000000c000176900  0000000000000000
    000000c000df17c0:  000000000040c0c3 <runtime.mallocgc+787>  000000c000df1810
    000000c000df17d0:  000000000041432e <runtime.(*mspan).nextFreeIndex+302>  00000000013f5a00
    000000c000df17e0:  0000000000000010  0000000000000200
    000000c000df17f0:  0000000000000080  000000000000007f
    000000c000df1800:  000000c000df1838  000000000040bbc4 <runtime.(*mcache).nextFree+84>
    000000c000df1810:  00000000013f5a00  000000000000007f
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 1 [chan receive]:
    testing.(*T).Run(0xc0001b6900, 0x7292db, 0x8, 0x73c988, 0x483101)
            c:/go/src/testing/testing.go:917 +0x388
    testing.runTests.func1(0xc0000d2100)
            c:/go/src/testing/testing.go:1157 +0x7f
    testing.tRunner(0xc0000d2100, 0xc000089e30)
            c:/go/src/testing/testing.go:865 +0xc7
    testing.runTests(0xc000058940, 0x990d80, 0x13, 0x13, 0x0)
            c:/go/src/testing/testing.go:1155 +0x2b0
    testing.(*M).Run(0xc0000ca100, 0x0)
            c:/go/src/testing/testing.go:1072 +0x169
    main.main()
            _testmain.go:100 +0x145
    
    goroutine 100 [chan receive]:
    testing.tRunner.func1(0xc0001b6900)
            c:/go/src/testing/testing.go:841 +0x20a
    testing.tRunner(0xc0001b6900, 0x73c988)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 89 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0001b6b00)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func2(0xc0001b6b00)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:391 +0x66
    testing.tRunner(0xc0001b6b00, 0xc0002e2980)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 90 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0001b6c00)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func1(0xc0001b6c00)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:383 +0x66
    testing.tRunner(0xc0001b6c00, 0xc0002e29c0)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 91 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0001b6d00)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func2(0xc0001b6d00)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:391 +0x66
    testing.tRunner(0xc0001b6d00, 0xc0002e2a00)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 92 [chan send]:
    testing.tRunner.func1(0xc0001b6e00)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0001b6e00, 0xc0002e2a40)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 93 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0001b6f00)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func2(0xc0001b6f00)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:391 +0x66
    testing.tRunner(0xc0001b6f00, 0xc0002e2ac0)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 94 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0001b7000)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func1(0xc0001b7000)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:383 +0x66
    testing.tRunner(0xc0001b7000, 0xc0002e2b00)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 95 [chan send]:
    testing.tRunner.func1(0xc0001b7100)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0001b7100, 0xc0002e2b40)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 96 [running]:
            goroutine running on other thread; stack unavailable
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 97 [running]:
            goroutine running on other thread; stack unavailable
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 146 [running]:
            goroutine running on other thread; stack unavailable
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 147 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0001b7500)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func2(0xc0001b7500)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:391 +0x66
    testing.tRunner(0xc0001b7500, 0xc0002e2cc0)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 148 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0001b7600)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func1(0xc0001b7600)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:383 +0x66
    testing.tRunner(0xc0001b7600, 0xc0002e2d40)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 149 [chan send]:
    testing.tRunner.func1(0xc0001b7700)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0001b7700, 0xc0002e2dc0)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 150 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0001b7800)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func1(0xc0001b7800)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:383 +0x66
    testing.tRunner(0xc0001b7800, 0xc0002e2e40)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 151 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0001b7900)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func2(0xc0001b7900)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:391 +0x66
    testing.tRunner(0xc0001b7900, 0xc0002e2ec0)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 152 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0001b7a00)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func1(0xc0001b7a00)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:383 +0x66
    testing.tRunner(0xc0001b7a00, 0xc0002e2f40)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 153 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0001b7c00)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func2(0xc0001b7c00)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:391 +0x66
    testing.tRunner(0xc0001b7c00, 0xc0002e2fc0)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 154 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0001b7d00)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func1(0xc0001b7d00)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:383 +0x66
    testing.tRunner(0xc0001b7d00, 0xc0002e3040)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 155 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0001b7e00)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func2(0xc0001b7e00)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:391 +0x66
    testing.tRunner(0xc0001b7e00, 0xc0002e30c0)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 156 [chan send]:
    testing.tRunner.func1(0xc0001b7f00)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0001b7f00, 0xc0002e3140)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 157 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0000d2200)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func2(0xc0000d2200)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:391 +0x66
    testing.tRunner(0xc0000d2200, 0xc0002e31c0)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 158 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0000d2300)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func1(0xc0000d2300)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:383 +0x66
    testing.tRunner(0xc0000d2300, 0xc0002e3240)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 159 [chan send]:
    testing.tRunner.func1(0xc0000d2400)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000d2400, 0xc0002e32c0)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 160 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0000d2500)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func1(0xc0000d2500)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:383 +0x66
    testing.tRunner(0xc0000d2500, 0xc0002e3340)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 161 [chan receive]:
    testing.(*testContext).waitParallel(0xc0000af740)
            c:/go/src/testing/testing.go:964 +0x9c
    testing.(*T).Parallel(0xc0000d2600)
            c:/go/src/testing/testing.go:771 +0x1f8
    github.com/go-interpreter/wagon/exec_test.testModules.func2(0xc0000d2600)
            C:/Users/sampa/Desktop/wagon/exec/exec_test.go:391 +0x66
    testing.tRunner(0xc0000d2600, 0xc0002e33c0)
            c:/go/src/testing/testing.go:865 +0xc7
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 162 [chan send]:
    testing.tRunner.func1(0xc0000d2700)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000d2700, 0xc0002e3440)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 164 [running]:
            goroutine running on other thread; stack unavailable
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 165 [running]:
            goroutine running on other thread; stack unavailable
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 166 [chan send]:
    testing.tRunner.func1(0xc0000d2b00)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000d2b00, 0xc0002e3640)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 167 [chan send]:
    testing.tRunner.func1(0xc0000d2c00)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000d2c00, 0xc0002e36c0)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 168 [running]:
            goroutine running on other thread; stack unavailable
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 169 [running]:
            goroutine running on other thread; stack unavailable
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 170 [chan send]:
    testing.tRunner.func1(0xc0000d2f00)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000d2f00, 0xc0002e3840)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 171 [chan send]:
    testing.tRunner.func1(0xc0000d3000)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000d3000, 0xc0002e38c0)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 172 [chan send]:
    testing.tRunner.func1(0xc0000d3100)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000d3100, 0xc0002e3940)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 173 [chan send]:
    testing.tRunner.func1(0xc0000d3e00)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000d3e00, 0xc0002e39c0)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 174 [chan send]:
    testing.tRunner.func1(0xc000160500)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc000160500, 0xc0002e3a40)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 175 [chan send]:
    testing.tRunner.func1(0xc0000c2000)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000c2000, 0xc0002e3ac0)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 176 [chan send]:
    testing.tRunner.func1(0xc0000c2100)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000c2100, 0xc0002e3b40)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 177 [chan send]:
    testing.tRunner.func1(0xc0000c2200)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000c2200, 0xc0002e3bc0)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 178 [chan send]:
    testing.tRunner.func1(0xc0000c2300)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000c2300, 0xc0002e3c40)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 179 [chan send]:
    testing.tRunner.func1(0xc0000c2400)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000c2400, 0xc0002e3cc0)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 180 [chan send]:
    testing.tRunner.func1(0xc0000c2500)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000c2500, 0xc0002e3d40)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 181 [chan send]:
    testing.tRunner.func1(0xc0000c2600)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000c2600, 0xc0002e3dc0)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 182 [chan send]:
    testing.tRunner.func1(0xc0000c2700)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000c2700, 0xc0002e3e40)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 183 [chan send]:
    testing.tRunner.func1(0xc0000c2800)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000c2800, 0xc0002e3ec0)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 184 [chan send]:
    testing.tRunner.func1(0xc0000c2900)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000c2900, 0xc0002e3f40)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 185 [chan send]:
    testing.tRunner.func1(0xc0000c2a00)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000c2a00, 0xc0002e3fc0)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 186 [chan send]:
    testing.tRunner.func1(0xc0000c2b00)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000c2b00, 0xc000266100)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    
    goroutine 187 [chan send]:
    testing.tRunner.func1(0xc0000c2c00)
            c:/go/src/testing/testing.go:860 +0x28a
    testing.tRunner(0xc0000c2c00, 0xc0002661c0)
            c:/go/src/testing/testing.go:869 +0xd1
    created by testing.(*T).Run
            c:/go/src/testing/testing.go:916 +0x361
    rax     0x1
    rbx     0x1
    rcx     0xc00027d080
    rdi     0xc000d660c0
    rsi     0xc000176990
    rbp     0xc000df1758
    rsp     0xc000df1720
    r8      0x0
    r9      0x0
    r10     0xc000176900
    r11     0xc000176918
    r12     0xc00005db00
    r13     0x0
    r14     0xc00005db00
    r15     0xc000f487f0
    rip     0x3a70051
    rflags  0x10246
    cs      0x33
    fs      0x53
    gs      0x2b
    FAIL    github.com/go-interpreter/wagon/exec    2.192s
    ok      github.com/go-interpreter/wagon/exec/internal/compile   (cached)
    ?       github.com/go-interpreter/wagon/internal/stack  [no test files]
    ?       github.com/go-interpreter/wagon/validate        [no test files]
    --- FAIL: TestEncode (0.07s)
        --- FAIL: TestEncode/rust-basic.wasm (0.02s)
            encode_test.go:55: modules are different
    FAIL
    FAIL    github.com/go-interpreter/wagon/wasm    0.542s
    ok      github.com/go-interpreter/wagon/wasm/internal/readpos   (cached)
    ok      github.com/go-interpreter/wagon/wasm/leb128     (cached)
    ok      github.com/go-interpreter/wagon/wasm/operators  (cached)
    ok      github.com/go-interpreter/wagon/wast    (cached)
    
    C:\Users\sampa\Desktop\wagon>
    
  • RFC: Modules containing native functions

    RFC: Modules containing native functions

    In its current state, this PR is a request for comments.

    The context of this PR:

    • Use the wagon interpreter as a library in a bigger project and use it as a WASM interpreter;
    • The client code wants to make some of its internal functions available to the client.

    The approach that is undertaken in this PR is to add a NativeFunctions section to Module, that lists the "native" functions to be called by the interpreter. It also adds a Native flag to theFunction struct, which is false by default. When the interpreter encounters a call opcode, the Native flag is checked. If it is true, then a function is called.

    This approach has issues I'm already aware of:

    • NativeFunctions is not specified in the standard
    • it makes module version management more difficult

    In its current state, the PR has shortcomings that can be fixed:

    • native code might panic
    • it doesn't support calling functions with parameters
    • call_indirect is not currently supported

    I'm looking forward to hearing your thoughts on the matter.


    This change is Reviewable

  • disasm: Fix referencing the incorrect function type in call_indirect

    disasm: Fix referencing the incorrect function type in call_indirect

    In Disassemble, the function uses the integer immediate for call_indirect as an index into the global index space, while it should be an index into the Type Section of the module. This causes it to reference an incorrect function, which would cause stack underflows later on. This CL fixes that.

    Also, fix the select operator decreasing the stack height by 3 instead of 2.

    (Fixes #49)


    This change is Reviewable

  • Stack Underflow on valid wasm file

    Stack Underflow on valid wasm file

    I have this minimal example, which runs fine if I execute it with wasm-interp, but throws a ErrStackUnderflow if I try and execute it with wagon

    Source:

    #![allow(non_snake_case)]
    
    use std::mem;
    use std::collections::HashMap;
    
    pub struct Sample {
        map: HashMap<u64, u64>,
    }
    
    impl Sample {
        pub fn new() -> Sample {
            Sample { map: HashMap::new() }
        }
    }
    
    #[no_mangle]
    pub fn init() -> *mut Sample {
        let mut _s = unsafe { mem::transmute(Box::new(Sample::new())) };
        _s
    }
    

    wasm-intrep output

    > wasm-interp token.wasm --run-all-exports
    memory() => error: unreachable executed
    init() => i32:1114496
    

    Attached is the token.wasm.zip

  • exec: Add a function to re-use a VM that was Terminated

    exec: Add a function to re-use a VM that was Terminated

    When a VM has been terminated, it can not be re-used. By implementing a Restart function that clears the abort flag, clients of the VM interface can re-use a given VM.

  • invalid number of arguments to function

    invalid number of arguments to function

    Hi, first thanks for your great work.

    I met a strange behavior and got confuse whether it is a bug or excepted behavior.

    The source is simple cpp codes as

    extern "C" {
        extern void print(int v);
        extern int iadd(int a, int b);
        int test(){
            int a = iadd(4,10);
            print(a);
            return 0;
        }
    }
    

    then I got a .wasm file, I pasted the .wast content below:

    (module
     (type $FUNCSIG$iii (func (param i32 i32) (result i32)))
     (type $FUNCSIG$vi (func (param i32)))
     (import "env" "iadd" (func $iadd (param i32 i32) (result i32)))
     (import "env" "print" (func $print (param i32)))
     (table 0 anyfunc)
     (memory $0 1)
     (data (i32.const 4) "\10@\00\00")
     (export "memory" (memory $0))
     (export "test" (func $test))
     (func $test (result i32)
      (call $print
       (call $iadd
        (i32.const 4)
        (i32.const 10)
       )
      )
      (i32.const 0)
     )
    )
    

    Following your vm_example_test.go, I tried these ( only Types, FunctionIndexSpace, Export below)

    var nativeFunctionList = []string{
    	"print42",
    	"print",
    	"iadd",
    }
    
    var nativeFunctionTypes = &wasm.SectionTypes{
    	Entries: []wasm.FunctionSig{
    		{
    			ParamTypes:  []wasm.ValueType{},
    			ReturnTypes: []wasm.ValueType{},
    		},
    		{
    			ParamTypes:  []wasm.ValueType{wasm.ValueTypeI32},
    			ReturnTypes: []wasm.ValueType{},
    		},
    		{
    			ParamTypes:  []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32},
    			ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32},
    		},
    	},
    }
    
    var nativeFunction = []wasm.Function{
    	{
    		Sig:  &nativeFunctionTypes.Entries[0],
    		Host: reflect.ValueOf(func(proc *exec.Process) { fmt.Printf("result = 42\n") }),
    		Body: &wasm.FunctionBody{},
    	},
    	{
    		Sig:  &nativeFunctionTypes.Entries[1],
    		Host: reflect.ValueOf(func(cos *exec.Process, v int32) { fmt.Printf("result = %v \n", v) }),
    		Body: &wasm.FunctionBody{},
    	},
    	{
    		Sig:  &nativeFunctionTypes.Entries[2],
    		Host: reflect.ValueOf(func(proc *exec.Process, a int32, b int32) int32 { return a + b }),
    		Body: &wasm.FunctionBody{},
    	},
    }
    

    Look good, right?

    But in the runtime, compiler complained: invalid number of arguments to function. And I found the print function, which was passed 0 args.

    I tried to replace from int a = iadd(4,10); to int a = 4;. It works well.

    And I removed print(a);. It works well too.

    Just look as the

       (call $iadd
        (i32.const 4)
        (i32.const 10)
       )
    

    doesn't be treated as the parameter to print function even the return type is int32 ?

  • meta: new maintainer/champion for wagon

    meta: new maintainer/champion for wagon

    hi there,

    lately I've found my involvement in wagon to dwindle. partly because my main interest in wasm+wagon was to be able to build an interpreter for Go, in Go, and that there are now at least 2 Go interpreters (gomacro and yaegi) that fit the bill for me. partly because I just don't find time to allocate for all the WebAssembly-related activity.

    so... this is a meta-issue to look for new maintainers or champions. please reach out. (alternatively, wagon could be handed over to gowasm, if they want it)

    (I'll give it a month)

  • wasm: functions in FunctionIndexSpace include wrong contents

    wasm: functions in FunctionIndexSpace include wrong contents

    1. Build a hello-world wasm:
    package main
    
    func main() {
            println("Hello, World!")
    }
    
    GOOS=js GOARCH=wasm go build -o test.wasm helloworld.go 
    
    1. Run the below code:
    package main
    
    import (
            "fmt"
            "os"
    
            "github.com/go-interpreter/wagon/wasm"
    )
    
    func main() {
            f, err := os.Open("test.wasm")
            if err != nil {
                    panic(err)
            }
            defer f.Close()
    
            mod, err := wasm.ReadModule(f, nil)
            if err != nil {
                    panic(err)
            }
    
            fn := mod.FunctionIndexSpace[0]
            fmt.Printf("name: %q\n", fn.Name) // This is empty. That's fine since this is an imported function 'debug'.                                                                                                                           
            fmt.Printf("len(code): %d\n", len(fn.Body.Code)) // This is not empty. That's unexpected. This content is for the first function as 'go.buildid'.                                                                                     
    }
    

    It looks like the function's index and its instructions don't match. Some of the first functions should be imported functions, but include instructions for other functions. I think the function bodies are shifted.

  • wasm-run: execute expressions from argv

    wasm-run: execute expressions from argv

    Currently wasm-run executes every exported function. I modified it to execute expressions given as command line arguments in post fix order. e.g.: wasm-run file.wasm 1 2 neg 3 add (where neg takes one and add takes 2 arguments).

    var stack []uint64
    	for i := range args {
    		if u, e := strconv.ParseUint(args[i], 10, 64); e == nil {
    			stack = append(stack, u)
    		} else if args[i] == "dump" {
    			dump(vm.Memory(), stack[len(stack)-2], stack[len(stack)-1])
    			stack = stack[:len(stack)-2]
    		} else {
    			x, ok := m.Export.Entries[args[i]]
    			if !ok {
    				panic("unknown func: " + args[i])
    			}
    			fidx := m.Function.Types[x.Index]
    			ftyp := m.Types.Entries[fidx]
    			n := len(ftyp.ParamTypes)
    			pop := make([]uint64, n)
    			copy(pop, stack[len(stack)-n:])
    			stack = stack[:len(stack)-n]
    			res, e := vm.ExecCode(int64(x.Index), pop...)
    			if e != nil {
    				panic(e)
    			}
    			stack = append(stack, u64(res))
    			fmt.Printf("%s %v: %v(%x)\n", args[i], pop, res, res)
    		}
    	}
    

    Maybe you find it useful. (i use it in my wasm compiler for a custom language: https://github.com/ktye/i/tree/master/_/w)

  • Fix shift instructions

    Fix shift instructions

    Hi, as the Wasm Spec said, all shift instructions should mod the second arg: Let k be i2 modulo N. . And here is a simple online test case. I have fixed this bug and added some unit tests. Please double check this.

  • Expose access to Ops to avoid stringly typing downstream

    Expose access to Ops to avoid stringly typing downstream

    Avoid this kind of sadness:

    https://github.com/perlin-network/life/blob/master/compiler/ssa.go#L445-L454

    with:

    operators.Get(operators.CurrentMemory).Name
    

    Signed-off-by: Silas Davis [email protected]

A JavaScript interpreter in Go (golang)

otto -- import "github.com/robertkrimen/otto" Package otto is a JavaScript parser and interpreter written natively in Go. http://godoc.org/github.com/

Jan 2, 2023
gpython is a python interpreter written in go "batteries not included"

gpython gpython is a part re-implementation / part port of the Python 3.4 interpreter to the Go language, "batteries not included". It includes: runti

Dec 28, 2022
Yaegi is Another Elegant Go Interpreter
Yaegi is Another Elegant Go Interpreter

Yaegi is Another Elegant Go Interpreter. It powers executable Go scripts and plugins, in embedded interpreters or interactive shells, on top of the Go

Jan 5, 2023
A BASIC interpreter written in golang.
A BASIC interpreter written in golang.

05 PRINT "Index" 10 PRINT "GOBASIC!" 20 PRINT "Limitations" Arrays Line Numbers IF Statement DATA / READ Statements Builtin Functions Types 30 PRINT "

Dec 24, 2022
Lisp Interpreter

golisp Lisp Interpreter Usage $ golisp < foo.lisp Installation $ go get github.com/mattn/golisp/cmd/golisp Features Call Go functions. Print random in

Dec 15, 2022
A simple interpreter

类型: 基础类型: 整形,浮点,字符串,布尔,空值(nil) 符合类型: 数组,只读数组(元组),字典 支持语句: if, elif, else, for, foreach, break, continue, return 支持类型定义: class, func 语法格式: 赋值语句---> ```

Aug 10, 2022
Scriptable interpreter written in golang
Scriptable interpreter written in golang

Anko Anko is a scriptable interpreter written in Go. (Picture licensed under CC BY-SA 3.0, photo by Ocdp) Usage Example - Embedded package main impor

Dec 23, 2022
Syntax-aware Go code search, based on the mvdan/gogrep
Syntax-aware Go code search, based on the mvdan/gogrep

gogrep WIP: this is an attempt to move modified gogrep from the go-ruleguard project, so it can be used outside of the ruleguard as a library. Acknowl

Nov 9, 2022
Interpreter - The Official Interpreter for the Infant Lang written in Go

Infant Lang Interpreter Infant Lang Minimalistic Less Esoteric Programming Langu

Jan 10, 2022
Go compiler for small places. Microcontrollers, WebAssembly, and command-line tools. Based on LLVM.

TinyGo - Go compiler for small places TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and comma

Dec 30, 2022
Go compiler for small places. Microcontrollers, WebAssembly, and command-line tools. Based on LLVM.

TinyGo - Go compiler for small places TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and comma

Jan 4, 2023
An interpreter written in go for a brainfuck-based language called €*

eurostar-go-interpreter This is an interpreter written in go for a brainfuck-bas

Sep 14, 2022
A package to build progressive web apps with Go programming language and WebAssembly.
A package to build progressive web apps with Go programming language and WebAssembly.

go-app is a package to build progressive web apps (PWA) with Go programming language and WebAssembly. It uses a declarative syntax that allows creatin

Dec 28, 2022
Qt binding for Go (Golang) with support for Windows / macOS / Linux / FreeBSD / Android / iOS / Sailfish OS / Raspberry Pi / AsteroidOS / Ubuntu Touch / JavaScript / WebAssembly

Introduction Qt is a free and open-source widget toolkit for creating graphical user interfaces as well as cross-platform applications that run on var

Jan 2, 2023
WebAssembly interop between Go and JS values.

vert Package vert provides WebAssembly interop between Go and JS values. Install GOOS=js GOARCH=wasm go get github.com/norunners/vert Examples Hello W

Dec 28, 2022
WebAssembly for Proxies (Golang host implementation)

WebAssembly for Proxies (GoLang host implementation) The GoLang implementation for proxy-wasm, enabling developer to run proxy-wasm extensions in Go.

Dec 29, 2022
🐹🕸️ WebAssembly runtime for Go
🐹🕸️ WebAssembly runtime for Go

Wasmer Go Website • Docs • Slack Channel A complete and mature WebAssembly runtime for Go based on Wasmer. Features Easy to use: The wasmer API mimics

Jan 2, 2023
🐹🕸️ WebAssembly runtime for Go
🐹🕸️ WebAssembly runtime for Go

Wasmer Go Website • Docs • Slack Channel A complete and mature WebAssembly runtime for Go based on Wasmer. Features Easy to use: The wasmer API mimics

Dec 29, 2022
Golang-WASM provides a simple idiomatic, and comprehensive API and bindings for working with WebAssembly for Go and JavaScript developers
Golang-WASM provides a simple idiomatic, and comprehensive API and bindings for working with WebAssembly for Go and JavaScript developers

A bridge and bindings for JS DOM API with Go WebAssembly. Written by Team Ortix - Hamza Ali and Chan Wen Xu. GOOS=js GOARCH=wasm go get -u github.com/

Dec 22, 2022
📦 CLI for setting up a Go WebAssembly frontend app
📦 CLI for setting up a Go WebAssembly frontend app

Simple CLI for setting up Go WebAssembly frontend app. What's included ??️ Dev Server with live reload ??️ TinyGo for small WebAssembly output ➡ Git s

Dec 3, 2022