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>
This commit is contained in:
Evan Phoenix 2023-01-22 15:30:59 -08:00 committed by Gopher Robot
parent af9f21289f
commit 02411bcd7c
29 changed files with 585 additions and 145 deletions

View file

@ -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
}