aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile
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/compile
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/compile')
-rw-r--r--src/cmd/compile/internal/gc/compile.go4
-rw-r--r--src/cmd/compile/internal/ir/func.go10
-rw-r--r--src/cmd/compile/internal/ir/sizeof_test.go2
-rw-r--r--src/cmd/compile/internal/noder/linker.go11
-rw-r--r--src/cmd/compile/internal/noder/noder.go37
-rw-r--r--src/cmd/compile/internal/noder/reader.go12
-rw-r--r--src/cmd/compile/internal/noder/writer.go26
-rw-r--r--src/cmd/compile/internal/ssagen/abi.go88
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
+}