Open valvesss opened 6 years ago
I still think it should handle the issue more gracefully, but this isn't working because it needs the "go" module loaded into the runtime (as represented by wasm_exec.js that comes with your Go installation).
If I stub out the go module it gets past this issue and executes the wasm module, meaning I can call "run" which is the entrypoint that Go exposes. The results are less than satisfying because there are a number of imported functions that it uses that are required for it to work.
I'll continue to work on implementing a mirror of the go module:
func main() {
// ...
g, err := newGoModule()
if err != nil {
log.Fatal("error creating go")
}
// ...
m, err := wasm.ReadModule(f, func(name string) (*wasm.Module, error) {
switch name {
case "go":
return g.m, nil
default:
panic("oops" + name)
}
})
if err != nil {
log.Fatalf("could not read module: %v", err)
}
}
type gomod struct {
m *wasm.Module
}
func stub(name string) func(proc *exec.Process, val int32) {
return func(proc *exec.Process, val int32) { fmt.Println("called", name, "with val", val) }
}
func newGoModule() (*gomod, error) {
g := &gomod{
m: wasm.NewModule(),
}
g.m.Types = &wasm.SectionTypes{
Entries: []wasm.FunctionSig{
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
}, // go debug {1} function <func [i32] -> []>
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
}, // go runtime.wasmExit {1} function <func [i32] -> []>
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
}, // go runtime.wasmWrite {1} function <func [i32] -> []>
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
}, // go runtime.nanotime {1} function <func [i32] -> []>
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
}, // go runtime.walltime {1} function <func [i32] -> []>
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
}, // go runtime.scheduleCallback {1} function <func [i32] -> []>
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
}, // go runtime.clearScheduledCallback {1} function <func [i32] -> []>
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
}, // go runtime.getRandomData {1} function <func [i32] -> []>
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
}, // go syscall/js.stringVal {1} function <func [i32] -> []>
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
}, // go syscall/js.valueGet {1} function <func [i32] -> []>
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
}, // go syscall/js.valueCall {1} function <func [i32] -> []>
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
}, // go syscall/js.valueNew {1} function <func [i32] -> []>
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
}, // go syscall/js.valuePrepareString {1} function <func [i32] -> []>
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
}, // go syscall/js.valueLoadString {1} function <func [i32] -> []>
},
}
g.m.Export.Entries = map[string]wasm.ExportEntry{}
for i := range g.m.Types.Entries {
g.m.FunctionIndexSpace = append(g.m.FunctionIndexSpace, wasm.Function{
Sig: &g.m.Types.Entries[i],
Host: reflect.ValueOf(stub(funcs[i])),
Body: &wasm.FunctionBody{},
})
g.m.Export.Entries[funcs[i]] = wasm.ExportEntry{
FieldStr: funcs[i],
Kind: wasm.ExternalFunction,
Index: uint32(i),
}
}
return g, nil
}
var funcs = []string{
"debug",
"runtime.wasmExit",
"runtime.wasmWrite",
"runtime.nanotime",
"runtime.walltime",
"runtime.scheduleCallback",
"runtime.clearScheduledCallback",
"runtime.getRandomData",
"syscall/js.stringVal",
"syscall/js.valueGet",
"syscall/js.valueCall",
"syscall/js.valueNew",
"syscall/js.valuePrepareString",
"syscall/js.valueLoadString",
}
Nice! That's something I wanted to tackle for (or be ready by) 1.11. I'd be happy to merge this in, either as a separate package that exposes such a module, or a 'default' importer.
@sbinet will definitely be useful to have. There will definitely be places where we need to stub with “not available” type exceptions since (I hope) nobody will expect this to implement the full JavaScript environment.
Where possible I’ll try to make it return Go’s “js.Undefined” in “get” type calls or act like it would in a browser if a function/object wasn’t available on a “call” or “set”.
The runtime methods should be functional though.
If I was to PR this in where would standard modules live in wagon?
@sbinet Would something like the following be considered, or is there a specific reason we don't want to be able to point directly at the in process memory?
// ErrOutOfBounds is returned when you attempt to buffer memory out of the bounds of process memory
var ErrOutOfBounds = errors.New("offset and length out of memory bounds")
// BufferAt returns a slice pointing at the process memory at offset "off" for length "len"
func (proc *Process) BufferAt(offset, length int64) ([]byte, error) {
mem := proc.vm.Memory()
if len(mem) < int(offset+length) {
return nil, ErrOutOfBounds
}
return mem[offset : offset+length], nil
}
The reason I ask is implementing the Go module is requiring significant creating and copying of []byte buffers where it normally wouldn't be required if we can just slice the memory.
@sbinet Unfortunately even the most basic "Hello, World!" appears to require at least some emulation of the JS environment.
Will continue to work on it tonight. I should have something that's functional if not perfect in the next few days.
WIP branch: https://github.com/netvm/wagon/tree/go-runtime
@evandigby @sbinet I'm developing an importer, and while I was compiling my WASM, i got into a trouble running it. It says the memory should be exported and the table too. I think my emcc command line must be wrong. Can you guys help me out?
This is my C code that I want to export to WASM
void consoleLog(int v);
int main(int argc, char ** argv) {
consoleLog(5);
}
Its WAT:
(module
(type (;0;) (func (param i32)))
(type (;1;) (func (param i32 i32) (result i32)))
(type (;2;) (func))
(type (;3;) (func (result f64)))
(import "env" "table" (table (;0;) 2 anyfunc))
(import "env" "memoryBase" (global (;0;) i32))
(import "env" "tableBase" (global (;1;) i32))
(import "env" "abort" (func (;0;) (type 0)))
(import "env" "_consoleLog" (func (;1;) (type 0)))
(func (;2;) (type 1) (param i32 i32) (result i32)
i32.const 5
call 1
i32.const 0)
(func (;3;) (type 2)
nop)
(func (;4;) (type 2)
get_global 0
set_global 2
get_global 2
i32.const 5242880
i32.add
set_global 3)
(func (;5;) (type 3) (result f64)
i32.const 0
call 0
f64.const 0x0p+0 (;=0;))
(global (;2;) (mut i32) (i32.const 0))
(global (;3;) (mut i32) (i32.const 0))
(global (;4;) i32 (i32.const 1))
(export "__post_instantiate" (func 4))
(export "_main" (func 2))
(export "runPostSets" (func 3))
(export "fp$_main" (global 4))
(elem (get_global 1) 5 2))
The EMCC command line:
emcc -o testdata/hello.html testdata/program.c -O3 -s WASM=1 -s SIDE_MODULE=1
@evandigby this looks good.
I do have some minor comments but this can be worked out during the PR:
BufferAt
could be implemented in terms of io.WriterAt
/io.ReaderAt
(vm.Process
implements those)goExportIndexDebug
etc... could be just an array:var exports = [...]export{
{"debug", nil, []wasm.ValueType{wasm.ValueTypeI32}, nil},
{"runtime.wasmExit", Go.exportExit, []wasm.ValueType{wasm.ValueTypeI32}, nil},
}
type export struct {
Name string
Func func(g *Go, proc *exec.Process, sp int32)
Params []wasm.ValueType
Returns []wasm.ValueType
}
@vmesel I am afraid I myself have very limited knowledge about EMCC (perhaps you'd have better luck with their mailing list?)
that said...
It says the memory should be exported and the table too.
which tool says that?
@sbinet thanks! We can definitely go into more detail in PR. I had all of these functions implemented using read and write at; however, what I saw was that the process implementations of reader and writer at are copying data with every call when the intent established in wasm_exec.js was to refer to the memory directly. I suspected this could become a performance issue if Go’s runtime is expecting to refer to memory efficiently rather than copying it out.
I’ll leave it as-is for now just to get it finished then we can go into detail in PR. Changing it out would be simple.
ok.
@sbinet https://github.com/go-interpreter/wagon/pull/70 Work in progress PR so we can start discussing there. Obviously not ready to merge.
It's quite interesting what happens when you run this against a simple hello world go app.
Lots of bootstrapping the js environment with "eval" calls to script.
Btw the "run" function for Go always requires 2 params so to run it you need to pass 2 ints to it. 0, 0 works fine for what I've been doing.
Hello, I'm trying to compile and run a simple program from GO to WASM and execute the VM with wagon, but I'm stuck at stack underflow. Any idea?
Code (helloworld.go):
Compile (With golang beta, go1.11beta1):
GOARCH=wasm GOOS=js go1.11beta1 build -o test.wasm helloworld.go
Running (wagon functions, latest version) :
https://gist.github.com/valvesss/c3ac06a597bf25d466271275dd478fb3
Result:
could not create VM: disasm: stack underflow