Skip to content

Instantly share code, notes, and snippets.

@evanphx
Created July 6, 2021 05:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save evanphx/da7193a9301e2b16e32cb484ee5243e4 to your computer and use it in GitHub Desktop.
Save evanphx/da7193a9301e2b16e32cb484ee5243e4 to your computer and use it in GitHub Desktop.
Patch to rebase wasm import support
diff --git a/misc/wasm/wasm_exec.js b/misc/wasm/wasm_exec.js
index 231185a123..13030eea00 100644
--- a/misc/wasm/wasm_exec.js
+++ b/misc/wasm/wasm_exec.js
@@ -256,6 +256,9 @@
const timeOrigin = Date.now() - performance.now();
this.importObject = {
+ _gotest: {
+ add: (a, b) => a + b,
+ },
go: {
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
diff --git a/src/cmd/compile/internal/gc/compile.go b/src/cmd/compile/internal/gc/compile.go
index 00504451a8..210b84db5d 100644
--- a/src/cmd/compile/internal/gc/compile.go
+++ b/src/cmd/compile/internal/gc/compile.go
@@ -42,6 +42,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.
ssagen.InitLSym(fn, false)
diff --git a/src/cmd/compile/internal/ir/func.go b/src/cmd/compile/internal/ir/func.go
index 20fe965711..7ba44bd5c2 100644
--- a/src/cmd/compile/internal/ir/func.go
+++ b/src/cmd/compile/internal/ir/func.go
@@ -6,11 +6,20 @@ package ir
import (
"cmd/compile/internal/base"
+ "cmd/compile/internal/syntax"
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/src"
)
+type WasmInfo struct {
+ // Used by the //go:wasmimport directive to store info
+ // about a WebAssembly import. These are eventually assembled
+ // into a single WasmImport struct in $GOROOT/src/cmd/compile/ssagen/abi.go
+ Wasmimport *syntax.Wasmimport
+ Wasmfields *obj.FuncWasmfields
+}
+
// A Func corresponds to a single function in a Go program
// (and vice versa: each function is denoted by exactly one *Func).
//
@@ -132,6 +141,8 @@ type Func struct {
// function for go:nowritebarrierrec analysis. Only filled in
// if nowritebarrierrecCheck != nil.
NWBRCalls *[]SymAndPos
+
+ WasmInfo *WasmInfo
}
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 a4421fcf53..72b6320261 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{}, 192, 328},
+ {Func{}, 196, 336},
{Name{}, 112, 200},
}
diff --git a/src/cmd/compile/internal/noder/noder.go b/src/cmd/compile/internal/noder/noder.go
index 5fcad096c2..df2a52314e 100644
--- a/src/cmd/compile/internal/noder/noder.go
+++ b/src/cmd/compile/internal/noder/noder.go
@@ -571,12 +571,28 @@ func (p *noder) funcDecl(fun *syntax.FuncDecl) ir.Node {
f.Nname.Defn = f
f.Nname.Ntype = t
+ isWasmImport := false
if pragma, ok := fun.Pragma.(*pragmas); ok {
f.Pragma = pragma.Flag & funcPragmas
if pragma.Flag&ir.Systemstack != 0 && pragma.Flag&ir.Nosplit != 0 {
base.ErrorfAt(f.Pos(), "go:nosplit and go:systemstack cannot be combined")
}
pragma.Flag &^= funcPragmas
+ if pragma.Wasmimport != nil {
+ isWasmImport = true
+ // While functions annotated with //go:wasmimport are bodyless,
+ // the compiler generates a WebAssembly body for them. However,
+ // the body will never grow the Go stack.
+ f.Pragma |= ir.Nosplit
+
+ wi := f.WasmInfo
+ if wi == nil {
+ wi = &ir.WasmInfo{}
+ f.WasmInfo = wi
+ }
+
+ wi.Wasmimport = pragma.Wasmimport
+ }
p.checkUnused(pragma)
}
@@ -590,6 +606,9 @@ func (p *noder) funcDecl(fun *syntax.FuncDecl) ir.Node {
if f.Pragma&ir.Noescape != 0 {
base.ErrorfAt(f.Pos(), "can only use //go:noescape with external func implementations")
}
+ if isWasmImport {
+ base.ErrorfAt(f.Pos(), "cannot have function body with //go:wasmimport")
+ }
} else {
if base.Flag.Complete || strings.HasPrefix(ir.FuncName(f), "init.") {
// Linknamed functions are allowed to have no body. Hopefully
@@ -601,7 +620,7 @@ func (p *noder) funcDecl(fun *syntax.FuncDecl) ir.Node {
break
}
}
- if !isLinknamed {
+ if !isLinknamed && !isWasmImport {
base.ErrorfAt(f.Pos(), "missing function body")
}
}
@@ -1540,9 +1559,10 @@ 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 *syntax.Wasmimport
}
type pragmaPos struct {
@@ -1606,6 +1626,20 @@ 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 module_name import_name"})
+ }
+ 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"})
+ }
+ module := f[1]
+ name := f[2]
+ pragma.Wasmimport = &syntax.Wasmimport{
+ Module: module,
+ Name: name,
+ }
case strings.HasPrefix(text, "go:linkname "):
f := strings.Fields(text)
if !(2 <= len(f) && len(f) <= 3) {
diff --git a/src/cmd/compile/internal/ssagen/abi.go b/src/cmd/compile/internal/ssagen/abi.go
index e460adaf95..1235c948db 100644
--- a/src/cmd/compile/internal/ssagen/abi.go
+++ b/src/cmd/compile/internal/ssagen/abi.go
@@ -14,6 +14,7 @@ import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
+ "cmd/compile/internal/objw"
"cmd/compile/internal/staticdata"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
@@ -224,6 +225,77 @@ func (s *SymABIs) GenABIWrappers() {
}
}
+// CreateWasmImportWrapper is called when considering each function during
+// compilation. It detects if the function is a declaration point for a
+// function that will be imported from the wasm runtime.
+func CreateWasmImportWrapper(fn *ir.Func) bool {
+ info := fn.WasmInfo
+ if info == nil {
+ return false
+ }
+
+ wi := info.Wasmimport
+ if wi == nil || buildcfg.GOARCH != "wasm" {
+ return false
+ }
+
+ // Taken from `enqueueFunc` in "gc" package
+ // Specifically, the `len(fn.Body) == 0` if statement
+ types.CalcSize(fn.Type())
+ a := AbiForBodylessFuncStackMap(fn)
+ a.ABIAnalyze(fn.Type(), false)
+
+ if wi.Module != "go" {
+ info := fn.WasmInfo
+ if info == nil {
+ info = &ir.WasmInfo{}
+ fn.WasmInfo = info
+ }
+
+ // See `setupTextLSym`
+ info.Wasmfields = &obj.FuncWasmfields{
+ Params: toWasmFields(fn.Type().Params().FieldSlice()),
+ Results: toWasmFields(fn.Type().Results().FieldSlice()),
+ }
+ }
+ InitLSym(fn, true)
+ // Go programs cannot call imported WASM functions directly.
+ // For each WASM import, the compiler generates a wrapper function, which
+ // follows the Go calling convention. All calls to the import go through the wrapper.
+ // Create an empty function, the body is generated in $GOROOT/src/cmd/internal/obj/wasm/wasmobj.go
+ pp := objw.NewProgs(fn, 0)
+ defer pp.Free()
+ pp.Text.To.Type = obj.TYPE_TEXTSIZE
+ pp.Text.To.Val = int32(types.Rnd(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(fields []*types.Field) []obj.WasmField {
+ wfs := make([]obj.WasmField, len(fields))
+ for i, f := range fields {
+ t := f.Type
+ switch {
+ case t.IsInteger() && t.Width == 4:
+ wfs[i].Type = obj.WasmI32
+ case t.IsInteger() && t.Width == 8:
+ wfs[i].Type = obj.WasmI64
+ case t.IsFloat() && t.Width == 4:
+ wfs[i].Type = obj.WasmF32
+ case t.IsFloat() && t.Width == 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 = f.Offset
+ }
+ return wfs
+}
+
// InitLSym defines f's obj.LSym and initializes it based on the
// properties of f. This includes setting the symbol flags and ABI and
// creating and initializing related DWARF symbols.
@@ -453,4 +525,53 @@ func setupTextLSym(f *ir.Func, flag int) {
}
base.Ctxt.InitTextSym(f.LSym, flag)
+
+ // Handle any wasm specific bits last. This info is populated on
+ // funcs regardless of the target ARCH, so we just bail out early
+ // when the target arch isn't wasm.
+ if buildcfg.GOARCH != "wasm" {
+ return
+ }
+
+ info := f.WasmInfo
+ if info == nil {
+ return
+ }
+
+ wi := info.Wasmimport
+ if wi != nil {
+ return
+ }
+
+ if wi.Module == "go" {
+ // Functions that are imported from the "go" module use a special ABI
+ // that just accepts the stack ptr.
+ // Example:
+ // ```
+ // go:wasmimport go add
+ // func importedAdd(a, b uint) uint
+ // ```
+ // will roughly become
+ // (import "go" "add" (func (param i32)))
+ f.LSym.Func().WasmImport = &obj.WasmImport{
+ Module: wi.Module,
+ Name: wi.Name,
+ 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)))
+ f.LSym.Func().WasmImport = &obj.WasmImport{
+ Module: wi.Module,
+ Name: wi.Name,
+ Params: info.Wasmfields.Params,
+ Results: info.Wasmfields.Results,
+ }
+ }
}
diff --git a/src/cmd/compile/internal/syntax/syntax.go b/src/cmd/compile/internal/syntax/syntax.go
index f3d4c09ed5..1ee53dc98a 100644
--- a/src/cmd/compile/internal/syntax/syntax.go
+++ b/src/cmd/compile/internal/syntax/syntax.go
@@ -53,6 +53,12 @@ type Pragma interface{}
// Blank specifies whether the line is blank before the pragma.
type PragmaHandler func(pos Pos, blank bool, text string, current Pragma) Pragma
+// Wasmimport stores metadata associated with the //go:wasmimport pragma
+type Wasmimport struct {
+ Module string
+ Name string
+}
+
// Parse parses a single Go source file from src and returns the corresponding
// syntax tree. If there are errors, Parse will return the first error found,
// and a possibly partially constructed syntax tree, or nil.
diff --git a/src/cmd/internal/goobj/objfile.go b/src/cmd/internal/goobj/objfile.go
index e2858bd57d..9a0fabfe15 100644
--- a/src/cmd/internal/goobj/objfile.go
+++ b/src/cmd/internal/goobj/objfile.go
@@ -436,6 +436,7 @@ const (
AuxPcline
AuxPcinline
AuxPcdata
+ AuxWasmImport
)
func (a *Aux) Type() uint8 { return a[0] }
diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go
index 28626e6e03..53aaf484d2 100644
--- a/src/cmd/internal/obj/link.go
+++ b/src/cmd/internal/obj/link.go
@@ -37,6 +37,7 @@ import (
"cmd/internal/objabi"
"cmd/internal/src"
"cmd/internal/sys"
+ "encoding/binary"
"fmt"
"sync"
"sync/atomic"
@@ -487,7 +488,9 @@ type FuncInfo struct {
OpenCodedDeferInfo *LSym
ArgInfo *LSym // argument info for traceback
- FuncInfoSym *LSym
+ FuncInfoSym *LSym
+ WasmImportSym *LSym
+ WasmImport *WasmImport
}
// NewFuncInfo allocates and returns a FuncInfo for LSym.
@@ -537,6 +540,64 @@ func (s *LSym) File() *FileInfo {
return f
}
+type WasmImport struct {
+ Module string
+ Name string
+ Params []WasmField
+ Results []WasmField
+}
+
+func (wi *WasmImport) WriteToSymContent(ctxt *Link, sym *LSym) {
+ var b [8]byte
+ writeByte := func(x byte) {
+ sym.WriteBytes(ctxt, sym.Size, []byte{x})
+ }
+ writeUint32 := func(x uint32) {
+ binary.LittleEndian.PutUint32(b[:], x)
+ sym.WriteBytes(ctxt, sym.Size, b[:4])
+ }
+ writeInt64 := func(x int64) {
+ binary.LittleEndian.PutUint64(b[:], uint64(x))
+ sym.WriteBytes(ctxt, sym.Size, b[:])
+ }
+ writeString := func(s string) {
+ writeUint32(uint32(len(s)))
+ sym.WriteString(ctxt, sym.Size, len(s), s)
+ }
+ writeString(wi.Module)
+ writeString(wi.Name)
+ writeUint32(uint32(len(wi.Params)))
+ for _, f := range wi.Params {
+ writeByte(byte(f.Type))
+ writeInt64(f.Offset)
+ }
+ writeUint32(uint32(len(wi.Results)))
+ for _, f := range wi.Results {
+ writeByte(byte(f.Type))
+ writeInt64(f.Offset)
+ }
+}
+
+type FuncWasmfields struct {
+ Results []WasmField
+ Params []WasmField
+}
+
+type WasmField struct {
+ Type WasmFieldType
+ Offset int64
+}
+
+type WasmFieldType byte
+
+const (
+ WasmI32 WasmFieldType = iota
+ WasmI64
+ WasmF32
+ WasmF64
+ WasmPtr
+)
+
type InlMark struct {
// When unwinding from an instruction in an inlined body, mark
// where we should unwind to.
diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go
index 24fb5a19de..3bdb4de592 100644
--- a/src/cmd/internal/obj/objfile.go
+++ b/src/cmd/internal/obj/objfile.go
@@ -549,7 +549,12 @@ func (w *writer) Aux(s *LSym) {
for _, pcSym := range fn.Pcln.Pcdata {
w.aux1(goobj.AuxPcdata, pcSym)
}
-
+ if fn.WasmImportSym != nil {
+ if fn.WasmImportSym.Size == 0 {
+ panic("wasmimport aux sym must have non-zero size")
+ }
+ w.aux1(goobj.AuxWasmImport, fn.WasmImportSym)
+ }
}
}
@@ -644,6 +649,12 @@ func nAuxSym(s *LSym) int {
n++
}
n += len(fn.Pcln.Pcdata)
+ if fn.WasmImport != nil {
+ if fn.WasmImportSym == nil || fn.WasmImportSym.Size == 0 {
+ panic("wasmimport aux sym must exist and have non-zero size")
+ }
+ n++
+ }
}
return n
}
@@ -720,7 +731,7 @@ func genFuncInfoSyms(ctxt *Link) {
fn.FuncInfoSym = isym
b.Reset()
- dwsyms := []*LSym{fn.dwarfRangesSym, fn.dwarfLocSym, fn.dwarfDebugLinesSym, fn.dwarfInfoSym}
+ dwsyms := []*LSym{fn.dwarfRangesSym, fn.dwarfLocSym, fn.dwarfDebugLinesSym, fn.dwarfInfoSym, fn.WasmImportSym}
for _, s := range dwsyms {
if s == nil || s.Size == 0 {
continue
diff --git a/src/cmd/internal/obj/plist.go b/src/cmd/internal/obj/plist.go
index 6beb4dd94c..d788c68751 100644
--- a/src/cmd/internal/obj/plist.go
+++ b/src/cmd/internal/obj/plist.go
@@ -78,7 +78,7 @@ func Flushplist(ctxt *Link, plist *Plist, newprog ProgAlloc, myimportpath string
// Add reference to Go arguments for assembly functions without them.
if ctxt.IsAsm {
for _, s := range text {
- if !strings.HasPrefix(s.Name, "\"\".") {
+ if !strings.HasPrefix(s.Name, "\"\".") || s.Func().WasmImport != nil {
continue
}
// The current args_stackmap generation in the compiler assumes
diff --git a/src/cmd/internal/obj/sym.go b/src/cmd/internal/obj/sym.go
index 9e8b4dd790..78b06800be 100644
--- a/src/cmd/internal/obj/sym.go
+++ b/src/cmd/internal/obj/sym.go
@@ -392,7 +392,7 @@ func (ctxt *Link) traverseFuncAux(flag traverseFlag, fsym *LSym, fn func(parent
}
}
- dwsyms := []*LSym{fninfo.dwarfRangesSym, fninfo.dwarfLocSym, fninfo.dwarfDebugLinesSym, fninfo.dwarfInfoSym}
+ dwsyms := []*LSym{fninfo.dwarfRangesSym, fninfo.dwarfLocSym, fninfo.dwarfDebugLinesSym, fninfo.dwarfInfoSym, fninfo.WasmImportSym}
for _, dws := range dwsyms {
if dws == nil || dws.Size == 0 {
continue
diff --git a/src/cmd/internal/obj/wasm/a.out.go b/src/cmd/internal/obj/wasm/a.out.go
index 72ecaa9286..5840ee84bd 100644
--- a/src/cmd/internal/obj/wasm/a.out.go
+++ b/src/cmd/internal/obj/wasm/a.out.go
@@ -18,8 +18,7 @@ const (
* wasm
*/
const (
- ACallImport = obj.ABaseWasm + obj.A_ARCHSPECIFIC + iota
- AGet
+ AGet = obj.ABaseWasm + obj.A_ARCHSPECIFIC + iota
ASet
ATee
ANot // alias for I32Eqz
diff --git a/src/cmd/internal/obj/wasm/anames.go b/src/cmd/internal/obj/wasm/anames.go
index 94123849ee..676552285e 100644
--- a/src/cmd/internal/obj/wasm/anames.go
+++ b/src/cmd/internal/obj/wasm/anames.go
@@ -5,8 +5,7 @@ package wasm
import "cmd/internal/obj"
var Anames = []string{
- obj.A_ARCHSPECIFIC: "CallImport",
- "Get",
+ obj.A_ARCHSPECIFIC: "Get",
"Set",
"Tee",
"Not",
diff --git a/src/cmd/internal/obj/wasm/wasmobj.go b/src/cmd/internal/obj/wasm/wasmobj.go
index ceeae7a257..d79a72c6db 100644
--- a/src/cmd/internal/obj/wasm/wasmobj.go
+++ b/src/cmd/internal/obj/wasm/wasmobj.go
@@ -100,7 +100,6 @@ var unaryDst = map[obj.As]bool{
ATee: true,
ACall: true,
ACallIndirect: true,
- ACallImport: true,
ABr: true,
ABrIf: true,
ABrTable: true,
@@ -135,7 +134,7 @@ var (
const (
/* mark flags */
- WasmImport = 1 << 0
+ IsWasmImport = 1 << 0
)
func instinit(ctxt *obj.Link) {
@@ -149,6 +148,168 @@ func instinit(ctxt *obj.Link) {
jmpdefer = ctxt.LookupABI(`"".jmpdefer`, obj.ABI0)
}
+func setupImport(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc, wi *obj.WasmImport) {
+ appendp := func(p *obj.Prog, as obj.As, args ...obj.Addr) *obj.Prog {
+ if p.As != obj.ANOP {
+ p2 := obj.Appendp(p, newprog)
+ p2.Pc = p.Pc
+ p = p2
+ }
+ p.As = as
+ switch len(args) {
+ case 0:
+ p.From = obj.Addr{}
+ p.To = obj.Addr{}
+ case 1:
+ if unaryDst[as] {
+ p.From = obj.Addr{}
+ p.To = args[0]
+ } else {
+ p.From = args[0]
+ p.To = obj.Addr{}
+ }
+ case 2:
+ p.From = args[0]
+ p.To = args[1]
+ default:
+ panic("bad args")
+ }
+ return p
+ }
+
+ auxSym := &obj.LSym{}
+ wi.WriteToSymContent(ctxt, auxSym)
+ s.Func().WasmImportSym = auxSym
+ p := s.Func().Text
+ if p.Link != nil {
+ panic("wrapper functions for WASM imports should not have a body")
+ }
+ to := obj.Addr{
+ Type: obj.TYPE_MEM,
+ Name: obj.NAME_EXTERN,
+ Sym: s,
+ }
+ if wi.Module == "go" {
+ p = appendp(p, AGet, regAddr(REG_SP))
+ p = appendp(p, ACall, to)
+ p.Mark = IsWasmImport
+ } else {
+ if len(wi.Results) > 1 {
+ panic("invalid results type") // impossible until multi-value proposal has landed
+ }
+ if len(wi.Results) == 1 {
+ p = appendp(p, AGet, regAddr(REG_SP)) // address has to be before the value
+ }
+ for _, f := range wi.Params {
+ p = appendp(p, AGet, regAddr(REG_SP))
+ f.Offset += 8
+ switch f.Type {
+ case obj.WasmI32:
+ p = appendp(p, AI32Load, constAddr(f.Offset))
+ case obj.WasmI64:
+ p = appendp(p, AI64Load, constAddr(f.Offset))
+ case obj.WasmF32:
+ p = appendp(p, AF32Load, constAddr(f.Offset))
+ case obj.WasmF64:
+ p = appendp(p, AF64Load, constAddr(f.Offset))
+ case obj.WasmPtr:
+ p = appendp(p, AI64Load, constAddr(f.Offset))
+ p = appendp(p, AI32WrapI64)
+ default:
+ panic("bad param type")
+ }
+ }
+ p = appendp(p, ACall, to)
+ p.Mark = IsWasmImport
+ if len(wi.Results) == 1 {
+ f := wi.Results[0]
+ f.Offset += 8
+ switch f.Type {
+ case obj.WasmI32:
+ p = appendp(p, AI32Store, constAddr(f.Offset))
+ case obj.WasmI64:
+ p = appendp(p, AI64Store, constAddr(f.Offset))
+ case obj.WasmF32:
+ p = appendp(p, AF32Store, constAddr(f.Offset))
+ case obj.WasmF64:
+ p = appendp(p, AF64Store, constAddr(f.Offset))
+ case obj.WasmPtr:
+ p = appendp(p, AI64ExtendI32U)
+ p = appendp(p, AI64Store, constAddr(f.Offset))
+ default:
+ panic("bad result type")
+ }
+ }
+ }
+ p = appendp(p, obj.ARET)
+ auxSym = &obj.LSym{}
+ wi.WriteToSymContent(ctxt, auxSym)
+ s.Func().WasmImportSym = auxSym
+ p = s.Func().Text
+ if p.Link != nil {
+ panic("wrapper functions for WASM imports should not have a body")
+ }
+ to = obj.Addr{
+ Type: obj.TYPE_MEM,
+ Name: obj.NAME_EXTERN,
+ Sym: s,
+ }
+ if wi.Module == "go" {
+ p = appendp(p, AGet, regAddr(REG_SP))
+ p = appendp(p, ACall, to)
+ p.Mark = IsWasmImport
+ } else {
+ if len(wi.Results) > 1 {
+ panic("invalid results type") // impossible until multi-value proposal has landed
+ }
+ if len(wi.Results) == 1 {
+ p = appendp(p, AGet, regAddr(REG_SP)) // address has to be before the value
+ }
+ for _, f := range wi.Params {
+ p = appendp(p, AGet, regAddr(REG_SP))
+ f.Offset += 8
+ switch f.Type {
+ case obj.WasmI32:
+ p = appendp(p, AI32Load, constAddr(f.Offset))
+ case obj.WasmI64:
+ p = appendp(p, AI64Load, constAddr(f.Offset))
+ case obj.WasmF32:
+ p = appendp(p, AF32Load, constAddr(f.Offset))
+ case obj.WasmF64:
+ p = appendp(p, AF64Load, constAddr(f.Offset))
+ case obj.WasmPtr:
+ p = appendp(p, AI64Load, constAddr(f.Offset))
+ p = appendp(p, AI32WrapI64)
+ default:
+ panic("bad param type")
+ }
+ }
+ p = appendp(p, ACall, to)
+ p.Mark = IsWasmImport
+ if len(wi.Results) == 1 {
+ f := wi.Results[0]
+ f.Offset += 8
+ switch f.Type {
+ case obj.WasmI32:
+ p = appendp(p, AI32Store, constAddr(f.Offset))
+ case obj.WasmI64:
+ p = appendp(p, AI64Store, constAddr(f.Offset))
+ case obj.WasmF32:
+ p = appendp(p, AF32Store, constAddr(f.Offset))
+ case obj.WasmF64:
+ p = appendp(p, AF64Store, constAddr(f.Offset))
+ case obj.WasmPtr:
+ p = appendp(p, AI64ExtendI32U)
+ p = appendp(p, AI64Store, constAddr(f.Offset))
+ default:
+ panic("bad result type")
+ }
+ }
+ }
+ p = appendp(p, obj.ARET)
+
+}
+
func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
appendp := func(p *obj.Prog, as obj.As, args ...obj.Addr) *obj.Prog {
if p.As != obj.ANOP {
@@ -185,6 +346,11 @@ func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
s.Func().Args = s.Func().Text.To.Val.(int32)
s.Func().Locals = int32(framesize)
+ wi := s.Func().WasmImport
+ if wi != nil {
+ setupImport(ctxt, s, newprog, wi)
+ }
+
if s.Func().Text.From.Sym.Wrapper() {
// if g._panic != nil && g._panic.argp == FP {
// g._panic.argp = bottom-of-frame
@@ -698,12 +864,6 @@ func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
default:
panic("bad MOV type")
}
-
- case ACallImport:
- p.As = obj.ANOP
- p = appendp(p, AGet, regAddr(REG_SP))
- p = appendp(p, ACall, obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: s})
- p.Mark = WasmImport
}
}
@@ -1006,7 +1166,7 @@ func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
r.Siz = 1 // actually variable sized
r.Off = int32(w.Len())
r.Type = objabi.R_CALL
- if p.Mark&WasmImport != 0 {
+ if p.Mark&IsWasmImport != 0 {
r.Type = objabi.R_WASMIMPORT
}
r.Sym = p.To.Sym
diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go
index efca824d98..ae8482ad47 100644
--- a/src/cmd/link/internal/loader/loader.go
+++ b/src/cmd/link/internal/loader/loader.go
@@ -1640,6 +1640,27 @@ func (l *Loader) Aux(i Sym, j int) Aux {
return Aux{r.Aux(li, j), r, l}
}
+// GetFuncWasmImportSym collects and returns the auxilary 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) GetFuncWasmImportSym(fnSymIdx Sym) Sym {
+ if l.SymType(fnSymIdx) != sym.STEXT {
+ log.Fatalf("error: non-function sym %d/%s t=%s passed to GetFuncWasmImportSym", 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())
+ }
+ }
+ panic("GetFuncWasmImportSym called for func without aux Wasm import sym")
+}
+
// 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 5bdfdbaee6..612deaa722 100644
--- a/src/cmd/link/internal/wasm/asm.go
+++ b/src/cmd/link/internal/wasm/asm.go
@@ -6,10 +6,12 @@ package wasm
import (
"bytes"
+ "cmd/internal/obj"
"cmd/internal/objabi"
"cmd/link/internal/ld"
"cmd/link/internal/loader"
"cmd/link/internal/sym"
+ "encoding/binary"
"internal/buildcfg"
"io"
"regexp"
@@ -44,9 +46,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 +57,44 @@ type wasmFuncType struct {
Results []byte
}
+func readWasmImport(b []byte) obj.WasmImport {
+ readUint32 := func() uint32 {
+ x := binary.LittleEndian.Uint32(b)
+ b = b[4:]
+ return x
+ }
+ readInt64 := func() int64 {
+ val := binary.LittleEndian.Uint64(b)
+ b = b[8:]
+ return int64(val)
+ }
+ readByte := func() byte {
+ byte_ := b[0]
+ b = b[1:]
+ return byte_
+ }
+ readString := func() string {
+ len_ := readUint32()
+ buf := b[:len_]
+ b = b[len_:]
+ return string(buf)
+ }
+ 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 = readInt64()
+ }
+ wi.Results = make([]obj.WasmField, readUint32())
+ for i := range wi.Results {
+ wi.Results[i].Type = obj.WasmFieldType(readByte())
+ wi.Results[i].Offset = readInt64()
+ }
+ return wi
+}
+
var wasmFuncTypes = map[string]*wasmFuncType{
"_rt0_wasm_js": {Params: []byte{}}, //
"wasm_export_run": {Params: []byte{I32, I32}}, // argc, argv
@@ -130,22 +171,27 @@ 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
hostImportMap := make(map[loader.Sym]int64)
+ // these are added as import statements to the top of the WebAssembly binary
+ var hostImports []*wasmFunc
+
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))
+ wasmImportSym := ldr.GetFuncWasmImportSym(fn)
+ wi := readWasmImport(ldr.Data(wasmImportSym))
+ hostImportMap[fn] = int64(len(hostImports))
hostImports = append(hostImports, &wasmFunc{
- Name: ldr.SymName(r.Sym()),
- Type: lookupType(&wasmFuncType{Params: []byte{I32}}, &types),
+ Module: wi.Module,
+ Name: wi.Name,
+ Type: lookupType(&wasmFuncType{
+ Params: fieldsToTypes(wi.Params),
+ Results: fieldsToTypes(wi.Results),
+ }, &types),
})
}
}
@@ -282,7 +328,7 @@ 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
+ writeName(ctxt.Out, fn.Module)
writeName(ctxt.Out, fn.Name)
ctxt.Out.WriteByte(0x00) // func import
writeUleb128(ctxt.Out, uint64(fn.Type))
@@ -604,3 +650,20 @@ 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
+ }
+ }
+ return b
+}
diff --git a/src/runtime/lock_js.go b/src/runtime/lock_js.go
index 0ca3512baf..b2bcebcdb0 100644
--- a/src/runtime/lock_js.go
+++ b/src/runtime/lock_js.go
@@ -7,10 +7,6 @@
package runtime
-import (
- _ "unsafe"
-)
-
// js/wasm has no support for threads yet. There is no preemption.
const (
@@ -225,9 +221,11 @@ func pause(newsp uintptr)
// scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds.
// It returns a timer id that can be used with clearTimeoutEvent.
+//go:wasmimport go runtime.scheduleTimeoutEvent
func scheduleTimeoutEvent(ms int64) int32
// clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent.
+//go:wasmimport go runtime.clearTimeoutEvent
func clearTimeoutEvent(id int32)
// handleEvent gets invoked on a call from JavaScript into Go. It calls the event handler of the syscall/js package
@@ -258,7 +256,6 @@ func handleEvent() {
var eventHandler func()
-//go:linkname setEventHandler syscall/js.setEventHandler
func setEventHandler(fn func()) {
eventHandler = fn
}
diff --git a/src/runtime/mem_js.go b/src/runtime/mem_js.go
index fe940360c0..e3cccf50f6 100644
--- a/src/runtime/mem_js.go
+++ b/src/runtime/mem_js.go
@@ -79,6 +79,7 @@ func growMemory(pages int32) int32
// resetMemoryDataView signals the JS front-end that WebAssembly's memory.grow instruction has been used.
// This allows the front-end to replace the old DataView object with a new one.
+//go:wasmimport go runtime.resetMemoryDataView
func resetMemoryDataView()
func sysMap(v unsafe.Pointer, n uintptr, sysStat *sysMemStat) {
diff --git a/src/runtime/os_js.go b/src/runtime/os_js.go
index 52b64e7602..bbc372a936 100644
--- a/src/runtime/os_js.go
+++ b/src/runtime/os_js.go
@@ -27,6 +27,7 @@ func closefd(fd int32) int32 { panic("not implemented")
func read(fd int32, p unsafe.Pointer, n int32) int32 { panic("not implemented") }
//go:noescape
+//go:wasmimport go runtime.wasmWrite
func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
func usleep(usec uint32)
@@ -117,6 +118,7 @@ func crash() {
*(*int32)(nil) = 0
}
+//go:wasmimport go runtime.getRandomData
func getRandomData(r []byte)
func goenvs() {
diff --git a/src/runtime/stubs3.go b/src/runtime/stubs3.go
index b895be4c70..3f1253a09b 100644
--- a/src/runtime/stubs3.go
+++ b/src/runtime/stubs3.go
@@ -7,4 +7,5 @@
package runtime
+//go:wasmimport go runtime.nanotime1
func nanotime1() int64
diff --git a/src/runtime/sys_wasm.go b/src/runtime/sys_wasm.go
index 057ed4ccd9..bd7f4e8a9a 100644
--- a/src/runtime/sys_wasm.go
+++ b/src/runtime/sys_wasm.go
@@ -24,6 +24,7 @@ func wasmDiv()
func wasmTruncS()
func wasmTruncU()
+//go:wasmimport go runtime.wasmExit
func wasmExit(code int32)
// adjust Gobuf as it if executed a call to fn with context ctxt
diff --git a/src/runtime/timestub2.go b/src/runtime/timestub2.go
index 800a2a94e0..4836c71321 100644
--- a/src/runtime/timestub2.go
+++ b/src/runtime/timestub2.go
@@ -13,4 +13,5 @@
package runtime
+//go:wasmimport go runtime.walltime
func walltime() (sec int64, nsec int32)
diff --git a/src/syscall/js/func.go b/src/syscall/js/func.go
index da4cf68774..810aae48c6 100644
--- a/src/syscall/js/func.go
+++ b/src/syscall/js/func.go
@@ -6,7 +6,10 @@
package js
-import "sync"
+import (
+ "sync"
+ _ "unsafe"
+)
var (
funcsMu sync.Mutex
@@ -62,6 +65,7 @@ func (c Func) Release() {
}
// setEventHandler is defined in the runtime package.
+//go:linkname setEventHandler runtime.setEventHandler
func setEventHandler(fn func())
func init() {
diff --git a/src/syscall/js/js.go b/src/syscall/js/js.go
index a48bbd4dd7..3744424a86 100644
--- a/src/syscall/js/js.go
+++ b/src/syscall/js/js.go
@@ -69,6 +69,7 @@ func makeValue(r ref) Value {
return Value{ref: r, gcPtr: gcPtr}
}
+//go:wasmimport go syscall/js.finalizeRef
func finalizeRef(r ref)
func predefValue(id uint32, typeFlag byte) Value {
@@ -220,6 +221,8 @@ func ValueOf(x interface{}) Value {
}
}
+//go:wasmimport go syscall/js.stringVal
+//go:noescape
func stringVal(x string) ref
// Type represents the JavaScript type of a Value.
@@ -303,6 +306,8 @@ func (v Value) Get(p string) Value {
return r
}
+//go:wasmimport go syscall/js.valueGet
+//go:noescape
func valueGet(v ref, p string) ref
// Set sets the JavaScript property p of value v to ValueOf(x).
@@ -317,6 +322,8 @@ func (v Value) Set(p string, x interface{}) {
runtime.KeepAlive(xv)
}
+//go:wasmimport go syscall/js.valueSet
+//go:noescape
func valueSet(v ref, p string, x ref)
// Delete deletes the JavaScript property p of value v.
@@ -329,6 +336,8 @@ func (v Value) Delete(p string) {
runtime.KeepAlive(v)
}
+//go:wasmimport go syscall/js.valueDelete
+//go:noescape
func valueDelete(v ref, p string)
// Index returns JavaScript index i of value v.
@@ -342,6 +351,7 @@ func (v Value) Index(i int) Value {
return r
}
+//go:wasmimport go syscall/js.valueIndex
func valueIndex(v ref, i int) ref
// SetIndex sets the JavaScript index i of value v to ValueOf(x).
@@ -356,6 +366,7 @@ func (v Value) SetIndex(i int, x interface{}) {
runtime.KeepAlive(xv)
}
+//go:wasmimport go syscall/js.valueSetIndex
func valueSetIndex(v ref, i int, x ref)
func makeArgs(args []interface{}) ([]Value, []ref) {
@@ -380,6 +391,7 @@ func (v Value) Length() int {
return r
}
+//go:wasmimport go syscall/js.valueLength
func valueLength(v ref) int
// Call does a JavaScript call to the method m of value v with the given arguments.
@@ -402,6 +414,8 @@ func (v Value) Call(m string, args ...interface{}) Value {
return makeValue(res)
}
+//go:wasmimport go syscall/js.valueCall
+//go:noescape
func valueCall(v ref, m string, args []ref) (ref, bool)
// Invoke does a JavaScript call of the value v with the given arguments.
@@ -421,6 +435,8 @@ func (v Value) Invoke(args ...interface{}) Value {
return makeValue(res)
}
+//go:wasmimport go syscall/js.valueInvoke
+//go:noescape
func valueInvoke(v ref, args []ref) (ref, bool)
// New uses JavaScript's "new" operator with value v as constructor and the given arguments.
@@ -440,6 +456,8 @@ func (v Value) New(args ...interface{}) Value {
return makeValue(res)
}
+//go:wasmimport go syscall/js.valueNew
+//go:noescape
func valueNew(v ref, args []ref) (ref, bool)
func (v Value) isNumber() bool {
@@ -539,8 +557,11 @@ func jsString(v Value) string {
return string(b)
}
+//go:wasmimport go syscall/js.valuePrepareString
func valuePrepareString(v ref) (ref, int)
+//go:wasmimport go syscall/js.valueLoadString
+//go:noescape
func valueLoadString(v ref, b []byte)
// InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator.
@@ -551,6 +572,7 @@ func (v Value) InstanceOf(t Value) bool {
return r
}
+//go:wasmimport go syscall/js.valueInstanceOf
func valueInstanceOf(v ref, t ref) bool
// A ValueError occurs when a Value method is invoked on
@@ -577,6 +599,8 @@ func CopyBytesToGo(dst []byte, src Value) int {
return n
}
+//go:wasmimport go syscall/js.copyBytesToGo
+//go:noescape
func copyBytesToGo(dst []byte, src ref) (int, bool)
// CopyBytesToJS copies bytes from src to dst.
@@ -591,4 +615,6 @@ func CopyBytesToJS(dst Value, src []byte) int {
return n
}
+//go:wasmimport go syscall/js.copyBytesToJS
+//go:noescape
func copyBytesToJS(dst ref, src []byte) (int, bool)
diff --git a/src/syscall/js/js_js.s b/src/syscall/js/js_js.s
deleted file mode 100644
index 47ad6b83e5..0000000000
--- a/src/syscall/js/js_js.s
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2018 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#include "textflag.h"
-
-TEXT ·finalizeRef(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·stringVal(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·valueGet(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·valueSet(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·valueDelete(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·valueIndex(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·valueSetIndex(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·valueCall(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·valueInvoke(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·valueNew(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·valueLength(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·valuePrepareString(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·valueLoadString(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·valueInstanceOf(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·copyBytesToGo(SB), NOSPLIT, $0
- CallImport
- RET
-
-TEXT ·copyBytesToJS(SB), NOSPLIT, $0
- CallImport
- RET
diff --git a/src/syscall/js/js_test.go b/src/syscall/js/js_test.go
index 5fc9107d40..b651751138 100644
--- a/src/syscall/js/js_test.go
+++ b/src/syscall/js/js_test.go
@@ -44,6 +44,19 @@ var dummys = js.Global().Call("eval", `({
objBooleanFalse: new Boolean(false),
})`)
+//go:wasmimport _gotest add
+func testAdd(a, b uint32) uint32
+
+func TestWasmImport(t *testing.T) {
+ a := uint32(3)
+ b := uint32(5)
+ expected := a + b
+ actual := testAdd(a, b)
+ if actual != expected {
+ t.Errorf("Expected %v, got: %v", expected, actual)
+ }
+}
+
func TestBool(t *testing.T) {
want := true
o := dummys.Get("someBool")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment