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/link | |
| 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/link')
| -rw-r--r-- | src/cmd/link/internal/loader/loader.go | 23 | ||||
| -rw-r--r-- | src/cmd/link/internal/wasm/asm.go | 118 |
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 +} |
