Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wasm: re-use //export mechanism for exporting identifiers within wasm modules #25612

Open
sbinet opened this issue May 28, 2018 · 11 comments

Comments

@sbinet
Copy link
Member

commented May 28, 2018

Right now, compiling the following piece of Go code:

package main

func main() {
	println("hello")
	println("1+3=", MyAdd(1, 3))
}

//export MyAdd
func MyAdd(i, j int32) int32 {
	return i + j
}

like so:

$> GOOS=js GOARCH=wasm go build -o foo.wasm ./main.go

will produce the following foo.wasm module:

$> wasm-objdump -h ./foo.wasm

foo.wasm:	file format wasm 0x1

Sections:

     Type start=0x0000000e end=0x00000048 (size=0x0000003a) count: 10
   Import start=0x0000004e end=0x000000ce (size=0x00000080) count: 6
 Function start=0x000000d4 end=0x0000042c (size=0x00000358) count: 854
    Table start=0x00000432 end=0x00000437 (size=0x00000005) count: 1
   Memory start=0x0000043d end=0x00000442 (size=0x00000005) count: 1
   Global start=0x00000448 end=0x0000047b (size=0x00000033) count: 10
   Export start=0x00000481 end=0x0000048f (size=0x0000000e) count: 2
     Elem start=0x00000495 end=0x00000acf (size=0x0000063a) count: 1
     Code start=0x00000ad5 end=0x000a3b7a (size=0x000a30a5) count: 854
     Data start=0x000a3b80 end=0x001290fd (size=0x0008557d) count: 7
   Custom start=0x00129103 end=0x0012f117 (size=0x00006014) "name"

especially:

$> wasm-objdump -j export -x ./foo.wasm

foo.wasm:	file format wasm 0x1

Section Details:

Export:
 - func[750] <_rt0_wasm_js> -> "run"
 - memory[0] -> "mem"

ie: the user can not control what is exported.
the recommended way to export something currently, is to use js.NewCallback.
this is - mainly - because one needs to setup a few things for the Go and js runtimes to cooperate nicely together.

I would argue that, in the same spirit than when one compiles the same main.go file with -buildmode=c-shared, it should be possible to achieve the same thing for GOOS=xyz GOARCH=wasm, and only export what is //export-ed.
initialization of the runtime(s) would be performed via the wasm module's start function.

(this is a re-hash of neelance/go#22.)

@nilslice

This comment has been minimized.

Copy link

commented May 13, 2019

I've hit the same rough patches after doing some "real" work with wasm in Go, and trying to integrate my module into a JS workflow.

To me, the primary issue is how Go functions are provided to JS. Presently, it seems that polluting the global object (window) with Go functions is the only route. The method I've found to keep collisions to a minimum is to declare a new map[string]interface{} as a "namespace" which translates to a JS Object. Then to use this object to add typed js.Funcs:

func main() {
	done := make(chan struct{})
	js.Global().Set("protolock", make(map[string]interface{}))
	module := js.Global().Get("protolock")
	module.Set("initialize",
		js.FuncOf(func(this js.Value, args []js.Value) interface{} {
			if args == nil {
				fmt.Println("initialize: not enough args")
				return nil
			}

			return toJson(initialize(args[0].String()))
		}),
	)
	module.Set("status", 
		js.FuncOf(func(this js.Value, args []js.Value) interface{} {
			if args == nil || len(args) < 2 {
				fmt.Println("status: not enough args")
				return nil
			}

			return toJson(status(args[0].String(), args[1].String()))
	}))
	<-done
}

While this works, it doesn't make for a very seamless integration with modern JS workflows. Ideally, we'd be able to generate at minimum a .d.ts file and stubs for the JS functions made available from the wasm module, to fit into the ES6 import style used by the majority of JS toolchains (webpack, rollup, etc).

wasm-pack does a pretty good job with this, but it's not clear to me how we can achieve a similar experience with Go. Ideally, there would be a common interface to populate the instance.exports object, and to generate those populated functions as stubs that wrap an invocation of instance.exports.myFunc in a JS package which can be imported by other JS code.

This would make the interoperability between wasm modules in a JS environment much simpler, hiding the fact that a function is run in wasm.

I'm not sure the //export mechanism is right, which would complicate go build, and lock Go into a particular way of doing things. I'm not opposed to using //export , but would like to consider an option that doesn't need to be as deeply integrated. Rather, some code-gen tooling to read Go AST, output JS stubs and definition files (in ES6 module format). The tooling would need to be aware of how the compiled Go code translates into accessible JS calls - and this is the part where I need to do more digging... I'm very interested in helping out here, but I think there is more discourse to be had around the workflow.

Do we have a working doc/wiki anywhere to track this? I couldn't find anything in an admittedly rather shallow search. Happy to kick it off if one does not exist yet.

@nilslice

This comment has been minimized.

Copy link

commented Jun 5, 2019

I'm not sure the //export mechanism is right, which would complicate go build, and lock Go into a particular way of doing things. I'm not opposed to using //export , but would like to consider an option that doesn't need to be as deeply integrated.

After more digging and a quick browse through @neelance's wasm implementation as well as the disassembled .wat of a Go compiled .wasm binary, it's much more clear that the //export semantics are definitely needed.. I have a hacked together PoC of some AST tooling that generates the JS bridge code, but its nowhere near the ideal. Having control of what is explicitly provided to the instance.exports is the most usable way for JS integration, and hopefully future wasm->wasm module imports.

@johanbrandhorst

This comment has been minimized.

Copy link
Member

commented Jun 27, 2019

TinyGo exposes this functionality via a //go:export pragma. Personally I don't really like magic comments, especially when there is a way to do this in pure Go with syscall/js.

@justinclift

This comment has been minimized.

Copy link

commented Jun 27, 2019

//export vs //go:export seems like just a difference in the specific text fragment to use.

The meaning for each approach seems the same though: "This function should be included in the wasm exports list, and therefore callable from Javascript. Functions without it will not be".

@johanbrandhorst When you say there's a way to do it in pure Go with syscall/js, what are you meaning?

@aykevl

This comment has been minimized.

Copy link

commented Jun 27, 2019

//go:export is basically an alias for //export in TinyGo so you could use that as well.

@johanbrandhorst

This comment has been minimized.

Copy link
Member

commented Jun 27, 2019

I mean that you can also do

js.Global().Set("myexport", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    //stuff
})
@sbinet

This comment has been minimized.

Copy link
Member Author

commented Jun 27, 2019

the point of my original request is exactly to not only target and support js, but also browser-less environments, especially now with wasi picking up speed. (my own original use case being wagon)

@johanbrandhorst

This comment has been minimized.

Copy link
Member

commented Jun 27, 2019

It'll be hard to say whether we'll have the same capability to set exports via syscall/wasi until work has begun on that, so we can't really judge whether we'll need the pragma for that use case either. Don't we all agree that magic comments are pretty smelly?

@dingchaoyan1983

This comment has been minimized.

Copy link

commented Aug 8, 2019

the wasm can't be used in webworker also.

@pkieltyka

This comment has been minimized.

Copy link

commented Aug 8, 2019

Why can’t the wasm be used in a webworker?

@dingchaoyan1983

This comment has been minimized.

Copy link

commented Aug 13, 2019

@pkieltyka because the exported function is mounted on window or global variable, but in webworker, we should add it on self

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
10 participants
You can’t perform that action at this time.