Skip to content

Instantly share code, notes, and snippets.

@icexin
Created November 4, 2018 15:47
Show Gist options
  • Save icexin/3123b51d2ae97c21aade3ea2975ecea2 to your computer and use it in GitHub Desktop.
Save icexin/3123b51d2ae97c21aade3ea2975ecea2 to your computer and use it in GitHub Desktop.
A simple go wasm interpreter
package main
import (
"bytes"
"encoding/binary"
"flag"
"fmt"
"io/ioutil"
"syscall"
"time"
"github.com/perlin-network/life/exec"
)
var timeorigin = time.Now()
func dummyMethod(vm *exec.VirtualMachine) int64 {
return 0
}
// Resolver defines imports for WebAssembly modules ran in Life.
type Resolver struct {
tempRet0 int64
}
// ResolveFunc defines a set of import functions that may be called within a WebAssembly module.
func (r *Resolver) ResolveFunc(module, field string) exec.FunctionImport {
fmt.Printf("Resolve func: %s %s\n", module, field)
switch module {
case "env":
switch field {
case "__life_ping":
return func(vm *exec.VirtualMachine) int64 {
return vm.GetCurrentFrame().Locals[0] + 1
}
case "__life_log":
return func(vm *exec.VirtualMachine) int64 {
ptr := int(uint32(vm.GetCurrentFrame().Locals[0]))
msgLen := int(uint32(vm.GetCurrentFrame().Locals[1]))
msg := vm.Memory[ptr : ptr+msgLen]
fmt.Printf("[app] %s\n", string(msg))
return 0
}
default:
panic(fmt.Errorf("unknown field: %s", field))
}
case "go":
switch field {
case "runtime.nanotime":
return func(vm *exec.VirtualMachine) int64 {
sp := vm.GetCurrentFrame().Locals[0]
mem := vm.Memory
binary.LittleEndian.PutUint64(mem[sp+8:], uint64(time.Since(timeorigin).Nanoseconds()))
return 0
}
case "runtime.wasmWrite":
return func(vm *exec.VirtualMachine) int64 {
sp := vm.GetCurrentFrame().Locals[0]
mem := vm.Memory
buf := bytes.NewBuffer(mem[sp+8:])
var fd, p int64
var n int32
binary.Read(buf, binary.LittleEndian, &fd)
binary.Read(buf, binary.LittleEndian, &p)
binary.Read(buf, binary.LittleEndian, &n)
syscall.Write(int(fd), mem[p:p+int64(n)])
return 0
}
default:
return dummyMethod
}
default:
panic(fmt.Errorf("unknown module: %s", module))
}
}
// ResolveGlobal defines a set of global variables for use within a WebAssembly module.
func (r *Resolver) ResolveGlobal(module, field string) int64 {
fmt.Printf("Resolve global: %s %s\n", module, field)
switch module {
case "env":
switch field {
case "__life_magic":
return 424
default:
panic(fmt.Errorf("unknown field: %s", field))
}
default:
panic(fmt.Errorf("unknown module: %s", module))
}
}
func main() {
entryFunctionFlag := flag.String("entry", "run", "entry function id")
jitFlag := flag.Bool("jit", false, "enable jit")
flag.Parse()
// Read WebAssembly *.wasm file.
input, err := ioutil.ReadFile(flag.Arg(0))
if err != nil {
panic(err)
}
// Instantiate a new WebAssembly VM with a few resolved imports.
vm, err := exec.NewVirtualMachine(input, exec.VMConfig{
EnableJIT: *jitFlag,
DefaultMemoryPages: 128,
DefaultTableSize: 65536,
}, new(Resolver), nil)
if err != nil {
panic(err)
}
// Get the function ID of the entry function to be executed.
entryID, ok := vm.GetFunctionExport(*entryFunctionFlag)
if !ok {
fmt.Printf("Entry function %s not found; starting from 0.\n", *entryFunctionFlag)
entryID = 0
}
start := time.Now()
// If any function prior to the entry function was declared to be
// called by the module, run it first.
if vm.Module.Base.Start != nil {
startID := int(vm.Module.Base.Start.Index)
_, err := vm.Run(startID)
if err != nil {
vm.PrintStackTrace()
panic(err)
}
}
// Run the WebAssembly module's entry function.
ret, err := vm.Run(entryID, 0, 0)
if err != nil {
vm.PrintStackTrace()
panic(err)
}
end := time.Now()
fmt.Printf("return value = %d, duration = %v\n", ret, end.Sub(start))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment