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/compile | |
| 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/compile')
| -rw-r--r-- | src/cmd/compile/internal/gc/compile.go | 4 | ||||
| -rw-r--r-- | src/cmd/compile/internal/ir/func.go | 10 | ||||
| -rw-r--r-- | src/cmd/compile/internal/ir/sizeof_test.go | 2 | ||||
| -rw-r--r-- | src/cmd/compile/internal/noder/linker.go | 11 | ||||
| -rw-r--r-- | src/cmd/compile/internal/noder/noder.go | 37 | ||||
| -rw-r--r-- | src/cmd/compile/internal/noder/reader.go | 12 | ||||
| -rw-r--r-- | src/cmd/compile/internal/noder/writer.go | 26 | ||||
| -rw-r--r-- | src/cmd/compile/internal/ssagen/abi.go | 88 |
8 files changed, 185 insertions, 5 deletions
diff --git a/src/cmd/compile/internal/gc/compile.go b/src/cmd/compile/internal/gc/compile.go index cfce77d828..4795297e7e 100644 --- a/src/cmd/compile/internal/gc/compile.go +++ b/src/cmd/compile/internal/gc/compile.go @@ -43,6 +43,10 @@ func enqueueFunc(fn *ir.Func) { return // we'll get this as part of its enclosing function } + if ssagen.CreateWasmImportWrapper(fn) { + return + } + if len(fn.Body) == 0 { // Initialize ABI wrappers if necessary. ir.InitLSym(fn, false) diff --git a/src/cmd/compile/internal/ir/func.go b/src/cmd/compile/internal/ir/func.go index 967ebb02c2..76ab952157 100644 --- a/src/cmd/compile/internal/ir/func.go +++ b/src/cmd/compile/internal/ir/func.go @@ -133,6 +133,16 @@ type Func struct { // For wrapper functions, WrappedFunc point to the original Func. // Currently only used for go/defer wrappers. WrappedFunc *Func + + // WasmImport is used by the //go:wasmimport directive to store info about + // a WebAssembly function import. + WasmImport *WasmImport +} + +// WasmImport stores metadata associated with the //go:wasmimport pragma. +type WasmImport struct { + Module string + Name string } func NewFunc(pos src.XPos) *Func { diff --git a/src/cmd/compile/internal/ir/sizeof_test.go b/src/cmd/compile/internal/ir/sizeof_test.go index 754d1a8de0..307f40d484 100644 --- a/src/cmd/compile/internal/ir/sizeof_test.go +++ b/src/cmd/compile/internal/ir/sizeof_test.go @@ -20,7 +20,7 @@ func TestSizeof(t *testing.T) { _32bit uintptr // size on 32bit platforms _64bit uintptr // size on 64bit platforms }{ - {Func{}, 184, 320}, + {Func{}, 188, 328}, {Name{}, 100, 176}, } diff --git a/src/cmd/compile/internal/noder/linker.go b/src/cmd/compile/internal/noder/linker.go index 0f39fdec05..44de017ae5 100644 --- a/src/cmd/compile/internal/noder/linker.go +++ b/src/cmd/compile/internal/noder/linker.go @@ -5,6 +5,7 @@ package noder import ( + "internal/buildcfg" "internal/pkgbits" "io" @@ -269,6 +270,16 @@ func (l *linker) relocFuncExt(w *pkgbits.Encoder, name *ir.Name) { l.pragmaFlag(w, name.Func.Pragma) l.linkname(w, name) + if buildcfg.GOARCH == "wasm" { + if name.Func.WasmImport != nil { + w.String(name.Func.WasmImport.Module) + w.String(name.Func.WasmImport.Name) + } else { + w.String("") + w.String("") + } + } + // Relocated extension data. w.Bool(true) diff --git a/src/cmd/compile/internal/noder/noder.go b/src/cmd/compile/internal/noder/noder.go index 10619bf569..16113e37a3 100644 --- a/src/cmd/compile/internal/noder/noder.go +++ b/src/cmd/compile/internal/noder/noder.go @@ -7,6 +7,7 @@ package noder import ( "errors" "fmt" + "internal/buildcfg" "os" "path/filepath" "runtime" @@ -166,9 +167,17 @@ var allowedStdPragmas = map[string]bool{ // *pragmas is the value stored in a syntax.pragmas during parsing. type pragmas struct { - Flag ir.PragmaFlag // collected bits - Pos []pragmaPos // position of each individual flag - Embeds []pragmaEmbed + Flag ir.PragmaFlag // collected bits + Pos []pragmaPos // position of each individual flag + Embeds []pragmaEmbed + WasmImport *WasmImport +} + +// WasmImport stores metadata associated with the //go:wasmimport pragma +type WasmImport struct { + Pos syntax.Pos + Module string + Name string } type pragmaPos struct { @@ -192,6 +201,9 @@ func (p *noder) checkUnusedDuringParse(pragma *pragmas) { p.error(syntax.Error{Pos: e.Pos, Msg: "misplaced go:embed directive"}) } } + if pragma.WasmImport != nil { + p.error(syntax.Error{Pos: pragma.WasmImport.Pos, Msg: "misplaced go:wasmimport directive"}) + } } // pragma is called concurrently if files are parsed concurrently. @@ -219,6 +231,25 @@ func (p *noder) pragma(pos syntax.Pos, blankLine bool, text string, old syntax.P } switch { + case strings.HasPrefix(text, "go:wasmimport "): + f := strings.Fields(text) + if len(f) != 3 { + p.error(syntax.Error{Pos: pos, Msg: "usage: //go:wasmimport importmodule importname"}) + break + } + if !base.Flag.CompilingRuntime && base.Ctxt.Pkgpath != "syscall/js" && base.Ctxt.Pkgpath != "syscall/js_test" { + p.error(syntax.Error{Pos: pos, Msg: "//go:wasmimport directive cannot be used outside of runtime or syscall/js"}) + break + } + + if buildcfg.GOARCH == "wasm" { + // Only actually use them if we're compiling to WASM though. + pragma.WasmImport = &WasmImport{ + Pos: pos, + Module: f[1], + Name: f[2], + } + } case strings.HasPrefix(text, "go:linkname "): f := strings.Fields(text) if !(2 <= len(f) && len(f) <= 3) { diff --git a/src/cmd/compile/internal/noder/reader.go b/src/cmd/compile/internal/noder/reader.go index b7605e9317..e4ab80b2d0 100644 --- a/src/cmd/compile/internal/noder/reader.go +++ b/src/cmd/compile/internal/noder/reader.go @@ -1081,6 +1081,18 @@ func (r *reader) funcExt(name *ir.Name, method *types.Sym) { fn.Pragma = r.pragmaFlag() r.linkname(name) + if buildcfg.GOARCH == "wasm" { + xmod := r.String() + xname := r.String() + + if xmod != "" && xname != "" { + fn.WasmImport = &ir.WasmImport{ + Module: xmod, + Name: xname, + } + } + } + typecheck.Func(fn) if r.Bool() { diff --git a/src/cmd/compile/internal/noder/writer.go b/src/cmd/compile/internal/noder/writer.go index da5c1e910d..5dd8d1de2d 100644 --- a/src/cmd/compile/internal/noder/writer.go +++ b/src/cmd/compile/internal/noder/writer.go @@ -6,6 +6,7 @@ package noder import ( "fmt" + "internal/buildcfg" "internal/pkgbits" "cmd/compile/internal/base" @@ -1003,11 +1004,15 @@ func (w *writer) funcExt(obj *types2.Func) { if pragma&ir.Systemstack != 0 && pragma&ir.Nosplit != 0 { w.p.errorf(decl, "go:nosplit and go:systemstack cannot be combined") } + wi := asWasmImport(decl.Pragma) if decl.Body != nil { if pragma&ir.Noescape != 0 { w.p.errorf(decl, "can only use //go:noescape with external func implementations") } + if wi != nil { + w.p.errorf(decl, "can only use //go:wasmimport with external func implementations") + } if (pragma&ir.UintptrKeepAlive != 0 && pragma&ir.UintptrEscapes == 0) && pragma&ir.Nosplit == 0 { // Stack growth can't handle uintptr arguments that may // be pointers (as we don't know which are pointers @@ -1028,7 +1033,8 @@ func (w *writer) funcExt(obj *types2.Func) { if base.Flag.Complete || decl.Name.Value == "init" { // Linknamed functions are allowed to have no body. Hopefully // the linkname target has a body. See issue 23311. - if _, ok := w.p.linknames[obj]; !ok { + // Wasmimport functions are also allowed to have no body. + if _, ok := w.p.linknames[obj]; !ok && wi == nil { w.p.errorf(decl, "missing function body") } } @@ -1041,6 +1047,17 @@ func (w *writer) funcExt(obj *types2.Func) { w.Sync(pkgbits.SyncFuncExt) w.pragmaFlag(pragma) w.linkname(obj) + + if buildcfg.GOARCH == "wasm" { + if wi != nil { + w.String(wi.Module) + w.String(wi.Name) + } else { + w.String("") + w.String("") + } + } + w.Bool(false) // stub extension w.Reloc(pkgbits.RelocBody, body) w.Sync(pkgbits.SyncEOF) @@ -2728,6 +2745,13 @@ func asPragmaFlag(p syntax.Pragma) ir.PragmaFlag { return p.(*pragmas).Flag } +func asWasmImport(p syntax.Pragma) *WasmImport { + if p == nil { + return nil + } + return p.(*pragmas).WasmImport +} + // isPtrTo reports whether from is the type *to. func isPtrTo(from, to types2.Type) bool { ptr, ok := from.(*types2.Pointer) diff --git a/src/cmd/compile/internal/ssagen/abi.go b/src/cmd/compile/internal/ssagen/abi.go index fa26ae1f06..9c725b898d 100644 --- a/src/cmd/compile/internal/ssagen/abi.go +++ b/src/cmd/compile/internal/ssagen/abi.go @@ -11,11 +11,14 @@ import ( "os" "strings" + "cmd/compile/internal/abi" "cmd/compile/internal/base" "cmd/compile/internal/ir" + "cmd/compile/internal/objw" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" "cmd/internal/obj" + "cmd/internal/obj/wasm" ) // SymABIs records information provided by the assembler about symbol @@ -336,3 +339,88 @@ func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) { typecheck.DeclContext = savedclcontext ir.CurFunc = savedcurfn } + +// CreateWasmImportWrapper creates a wrapper for imported WASM functions to +// adapt them to the Go calling convention. The body for this function is +// generated in cmd/internal/obj/wasm/wasmobj.go +func CreateWasmImportWrapper(fn *ir.Func) bool { + if fn.WasmImport == nil { + return false + } + if buildcfg.GOARCH != "wasm" { + base.FatalfAt(fn.Pos(), "CreateWasmImportWrapper call not supported on %s: func was %v", buildcfg.GOARCH, fn) + } + + ir.InitLSym(fn, true) + + setupWasmABI(fn) + + pp := objw.NewProgs(fn, 0) + defer pp.Free() + pp.Text.To.Type = obj.TYPE_TEXTSIZE + pp.Text.To.Val = int32(types.RoundUp(fn.Type().ArgWidth(), int64(types.RegSize))) + // Wrapper functions never need their own stack frame + pp.Text.To.Offset = 0 + pp.Flush() + + return true +} + +func toWasmFields(result *abi.ABIParamResultInfo, abiParams []abi.ABIParamAssignment) []obj.WasmField { + wfs := make([]obj.WasmField, len(abiParams)) + for i, p := range abiParams { + t := p.Type + switch { + case t.IsInteger() && t.Size() == 4: + wfs[i].Type = obj.WasmI32 + case t.IsInteger() && t.Size() == 8: + wfs[i].Type = obj.WasmI64 + case t.IsFloat() && t.Size() == 4: + wfs[i].Type = obj.WasmF32 + case t.IsFloat() && t.Size() == 8: + wfs[i].Type = obj.WasmF64 + case t.IsPtr(): + wfs[i].Type = obj.WasmPtr + default: + base.Fatalf("wasm import has bad function signature") + } + wfs[i].Offset = p.FrameOffset(result) + } + return wfs +} + +// setupTextLSym initializes the LSym for a with-body text symbol. +func setupWasmABI(f *ir.Func) { + wi := obj.WasmImport{ + Module: f.WasmImport.Module, + Name: f.WasmImport.Name, + } + if wi.Module == wasm.GojsModule { + // Functions that are imported from the "gojs" module use a special + // ABI that just accepts the stack pointer. + // Example: + // + // //go:wasmimport gojs add + // func importedAdd(a, b uint) uint + // + // will roughly become + // + // (import "gojs" "add" (func (param i32))) + wi.Params = []obj.WasmField{{Type: obj.WasmI32}} + } else { + // All other imported functions use the normal WASM ABI. + // Example: + // + // //go:wasmimport a_module add + // func importedAdd(a, b uint) uint + // + // will roughly become + // + // (import "a_module" "add" (func (param i32 i32) (result i32))) + abiConfig := AbiForBodylessFuncStackMap(f) + abiInfo := abiConfig.ABIAnalyzeFuncType(f.Type().FuncType()) + wi.Params = toWasmFields(abiInfo, abiInfo.InParams()) + wi.Results = toWasmFields(abiInfo, abiInfo.OutParams()) + } + f.LSym.Func().WasmImport = &wi +} |
