aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/link
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/link
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/link')
-rw-r--r--src/cmd/link/internal/loader/loader.go23
-rw-r--r--src/cmd/link/internal/wasm/asm.go118
2 files changed, 126 insertions, 15 deletions
diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go
index e3ee819a9d..fa8c0c6b20 100644
--- a/src/cmd/link/internal/loader/loader.go
+++ b/src/cmd/link/internal/loader/loader.go
@@ -1618,6 +1618,29 @@ func (l *Loader) Aux(i Sym, j int) Aux {
return Aux{r.Aux(li, j), r, l}
}
+// WasmImportSym returns the auxiliary WebAssembly import symbol associated with
+// a given function symbol. The aux sym only exists for Go function stubs that
+// have been annotated with the //go:wasmimport directive. The aux sym
+// contains the information necessary for the linker to add a WebAssembly
+// import statement.
+// (https://webassembly.github.io/spec/core/syntax/modules.html#imports)
+func (l *Loader) WasmImportSym(fnSymIdx Sym) (Sym, bool) {
+ if l.SymType(fnSymIdx) != sym.STEXT {
+ log.Fatalf("error: non-function sym %d/%s t=%s passed to WasmImportSym", fnSymIdx, l.SymName(fnSymIdx), l.SymType(fnSymIdx).String())
+ }
+ r, li := l.toLocal(fnSymIdx)
+ auxs := r.Auxs(li)
+ for i := range auxs {
+ a := &auxs[i]
+ switch a.Type() {
+ case goobj.AuxWasmImport:
+ return l.resolve(r, a.Sym()), true
+ }
+ }
+
+ return 0, false
+}
+
// GetFuncDwarfAuxSyms collects and returns the auxiliary DWARF
// symbols associated with a given function symbol. Prior to the
// introduction of the loader, this was done purely using name
diff --git a/src/cmd/link/internal/wasm/asm.go b/src/cmd/link/internal/wasm/asm.go
index b5685701f2..30d0dc7ff2 100644
--- a/src/cmd/link/internal/wasm/asm.go
+++ b/src/cmd/link/internal/wasm/asm.go
@@ -6,10 +6,14 @@ package wasm
import (
"bytes"
+ "cmd/internal/obj"
+ "cmd/internal/obj/wasm"
"cmd/internal/objabi"
"cmd/link/internal/ld"
"cmd/link/internal/loader"
"cmd/link/internal/sym"
+ "encoding/binary"
+ "fmt"
"internal/buildcfg"
"io"
"regexp"
@@ -44,9 +48,10 @@ func gentext(ctxt *ld.Link, ldr *loader.Loader) {
}
type wasmFunc struct {
- Name string
- Type uint32
- Code []byte
+ Module string
+ Name string
+ Type uint32
+ Code []byte
}
type wasmFuncType struct {
@@ -54,6 +59,59 @@ type wasmFuncType struct {
Results []byte
}
+func readWasmImport(ldr *loader.Loader, s loader.Sym) obj.WasmImport {
+ reportError := func(err error) { panic(fmt.Sprintf("failed to read WASM import in sym %v: %v", s, err)) }
+
+ data := ldr.Data(s)
+
+ readUint32 := func() (v uint32) {
+ v = binary.LittleEndian.Uint32(data)
+ data = data[4:]
+ return
+ }
+
+ readUint64 := func() (v uint64) {
+ v = binary.LittleEndian.Uint64(data)
+ data = data[8:]
+ return
+ }
+
+ readByte := func() byte {
+ if len(data) == 0 {
+ reportError(io.EOF)
+ }
+
+ b := data[0]
+ data = data[1:]
+ return b
+ }
+
+ readString := func() string {
+ n := readUint32()
+
+ s := string(data[:n])
+
+ data = data[n:]
+
+ return s
+ }
+
+ var wi obj.WasmImport
+ wi.Module = readString()
+ wi.Name = readString()
+ wi.Params = make([]obj.WasmField, readUint32())
+ for i := range wi.Params {
+ wi.Params[i].Type = obj.WasmFieldType(readByte())
+ wi.Params[i].Offset = int64(readUint64())
+ }
+ wi.Results = make([]obj.WasmField, readUint32())
+ for i := range wi.Results {
+ wi.Results[i].Type = obj.WasmFieldType(readByte())
+ wi.Results[i].Offset = int64(readUint64())
+ }
+ return wi
+}
+
var wasmFuncTypes = map[string]*wasmFuncType{
"_rt0_wasm_js": {Params: []byte{}}, //
"wasm_export_run": {Params: []byte{I32, I32}}, // argc, argv
@@ -136,23 +194,30 @@ func asmb2(ctxt *ld.Link, ldr *loader.Loader) {
}
// collect host imports (functions that get imported from the WebAssembly host, usually JavaScript)
- hostImports := []*wasmFunc{
- {
- Name: "debug",
- Type: lookupType(&wasmFuncType{Params: []byte{I32}}, &types),
- },
- }
+ // we store the import index of each imported function, so the R_WASMIMPORT relocation
+ // can write the correct index after a "call" instruction
+ // these are added as import statements to the top of the WebAssembly binary
+ var hostImports []*wasmFunc
hostImportMap := make(map[loader.Sym]int64)
for _, fn := range ctxt.Textp {
relocs := ldr.Relocs(fn)
for ri := 0; ri < relocs.Count(); ri++ {
r := relocs.At(ri)
if r.Type() == objabi.R_WASMIMPORT {
- hostImportMap[r.Sym()] = int64(len(hostImports))
- hostImports = append(hostImports, &wasmFunc{
- Name: ldr.SymName(r.Sym()),
- Type: lookupType(&wasmFuncType{Params: []byte{I32}}, &types),
- })
+ if lsym, ok := ldr.WasmImportSym(fn); ok {
+ wi := readWasmImport(ldr, lsym)
+ hostImportMap[fn] = int64(len(hostImports))
+ hostImports = append(hostImports, &wasmFunc{
+ Module: wi.Module,
+ Name: wi.Name,
+ Type: lookupType(&wasmFuncType{
+ Params: fieldsToTypes(wi.Params),
+ Results: fieldsToTypes(wi.Results),
+ }, &types),
+ })
+ } else {
+ panic(fmt.Sprintf("missing wasm symbol for %s", ldr.SymName(r.Sym())))
+ }
}
}
}
@@ -288,7 +353,11 @@ func writeImportSec(ctxt *ld.Link, hostImports []*wasmFunc) {
writeUleb128(ctxt.Out, uint64(len(hostImports))) // number of imports
for _, fn := range hostImports {
- writeName(ctxt.Out, "go") // provided by the import object in wasm_exec.js
+ if fn.Module != "" {
+ writeName(ctxt.Out, fn.Module)
+ } else {
+ writeName(ctxt.Out, wasm.GojsModule) // provided by the import object in wasm_exec.js
+ }
writeName(ctxt.Out, fn.Name)
ctxt.Out.WriteByte(0x00) // func import
writeUleb128(ctxt.Out, uint64(fn.Type))
@@ -610,3 +679,22 @@ func writeSleb128(w io.ByteWriter, v int64) {
w.WriteByte(c)
}
}
+
+func fieldsToTypes(fields []obj.WasmField) []byte {
+ b := make([]byte, len(fields))
+ for i, f := range fields {
+ switch f.Type {
+ case obj.WasmI32, obj.WasmPtr:
+ b[i] = I32
+ case obj.WasmI64:
+ b[i] = I64
+ case obj.WasmF32:
+ b[i] = F32
+ case obj.WasmF64:
+ b[i] = F64
+ default:
+ panic(fmt.Sprintf("fieldsToTypes: unknown field type: %d", f.Type))
+ }
+ }
+ return b
+}