aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile
diff options
context:
space:
mode:
authorCherry Mui <cherryyz@google.com>2024-08-03 14:20:58 -0400
committerCherry Mui <cherryyz@google.com>2024-08-09 20:07:54 +0000
commit1cf6e31f0d03bb3571cfe034f2d909591a0ae453 (patch)
treec89cb9d652377eb4517c497f6c4d4ea4ccf29b70 /src/cmd/compile
parentff2a57ba92b9ecc9315c992b332279d0428c36d7 (diff)
downloadgo-1cf6e31f0d03bb3571cfe034f2d909591a0ae453.tar.xz
cmd/compile: add basic wasmexport support
This CL adds a compiler directive go:wasmexport, which applies to a Go function and makes it an exported function of the Wasm module being built, so it can be called directly from the host. As proposed in #65199, parameter and result types are limited to 32-bit and 64-bit integers and floats, and there can be at most one result. As the Go and Wasm calling conventions are different, for a wasmexport function we generate a wrapper function does the ABI conversion at compile time. Currently this CL only adds basic support. In particular, - it only supports executable mode, i.e. the Go wasm module calls into the host via wasmimport, which then calls back to Go via wasmexport. Library (c-shared) mode is not implemented yet. - only supports wasip1, not js. - if the exported function unwinds stacks (goroutine switch, stack growth, etc.), it probably doesn't work. TODO: support stack unwinding, c-shared mode, js. For #65199. Change-Id: Id1777c2d44f7d51942c1caed3173c0a82f120cc4 Reviewed-on: https://go-review.googlesource.com/c/go/+/603055 Reviewed-by: Than McIntosh <thanm@golang.org> Reviewed-by: Randy Reddig <randy.reddig@fastly.com> Reviewed-by: David Chase <drchase@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/cmd/compile')
-rw-r--r--src/cmd/compile/internal/gc/compile.go5
-rw-r--r--src/cmd/compile/internal/ir/func.go8
-rw-r--r--src/cmd/compile/internal/ir/sizeof_test.go2
-rw-r--r--src/cmd/compile/internal/noder/linker.go5
-rw-r--r--src/cmd/compile/internal/noder/noder.go27
-rw-r--r--src/cmd/compile/internal/noder/reader.go17
-rw-r--r--src/cmd/compile/internal/noder/writer.go13
-rw-r--r--src/cmd/compile/internal/ssagen/abi.go76
8 files changed, 138 insertions, 15 deletions
diff --git a/src/cmd/compile/internal/gc/compile.go b/src/cmd/compile/internal/gc/compile.go
index 81a6023e47..5ade700d46 100644
--- a/src/cmd/compile/internal/gc/compile.go
+++ b/src/cmd/compile/internal/gc/compile.go
@@ -105,6 +105,11 @@ func prepareFunc(fn *ir.Func) {
// Calculate parameter offsets.
types.CalcSize(fn.Type())
+ // Generate wrappers between Go ABI and Wasm ABI, for a wasmexport
+ // function.
+ // Must be done after InitLSym and CalcSize.
+ ssagen.GenWasmExportWrapper(fn)
+
ir.CurFunc = fn
walk.Walk(fn)
ir.CurFunc = nil // enforce no further uses of CurFunc
diff --git a/src/cmd/compile/internal/ir/func.go b/src/cmd/compile/internal/ir/func.go
index e005ef7a7f..0675150b2d 100644
--- a/src/cmd/compile/internal/ir/func.go
+++ b/src/cmd/compile/internal/ir/func.go
@@ -142,6 +142,9 @@ type Func struct {
// WasmImport is used by the //go:wasmimport directive to store info about
// a WebAssembly function import.
WasmImport *WasmImport
+ // WasmExport is used by the //go:wasmexport directive to store info about
+ // a WebAssembly function import.
+ WasmExport *WasmExport
}
// WasmImport stores metadata associated with the //go:wasmimport pragma.
@@ -150,6 +153,11 @@ type WasmImport struct {
Name string
}
+// WasmExport stores metadata associated with the //go:wasmexport pragma.
+type WasmExport struct {
+ Name string
+}
+
// NewFunc returns a new Func with the given name and type.
//
// fpos is the position of the "func" token, and npos is the position
diff --git a/src/cmd/compile/internal/ir/sizeof_test.go b/src/cmd/compile/internal/ir/sizeof_test.go
index 68d2865595..6331cceb4a 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{}, 176, 296},
+ {Func{}, 180, 304},
{Name{}, 96, 168},
}
diff --git a/src/cmd/compile/internal/noder/linker.go b/src/cmd/compile/internal/noder/linker.go
index f5667f57ab..486013c7df 100644
--- a/src/cmd/compile/internal/noder/linker.go
+++ b/src/cmd/compile/internal/noder/linker.go
@@ -278,6 +278,11 @@ func (l *linker) relocFuncExt(w *pkgbits.Encoder, name *ir.Name) {
w.String("")
w.String("")
}
+ if name.Func.WasmExport != nil {
+ w.String(name.Func.WasmExport.Name)
+ } else {
+ w.String("")
+ }
}
// Relocated extension data.
diff --git a/src/cmd/compile/internal/noder/noder.go b/src/cmd/compile/internal/noder/noder.go
index 1652dc6618..7905c374c5 100644
--- a/src/cmd/compile/internal/noder/noder.go
+++ b/src/cmd/compile/internal/noder/noder.go
@@ -171,6 +171,7 @@ type pragmas struct {
Pos []pragmaPos // position of each individual flag
Embeds []pragmaEmbed
WasmImport *WasmImport
+ WasmExport *WasmExport
}
// WasmImport stores metadata associated with the //go:wasmimport pragma
@@ -180,6 +181,12 @@ type WasmImport struct {
Name string
}
+// WasmExport stores metadata associated with the //go:wasmexport pragma
+type WasmExport struct {
+ Pos syntax.Pos
+ Name string
+}
+
type pragmaPos struct {
Flag ir.PragmaFlag
Pos syntax.Pos
@@ -204,6 +211,9 @@ func (p *noder) checkUnusedDuringParse(pragma *pragmas) {
if pragma.WasmImport != nil {
p.error(syntax.Error{Pos: pragma.WasmImport.Pos, Msg: "misplaced go:wasmimport directive"})
}
+ if pragma.WasmExport != nil {
+ p.error(syntax.Error{Pos: pragma.WasmExport.Pos, Msg: "misplaced go:wasmexport directive"})
+ }
}
// pragma is called concurrently if files are parsed concurrently.
@@ -246,6 +256,23 @@ func (p *noder) pragma(pos syntax.Pos, blankLine bool, text string, old syntax.P
Name: f[2],
}
}
+
+ case strings.HasPrefix(text, "go:wasmexport "):
+ f := strings.Fields(text)
+ if len(f) != 2 {
+ // TODO: maybe make the name optional? It was once mentioned on proposal 65199.
+ p.error(syntax.Error{Pos: pos, Msg: "usage: //go:wasmexport exportname"})
+ break
+ }
+
+ if buildcfg.GOARCH == "wasm" {
+ // Only actually use them if we're compiling to WASM though.
+ pragma.WasmExport = &WasmExport{
+ Pos: pos,
+ Name: f[1],
+ }
+ }
+
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 ff44adedb4..1dd2e09b0d 100644
--- a/src/cmd/compile/internal/noder/reader.go
+++ b/src/cmd/compile/internal/noder/reader.go
@@ -1132,15 +1132,22 @@ func (r *reader) funcExt(name *ir.Name, method *types.Sym) {
r.linkname(name)
if buildcfg.GOARCH == "wasm" {
- xmod := r.String()
- xname := r.String()
+ importmod := r.String()
+ importname := r.String()
+ exportname := r.String()
- if xmod != "" && xname != "" {
+ if importmod != "" && importname != "" {
fn.WasmImport = &ir.WasmImport{
- Module: xmod,
- Name: xname,
+ Module: importmod,
+ Name: importname,
}
}
+ if exportname != "" {
+ if method != nil {
+ base.ErrorfAt(fn.Pos(), 0, "cannot use //go:wasmexport on a method")
+ }
+ fn.WasmExport = &ir.WasmExport{Name: exportname}
+ }
}
if r.Bool() {
diff --git a/src/cmd/compile/internal/noder/writer.go b/src/cmd/compile/internal/noder/writer.go
index c1560941b8..9f862f9a4c 100644
--- a/src/cmd/compile/internal/noder/writer.go
+++ b/src/cmd/compile/internal/noder/writer.go
@@ -1050,6 +1050,7 @@ func (w *writer) funcExt(obj *types2.Func) {
w.p.errorf(decl, "go:nosplit and go:systemstack cannot be combined")
}
wi := asWasmImport(decl.Pragma)
+ we := asWasmExport(decl.Pragma)
if decl.Body != nil {
if pragma&ir.Noescape != 0 {
@@ -1104,6 +1105,11 @@ func (w *writer) funcExt(obj *types2.Func) {
w.String("")
w.String("")
}
+ if we != nil {
+ w.String(we.Name)
+ } else {
+ w.String("")
+ }
}
w.Bool(false) // stub extension
@@ -3011,6 +3017,13 @@ func asWasmImport(p syntax.Pragma) *WasmImport {
return p.(*pragmas).WasmImport
}
+func asWasmExport(p syntax.Pragma) *WasmExport {
+ if p == nil {
+ return nil
+ }
+ return p.(*pragmas).WasmExport
+}
+
// isPtrTo reports whether from is the type *to.
func isPtrTo(from, to types2.Type) bool {
ptr, ok := types2.Unalias(from).(*types2.Pointer)
diff --git a/src/cmd/compile/internal/ssagen/abi.go b/src/cmd/compile/internal/ssagen/abi.go
index d5ae3b1793..0c42c84312 100644
--- a/src/cmd/compile/internal/ssagen/abi.go
+++ b/src/cmd/compile/internal/ssagen/abi.go
@@ -19,6 +19,8 @@ import (
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/obj/wasm"
+
+ rtabi "internal/abi"
)
// SymABIs records information provided by the assembler about symbol
@@ -347,7 +349,7 @@ func CreateWasmImportWrapper(fn *ir.Func) bool {
ir.InitLSym(fn, true)
- setupWasmABI(fn)
+ setupWasmImport(fn)
pp := objw.NewProgs(fn, 0)
defer pp.Free()
@@ -360,7 +362,49 @@ func CreateWasmImportWrapper(fn *ir.Func) bool {
return true
}
-func paramsToWasmFields(f *ir.Func, result *abi.ABIParamResultInfo, abiParams []abi.ABIParamAssignment) []obj.WasmField {
+func GenWasmExportWrapper(wrapped *ir.Func) {
+ if wrapped.WasmExport == nil {
+ return
+ }
+ if buildcfg.GOARCH != "wasm" {
+ base.FatalfAt(wrapped.Pos(), "GenWasmExportWrapper call not supported on %s: func was %v", buildcfg.GOARCH, wrapped)
+ }
+
+ pos := base.AutogeneratedPos
+ sym := &types.Sym{
+ Name: wrapped.WasmExport.Name,
+ Linkname: wrapped.WasmExport.Name,
+ }
+ ft := wrapped.Nname.Type()
+ fn := ir.NewFunc(pos, pos, sym, types.NewSignature(nil,
+ typecheck.NewFuncParams(ft.Params()),
+ typecheck.NewFuncParams(ft.Results())))
+ fn.ABI = obj.ABI0 // actually wasm ABI
+ // The wrapper function has a special calling convention that
+ // morestack currently doesn't handle. For now we require that
+ // the argument size fits in StackSmall, which we know we have
+ // on stack, so we don't need to split stack.
+ // cmd/internal/obj/wasm supports only 16 argument "registers"
+ // anyway.
+ if ft.ArgWidth() > rtabi.StackSmall {
+ base.ErrorfAt(wrapped.Pos(), 0, "wasmexport function argument too large")
+ }
+ fn.Pragma |= ir.Nosplit
+
+ ir.InitLSym(fn, true)
+
+ setupWasmExport(fn, wrapped)
+
+ pp := objw.NewProgs(fn, 0)
+ defer pp.Free()
+ pp.Text.To.Type = obj.TYPE_TEXTSIZE
+ pp.Text.To.Val = int32(0)
+ pp.Text.To.Offset = types.RoundUp(ft.ArgWidth(), int64(types.RegSize))
+ pp.Flush()
+ // Actual code geneneration is in cmd/internal/obj/wasm.
+}
+
+func paramsToWasmFields(f *ir.Func, pragma string, result *abi.ABIParamResultInfo, abiParams []abi.ABIParamAssignment) []obj.WasmField {
wfs := make([]obj.WasmField, len(abiParams))
for i, p := range abiParams {
t := p.Type
@@ -376,16 +420,16 @@ func paramsToWasmFields(f *ir.Func, result *abi.ABIParamResultInfo, abiParams []
case types.TUNSAFEPTR:
wfs[i].Type = obj.WasmPtr
default:
- base.ErrorfAt(f.Pos(), 0, "go:wasmimport %s %s: unsupported parameter type %s", f.WasmImport.Module, f.WasmImport.Name, t.String())
+ base.ErrorfAt(f.Pos(), 0, "%s: unsupported parameter type %s", pragma, t.String())
}
wfs[i].Offset = p.FrameOffset(result)
}
return wfs
}
-func resultsToWasmFields(f *ir.Func, result *abi.ABIParamResultInfo, abiParams []abi.ABIParamAssignment) []obj.WasmField {
+func resultsToWasmFields(f *ir.Func, pragma string, result *abi.ABIParamResultInfo, abiParams []abi.ABIParamAssignment) []obj.WasmField {
if len(abiParams) > 1 {
- base.ErrorfAt(f.Pos(), 0, "go:wasmimport %s %s: too many return values", f.WasmImport.Module, f.WasmImport.Name)
+ base.ErrorfAt(f.Pos(), 0, "%s: too many return values", pragma)
return nil
}
wfs := make([]obj.WasmField, len(abiParams))
@@ -408,8 +452,9 @@ func resultsToWasmFields(f *ir.Func, result *abi.ABIParamResultInfo, abiParams [
return wfs
}
-// setupWasmABI calculates the params and results in terms of WebAssembly values for the given function.
-func setupWasmABI(f *ir.Func) {
+// setupWasmImport calculates the params and results in terms of WebAssembly values for the given function,
+// and sets up the wasmimport metadata.
+func setupWasmImport(f *ir.Func) {
wi := obj.WasmImport{
Module: f.WasmImport.Module,
Name: f.WasmImport.Name,
@@ -438,8 +483,21 @@ func setupWasmABI(f *ir.Func) {
// (import "a_module" "add" (func (param i32 i32) (result i32)))
abiConfig := AbiForBodylessFuncStackMap(f)
abiInfo := abiConfig.ABIAnalyzeFuncType(f.Type())
- wi.Params = paramsToWasmFields(f, abiInfo, abiInfo.InParams())
- wi.Results = resultsToWasmFields(f, abiInfo, abiInfo.OutParams())
+ wi.Params = paramsToWasmFields(f, "go:wasmimport", abiInfo, abiInfo.InParams())
+ wi.Results = resultsToWasmFields(f, "go:wasmimport", abiInfo, abiInfo.OutParams())
}
f.LSym.Func().WasmImport = &wi
}
+
+// setupWasmExport calculates the params and results in terms of WebAssembly values for the given function,
+// and sets up the wasmexport metadata.
+func setupWasmExport(f, wrapped *ir.Func) {
+ we := obj.WasmExport{
+ WrappedSym: wrapped.LSym,
+ }
+ abiConfig := AbiForBodylessFuncStackMap(wrapped)
+ abiInfo := abiConfig.ABIAnalyzeFuncType(wrapped.Type())
+ we.Params = paramsToWasmFields(wrapped, "go:wasmexport", abiInfo, abiInfo.InParams())
+ we.Results = resultsToWasmFields(wrapped, "go:wasmexport", abiInfo, abiInfo.OutParams())
+ f.LSym.Func().WasmExport = &we
+}