diff options
| author | Evan Phoenix <evan@phx.io> | 2023-01-22 15:30:59 -0800 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2023-03-02 05:28:55 +0000 |
| commit | 02411bcd7c8eda9c694a5755aff0a516d4983952 (patch) | |
| tree | 71106b1c22a459f53d59f68751267a35c4ddf65a /src/cmd/internal/obj/wasm | |
| parent | af9f21289fff0c513df3a785c97d8ca35e1829b2 (diff) | |
| download | go-02411bcd7c8eda9c694a5755aff0a516d4983952.tar.xz | |
all: implement wasmimport directive
Go programs can now use the //go:wasmimport module_name function_name
directive to import functions from the WebAssembly runtime.
For now, the directive is restricted to the runtime and syscall/js
packages.
* Derived from CL 350737
* Original work modified to work with changes to the IR conversion code.
* Modification of CL 350737 changes to fully exist in Unified IR path (emp)
* Original work modified to work with changes to the ABI configuration code.
* Fixes #38248
Co-authored-by: Vedant Roy <vroy101@gmail.com>
Co-authored-by: Richard Musiol <mail@richard-musiol.de>
Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
Change-Id: I740719735d91c306ac718a435a78e1ee9686bc16
Reviewed-on: https://go-review.googlesource.com/c/go/+/463018
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
Reviewed-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
Diffstat (limited to 'src/cmd/internal/obj/wasm')
| -rw-r--r-- | src/cmd/internal/obj/wasm/a.out.go | 3 | ||||
| -rw-r--r-- | src/cmd/internal/obj/wasm/anames.go | 3 | ||||
| -rw-r--r-- | src/cmd/internal/obj/wasm/wasmobj.go | 135 |
3 files changed, 128 insertions, 13 deletions
diff --git a/src/cmd/internal/obj/wasm/a.out.go b/src/cmd/internal/obj/wasm/a.out.go index 83ce0a6738..0262630d5a 100644 --- a/src/cmd/internal/obj/wasm/a.out.go +++ b/src/cmd/internal/obj/wasm/a.out.go @@ -18,8 +18,7 @@ const ( * wasm */ const ( - ACallImport = obj.ABaseWasm + obj.A_ARCHSPECIFIC + iota - AGet + AGet = obj.ABaseWasm + obj.A_ARCHSPECIFIC + iota ASet ATee ANot // alias for I32Eqz diff --git a/src/cmd/internal/obj/wasm/anames.go b/src/cmd/internal/obj/wasm/anames.go index c9bc15d270..6f1a662960 100644 --- a/src/cmd/internal/obj/wasm/anames.go +++ b/src/cmd/internal/obj/wasm/anames.go @@ -5,8 +5,7 @@ package wasm import "cmd/internal/obj" var Anames = []string{ - obj.A_ARCHSPECIFIC: "CallImport", - "Get", + obj.A_ARCHSPECIFIC: "Get", "Set", "Tee", "Not", diff --git a/src/cmd/internal/obj/wasm/wasmobj.go b/src/cmd/internal/obj/wasm/wasmobj.go index 96a2ef4a6f..fd0faec84b 100644 --- a/src/cmd/internal/obj/wasm/wasmobj.go +++ b/src/cmd/internal/obj/wasm/wasmobj.go @@ -100,7 +100,6 @@ var unaryDst = map[obj.As]bool{ ATee: true, ACall: true, ACallIndirect: true, - ACallImport: true, ABr: true, ABrIf: true, ABrTable: true, @@ -135,6 +134,14 @@ const ( WasmImport = 1 << 0 ) +const ( + // This is a special wasm module name that when used as the module name + // in //go:wasmimport will cause the generated code to pass the stack pointer + // directly to the imported function. In other words, any function that + // uses the gojs module understands the internal Go WASM ABI directly. + GojsModule = "gojs" +) + func instinit(ctxt *obj.Link) { morestack = ctxt.Lookup("runtime.morestack") morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt") @@ -177,7 +184,121 @@ func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { s.Func().Args = s.Func().Text.To.Val.(int32) s.Func().Locals = int32(framesize) - if s.Func().Text.From.Sym.Wrapper() { + // If the function exits just to call out to a wasmimport, then + // generate the code to translate from our internal Go-stack + // based call convention to the native webassembly call convention. + if wi := s.Func().WasmImport; wi != nil { + s.Func().WasmImportSym = wi.CreateSym(ctxt) + p := s.Func().Text + if p.Link != nil { + panic("wrapper functions for WASM imports should not have a body") + } + to := obj.Addr{ + Type: obj.TYPE_MEM, + Name: obj.NAME_EXTERN, + Sym: s, + } + + // If the module that the import is for is our magic "gojs" module, then this + // indicates that the called function understands the Go stack-based call convention + // so we just pass the stack pointer to it, knowing it will read the params directly + // off the stack and push the results into memory based on the stack pointer. + if wi.Module == GojsModule { + // The called function has a signature of 'func(sp int)'. It has access to the memory + // value somewhere to be able to address the memory based on the "sp" value. + + p = appendp(p, AGet, regAddr(REG_SP)) + p = appendp(p, ACall, to) + + p.Mark = WasmImport + } else { + if len(wi.Results) > 1 { + // TODO(evanphx) implement support for the multi-value proposal: + // https://github.com/WebAssembly/multi-value/blob/master/proposals/multi-value/Overview.md + panic("invalid results type") // impossible until multi-value proposal has landed + } + if len(wi.Results) == 1 { + // If we have a result (rather than returning nothing at all), then + // we'll write the result to the Go stack relative to the current stack pointer. + // We cache the current stack pointer value on the wasm stack here and then use + // it after the Call instruction to store the result. + p = appendp(p, AGet, regAddr(REG_SP)) + } + for _, f := range wi.Params { + // Each load instructions will consume the value of sp on the stack, so + // we need to read sp for each param. WASM appears to not have a stack dup instruction + // (a strange ommission for a stack-based VM), if it did, we'd be using the dup here. + p = appendp(p, AGet, regAddr(REG_SP)) + + // Offset is the location of the param on the Go stack (ie relative to sp). + // Because of our call convention, the parameters are located an additional 8 bytes + // from sp because we store the return address as a int64 at the bottom of the stack. + // Ie the stack looks like [return_addr, param3, param2, param1, etc] + + // Ergo, we add 8 to the true byte offset of the param to skip the return address. + loadOffset := f.Offset + 8 + + // We're reading the value from the Go stack onto the WASM stack and leaving it there + // for CALL to pick them up. + switch f.Type { + case obj.WasmI32: + p = appendp(p, AI32Load, constAddr(loadOffset)) + case obj.WasmI64: + p = appendp(p, AI64Load, constAddr(loadOffset)) + case obj.WasmF32: + p = appendp(p, AF32Load, constAddr(loadOffset)) + case obj.WasmF64: + p = appendp(p, AF64Load, constAddr(loadOffset)) + case obj.WasmPtr: + p = appendp(p, AI64Load, constAddr(loadOffset)) + p = appendp(p, AI32WrapI64) + default: + panic("bad param type") + } + } + + // The call instruction is marked as being for a wasm import so that a later phase + // will generate relocation information that allows us to patch this with then + // offset of the imported function in the wasm imports. + p = appendp(p, ACall, to) + p.Mark = WasmImport + + if len(wi.Results) == 1 { + f := wi.Results[0] + + // Much like with the params, we need to adjust the offset we store the result value + // to by 8 bytes to account for the return address on the Go stack. + storeOffset := f.Offset + 8 + + // This code is paired the code above that reads the stack pointer onto the wasm + // stack. We've done this so we have a consistent view of the sp value as it might + // be manipulated by the call and we want to ignore that manipulation here. + switch f.Type { + case obj.WasmI32: + p = appendp(p, AI32Store, constAddr(storeOffset)) + case obj.WasmI64: + p = appendp(p, AI64Store, constAddr(storeOffset)) + case obj.WasmF32: + p = appendp(p, AF32Store, constAddr(storeOffset)) + case obj.WasmF64: + p = appendp(p, AF64Store, constAddr(storeOffset)) + case obj.WasmPtr: + p = appendp(p, AI64ExtendI32U) + p = appendp(p, AI64Store, constAddr(storeOffset)) + default: + panic("bad result type") + } + } + } + + p = appendp(p, obj.ARET) + + // It should be 0 already, but we'll set it to 0 anyway just to be sure + // that the code below which adds frame expansion code to the function body + // isn't run. We don't want the frame expansion code because our function + // body is just the code to translate and call the imported function. + framesize = 0 + } else if s.Func().Text.From.Sym.Wrapper() { // if g._panic != nil && g._panic.argp == FP { // g._panic.argp = bottom-of-frame // } @@ -241,7 +362,9 @@ func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { p.Spadj = int32(framesize) } - needMoreStack := !s.Func().Text.From.Sym.NoSplit() + // If the framesize is 0, then imply nosplit because it's a specially + // generated function. + needMoreStack := framesize > 0 && !s.Func().Text.From.Sym.NoSplit() // If the maymorestack debug option is enabled, insert the // call to maymorestack *before* processing resume points so @@ -707,12 +830,6 @@ func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { default: panic("bad MOV type") } - - case ACallImport: - p.As = obj.ANOP - p = appendp(p, AGet, regAddr(REG_SP)) - p = appendp(p, ACall, obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: s}) - p.Mark = WasmImport } } |
