aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/internal/obj/wasm
diff options
context:
space:
mode:
authorEvan Phoenix <evan@phx.io>2023-01-22 15:30:59 -0800
committerGopher Robot <gobot@golang.org>2023-03-02 05:28:55 +0000
commit02411bcd7c8eda9c694a5755aff0a516d4983952 (patch)
tree71106b1c22a459f53d59f68751267a35c4ddf65a /src/cmd/internal/obj/wasm
parentaf9f21289fff0c513df3a785c97d8ca35e1829b2 (diff)
downloadgo-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.go3
-rw-r--r--src/cmd/internal/obj/wasm/anames.go3
-rw-r--r--src/cmd/internal/obj/wasm/wasmobj.go135
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
}
}