Skip to content
This repository has been archived by the owner on May 11, 2020. It is now read-only.

could not create VM: disasm: stack underflow #69

Open
valvesss opened this issue Jul 18, 2018 · 11 comments
Open

could not create VM: disasm: stack underflow #69

valvesss opened this issue Jul 18, 2018 · 11 comments

Comments

@valvesss
Copy link

valvesss commented Jul 18, 2018

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):

package main

func main(){
  ret := retorno()
  _ = ret
}

func retorno() int64 {
  var num int64 = 50
  return num
}

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

@evandigby
Copy link

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",
}

@sbinet
Copy link
Contributor

sbinet commented Jul 22, 2018

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.

@evandigby
Copy link

@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?

@evandigby
Copy link

@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.

@evandigby
Copy link

@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

@vmesel
Copy link

vmesel commented Jul 23, 2018

@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

@sbinet
Copy link
Contributor

sbinet commented Jul 24, 2018

@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)
  • the 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
}

@sbinet
Copy link
Contributor

sbinet commented Jul 24, 2018

@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?

@evandigby
Copy link

@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.

@sbinet
Copy link
Contributor

sbinet commented Jul 24, 2018

ok.

@evandigby
Copy link

evandigby commented Jul 24, 2018

@sbinet #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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants