aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/internal
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
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')
-rw-r--r--src/cmd/internal/goobj/objfile.go1
-rw-r--r--src/cmd/internal/obj/link.go74
-rw-r--r--src/cmd/internal/obj/objfile.go17
-rw-r--r--src/cmd/internal/obj/sym.go12
-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
7 files changed, 222 insertions, 23 deletions
diff --git a/src/cmd/internal/goobj/objfile.go b/src/cmd/internal/goobj/objfile.go
index 547b826495..0364f856cf 100644
--- a/src/cmd/internal/goobj/objfile.go
+++ b/src/cmd/internal/goobj/objfile.go
@@ -442,6 +442,7 @@ const (
AuxPcline
AuxPcinline
AuxPcdata
+ AuxWasmImport
)
func (a *Aux) Type() uint8 { return a[0] }
diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go
index d153afbfae..077562a267 100644
--- a/src/cmd/internal/obj/link.go
+++ b/src/cmd/internal/obj/link.go
@@ -37,6 +37,7 @@ import (
"cmd/internal/objabi"
"cmd/internal/src"
"cmd/internal/sys"
+ "encoding/binary"
"fmt"
"sync"
"sync/atomic"
@@ -499,7 +500,9 @@ type FuncInfo struct {
WrapInfo *LSym // for wrapper, info of wrapped function
JumpTables []JumpTable
- FuncInfoSym *LSym
+ FuncInfoSym *LSym
+ WasmImportSym *LSym
+ WasmImport *WasmImport
}
// JumpTable represents a table used for implementing multi-way
@@ -558,6 +561,75 @@ func (s *LSym) File() *FileInfo {
return f
}
+// WasmImport represents a WebAssembly (WASM) imported function with
+// parameters and results translated into WASM types based on the Go function
+// declaration.
+type WasmImport struct {
+ // Module holds the WASM module name specified by the //go:wasmimport
+ // directive.
+ Module string
+ // Name holds the WASM imported function name specified by the
+ // //go:wasmimport directive.
+ Name string
+ // Params holds the imported function parameter fields.
+ Params []WasmField
+ // Results holds the imported function result fields.
+ Results []WasmField
+}
+
+func (wi *WasmImport) CreateSym(ctxt *Link) *LSym {
+ var sym LSym
+
+ var b [8]byte
+ writeByte := func(x byte) {
+ sym.WriteBytes(ctxt, sym.Size, []byte{x})
+ }
+ writeUint32 := func(x uint32) {
+ binary.LittleEndian.PutUint32(b[:], x)
+ sym.WriteBytes(ctxt, sym.Size, b[:4])
+ }
+ writeInt64 := func(x int64) {
+ binary.LittleEndian.PutUint64(b[:], uint64(x))
+ sym.WriteBytes(ctxt, sym.Size, b[:])
+ }
+ writeString := func(s string) {
+ writeUint32(uint32(len(s)))
+ sym.WriteString(ctxt, sym.Size, len(s), s)
+ }
+ writeString(wi.Module)
+ writeString(wi.Name)
+ writeUint32(uint32(len(wi.Params)))
+ for _, f := range wi.Params {
+ writeByte(byte(f.Type))
+ writeInt64(f.Offset)
+ }
+ writeUint32(uint32(len(wi.Results)))
+ for _, f := range wi.Results {
+ writeByte(byte(f.Type))
+ writeInt64(f.Offset)
+ }
+
+ return &sym
+}
+
+type WasmField struct {
+ Type WasmFieldType
+ // Offset holds the frame-pointer-relative locations for Go's stack-based
+ // ABI. This is used by the src/cmd/internal/wasm package to map WASM
+ // import parameters to the Go stack in a wrapper function.
+ Offset int64
+}
+
+type WasmFieldType byte
+
+const (
+ WasmI32 WasmFieldType = iota
+ WasmI64
+ WasmF32
+ WasmF64
+ WasmPtr
+)
+
type InlMark struct {
// When unwinding from an instruction in an inlined body, mark
// where we should unwind to.
diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go
index 73c29d9686..78fa4c1076 100644
--- a/src/cmd/internal/obj/objfile.go
+++ b/src/cmd/internal/obj/objfile.go
@@ -605,7 +605,12 @@ func (w *writer) Aux(s *LSym) {
for _, pcSym := range fn.Pcln.Pcdata {
w.aux1(goobj.AuxPcdata, pcSym)
}
-
+ if fn.WasmImportSym != nil {
+ if fn.WasmImportSym.Size == 0 {
+ panic("wasmimport aux sym must have non-zero size")
+ }
+ w.aux1(goobj.AuxWasmImport, fn.WasmImportSym)
+ }
}
}
@@ -703,6 +708,12 @@ func nAuxSym(s *LSym) int {
n++
}
n += len(fn.Pcln.Pcdata)
+ if fn.WasmImport != nil {
+ if fn.WasmImportSym == nil || fn.WasmImportSym.Size == 0 {
+ panic("wasmimport aux sym must exist and have non-zero size")
+ }
+ n++
+ }
}
return n
}
@@ -759,8 +770,8 @@ func genFuncInfoSyms(ctxt *Link) {
fn.FuncInfoSym = isym
b.Reset()
- dwsyms := []*LSym{fn.dwarfRangesSym, fn.dwarfLocSym, fn.dwarfDebugLinesSym, fn.dwarfInfoSym}
- for _, s := range dwsyms {
+ auxsyms := []*LSym{fn.dwarfRangesSym, fn.dwarfLocSym, fn.dwarfDebugLinesSym, fn.dwarfInfoSym, fn.WasmImportSym}
+ for _, s := range auxsyms {
if s == nil || s.Size == 0 {
continue
}
diff --git a/src/cmd/internal/obj/sym.go b/src/cmd/internal/obj/sym.go
index e0817d5f74..4a01af3927 100644
--- a/src/cmd/internal/obj/sym.go
+++ b/src/cmd/internal/obj/sym.go
@@ -416,16 +416,16 @@ func (ctxt *Link) traverseFuncAux(flag traverseFlag, fsym *LSym, fn func(parent
}
}
- dwsyms := []*LSym{fninfo.dwarfRangesSym, fninfo.dwarfLocSym, fninfo.dwarfDebugLinesSym, fninfo.dwarfInfoSym}
- for _, dws := range dwsyms {
- if dws == nil || dws.Size == 0 {
+ auxsyms := []*LSym{fninfo.dwarfRangesSym, fninfo.dwarfLocSym, fninfo.dwarfDebugLinesSym, fninfo.dwarfInfoSym, fninfo.WasmImportSym}
+ for _, s := range auxsyms {
+ if s == nil || s.Size == 0 {
continue
}
- fn(fsym, dws)
+ fn(fsym, s)
if flag&traverseRefs != 0 {
- for _, r := range dws.R {
+ for _, r := range s.R {
if r.Sym != nil {
- fn(dws, r.Sym)
+ fn(s, r.Sym)
}
}
}
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
}
}