]> Cypherpunks.ru repositories - gostls13.git/commitdiff
all: implement wasmimport directive
authorEvan Phoenix <evan@phx.io>
Sun, 22 Jan 2023 23:30:59 +0000 (15:30 -0800)
committerGopher Robot <gobot@golang.org>
Thu, 2 Mar 2023 05:28:55 +0000 (05:28 +0000)
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>
29 files changed:
misc/wasm/wasm_exec.js
src/cmd/compile/internal/gc/compile.go
src/cmd/compile/internal/ir/func.go
src/cmd/compile/internal/ir/sizeof_test.go
src/cmd/compile/internal/noder/linker.go
src/cmd/compile/internal/noder/noder.go
src/cmd/compile/internal/noder/reader.go
src/cmd/compile/internal/noder/writer.go
src/cmd/compile/internal/ssagen/abi.go
src/cmd/internal/goobj/objfile.go
src/cmd/internal/obj/link.go
src/cmd/internal/obj/objfile.go
src/cmd/internal/obj/sym.go
src/cmd/internal/obj/wasm/a.out.go
src/cmd/internal/obj/wasm/anames.go
src/cmd/internal/obj/wasm/wasmobj.go
src/cmd/link/internal/loader/loader.go
src/cmd/link/internal/wasm/asm.go
src/runtime/lock_js.go
src/runtime/mem_js.go
src/runtime/os_js.go
src/runtime/rt0_js_wasm.s
src/runtime/stubs3.go
src/runtime/sys_wasm.go
src/runtime/sys_wasm.s
src/runtime/timestub2.go
src/syscall/js/js.go
src/syscall/js/js_js.s
src/syscall/js/js_test.go

index e6c8921091ec1253e5de679bb5ba75187936e0ee..7f72bee005c1d2fb351a9f907dfbd458e0bc21ce 100644 (file)
                                this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
                        }
 
+                       const setInt32 = (addr, v) => {
+                               this.mem.setUint32(addr + 0, v, true);
+                       }
+
                        const getInt64 = (addr) => {
                                const low = this.mem.getUint32(addr + 0, true);
                                const high = this.mem.getInt32(addr + 4, true);
 
                        const timeOrigin = Date.now() - performance.now();
                        this.importObject = {
-                               go: {
+                               _gotest: {
+                                       add: (a, b) => a + b,
+                               },
+                               gojs: {
                                        // 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
                                        // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
index cfce77d8289c7dbffb5ddeaf411279c3e0294576..4795297e7e3280a573126220867b2c583ed89cdf 100644 (file)
@@ -43,6 +43,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.
                ir.InitLSym(fn, false)
index 967ebb02c2f6146d099895b8f21df74dacafcb87..76ab952157d2ce36c2849b1d313101f2dc0fd692 100644 (file)
@@ -133,6 +133,16 @@ type Func struct {
        // For wrapper functions, WrappedFunc point to the original Func.
        // Currently only used for go/defer wrappers.
        WrappedFunc *Func
+
+       // WasmImport is used by the //go:wasmimport directive to store info about
+       // a WebAssembly function import.
+       WasmImport *WasmImport
+}
+
+// WasmImport stores metadata associated with the //go:wasmimport pragma.
+type WasmImport struct {
+       Module string
+       Name   string
 }
 
 func NewFunc(pos src.XPos) *Func {
index 754d1a8de070bf1ad7ba5fba0871c0eae451ea6a..307f40d4846710a5a8bad4f93b1f9728dcbfab84 100644 (file)
@@ -20,7 +20,7 @@ func TestSizeof(t *testing.T) {
                _32bit uintptr     // size on 32bit platforms
                _64bit uintptr     // size on 64bit platforms
        }{
-               {Func{}, 184, 320},
+               {Func{}, 188, 328},
                {Name{}, 100, 176},
        }
 
index 0f39fdec051f5f8e97762da5790e9574c7d6de7f..44de017ae543805fab1aa494550bfa60083c93f7 100644 (file)
@@ -5,6 +5,7 @@
 package noder
 
 import (
+       "internal/buildcfg"
        "internal/pkgbits"
        "io"
 
@@ -269,6 +270,16 @@ func (l *linker) relocFuncExt(w *pkgbits.Encoder, name *ir.Name) {
        l.pragmaFlag(w, name.Func.Pragma)
        l.linkname(w, name)
 
+       if buildcfg.GOARCH == "wasm" {
+               if name.Func.WasmImport != nil {
+                       w.String(name.Func.WasmImport.Module)
+                       w.String(name.Func.WasmImport.Name)
+               } else {
+                       w.String("")
+                       w.String("")
+               }
+       }
+
        // Relocated extension data.
        w.Bool(true)
 
index 10619bf569705f50c120a982f6699290f415c9cc..16113e37a39340df8c2898d62cc8392fc24d6298 100644 (file)
@@ -7,6 +7,7 @@ package noder
 import (
        "errors"
        "fmt"
+       "internal/buildcfg"
        "os"
        "path/filepath"
        "runtime"
@@ -166,9 +167,17 @@ 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 *WasmImport
+}
+
+// WasmImport stores metadata associated with the //go:wasmimport pragma
+type WasmImport struct {
+       Pos    syntax.Pos
+       Module string
+       Name   string
 }
 
 type pragmaPos struct {
@@ -192,6 +201,9 @@ func (p *noder) checkUnusedDuringParse(pragma *pragmas) {
                        p.error(syntax.Error{Pos: e.Pos, Msg: "misplaced go:embed directive"})
                }
        }
+       if pragma.WasmImport != nil {
+               p.error(syntax.Error{Pos: pragma.WasmImport.Pos, Msg: "misplaced go:wasmimport directive"})
+       }
 }
 
 // pragma is called concurrently if files are parsed concurrently.
@@ -219,6 +231,25 @@ 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 importmodule importname"})
+                       break
+               }
+               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"})
+                       break
+               }
+
+               if buildcfg.GOARCH == "wasm" {
+                       // Only actually use them if we're compiling to WASM though.
+                       pragma.WasmImport = &WasmImport{
+                               Pos:    pos,
+                               Module: f[1],
+                               Name:   f[2],
+                       }
+               }
        case strings.HasPrefix(text, "go:linkname "):
                f := strings.Fields(text)
                if !(2 <= len(f) && len(f) <= 3) {
index b7605e9317f270460a092b9c0b21089978f7d446..e4ab80b2d05d6ec1f22432818651714967cae320 100644 (file)
@@ -1081,6 +1081,18 @@ func (r *reader) funcExt(name *ir.Name, method *types.Sym) {
        fn.Pragma = r.pragmaFlag()
        r.linkname(name)
 
+       if buildcfg.GOARCH == "wasm" {
+               xmod := r.String()
+               xname := r.String()
+
+               if xmod != "" && xname != "" {
+                       fn.WasmImport = &ir.WasmImport{
+                               Module: xmod,
+                               Name:   xname,
+                       }
+               }
+       }
+
        typecheck.Func(fn)
 
        if r.Bool() {
index da5c1e910d74094fed9b62e25e71f48186af40ef..5dd8d1de2df1b510490493943899e25fdec40d50 100644 (file)
@@ -6,6 +6,7 @@ package noder
 
 import (
        "fmt"
+       "internal/buildcfg"
        "internal/pkgbits"
 
        "cmd/compile/internal/base"
@@ -1003,11 +1004,15 @@ func (w *writer) funcExt(obj *types2.Func) {
        if pragma&ir.Systemstack != 0 && pragma&ir.Nosplit != 0 {
                w.p.errorf(decl, "go:nosplit and go:systemstack cannot be combined")
        }
+       wi := asWasmImport(decl.Pragma)
 
        if decl.Body != nil {
                if pragma&ir.Noescape != 0 {
                        w.p.errorf(decl, "can only use //go:noescape with external func implementations")
                }
+               if wi != nil {
+                       w.p.errorf(decl, "can only use //go:wasmimport with external func implementations")
+               }
                if (pragma&ir.UintptrKeepAlive != 0 && pragma&ir.UintptrEscapes == 0) && pragma&ir.Nosplit == 0 {
                        // Stack growth can't handle uintptr arguments that may
                        // be pointers (as we don't know which are pointers
@@ -1028,7 +1033,8 @@ func (w *writer) funcExt(obj *types2.Func) {
                if base.Flag.Complete || decl.Name.Value == "init" {
                        // Linknamed functions are allowed to have no body. Hopefully
                        // the linkname target has a body. See issue 23311.
-                       if _, ok := w.p.linknames[obj]; !ok {
+                       // Wasmimport functions are also allowed to have no body.
+                       if _, ok := w.p.linknames[obj]; !ok && wi == nil {
                                w.p.errorf(decl, "missing function body")
                        }
                }
@@ -1041,6 +1047,17 @@ func (w *writer) funcExt(obj *types2.Func) {
        w.Sync(pkgbits.SyncFuncExt)
        w.pragmaFlag(pragma)
        w.linkname(obj)
+
+       if buildcfg.GOARCH == "wasm" {
+               if wi != nil {
+                       w.String(wi.Module)
+                       w.String(wi.Name)
+               } else {
+                       w.String("")
+                       w.String("")
+               }
+       }
+
        w.Bool(false) // stub extension
        w.Reloc(pkgbits.RelocBody, body)
        w.Sync(pkgbits.SyncEOF)
@@ -2728,6 +2745,13 @@ func asPragmaFlag(p syntax.Pragma) ir.PragmaFlag {
        return p.(*pragmas).Flag
 }
 
+func asWasmImport(p syntax.Pragma) *WasmImport {
+       if p == nil {
+               return nil
+       }
+       return p.(*pragmas).WasmImport
+}
+
 // isPtrTo reports whether from is the type *to.
 func isPtrTo(from, to types2.Type) bool {
        ptr, ok := from.(*types2.Pointer)
index fa26ae1f06d788f88cb4552176506c98f58ea901..9c725b898d482de25bebbb9c75b3bd0089965b66 100644 (file)
@@ -11,11 +11,14 @@ import (
        "os"
        "strings"
 
+       "cmd/compile/internal/abi"
        "cmd/compile/internal/base"
        "cmd/compile/internal/ir"
+       "cmd/compile/internal/objw"
        "cmd/compile/internal/typecheck"
        "cmd/compile/internal/types"
        "cmd/internal/obj"
+       "cmd/internal/obj/wasm"
 )
 
 // SymABIs records information provided by the assembler about symbol
@@ -336,3 +339,88 @@ func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) {
        typecheck.DeclContext = savedclcontext
        ir.CurFunc = savedcurfn
 }
+
+// CreateWasmImportWrapper creates a wrapper for imported WASM functions to
+// adapt them to the Go calling convention. The body for this function is
+// generated in cmd/internal/obj/wasm/wasmobj.go
+func CreateWasmImportWrapper(fn *ir.Func) bool {
+       if fn.WasmImport == nil {
+               return false
+       }
+       if buildcfg.GOARCH != "wasm" {
+               base.FatalfAt(fn.Pos(), "CreateWasmImportWrapper call not supported on %s: func was %v", buildcfg.GOARCH, fn)
+       }
+
+       ir.InitLSym(fn, true)
+
+       setupWasmABI(fn)
+
+       pp := objw.NewProgs(fn, 0)
+       defer pp.Free()
+       pp.Text.To.Type = obj.TYPE_TEXTSIZE
+       pp.Text.To.Val = int32(types.RoundUp(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(result *abi.ABIParamResultInfo, abiParams []abi.ABIParamAssignment) []obj.WasmField {
+       wfs := make([]obj.WasmField, len(abiParams))
+       for i, p := range abiParams {
+               t := p.Type
+               switch {
+               case t.IsInteger() && t.Size() == 4:
+                       wfs[i].Type = obj.WasmI32
+               case t.IsInteger() && t.Size() == 8:
+                       wfs[i].Type = obj.WasmI64
+               case t.IsFloat() && t.Size() == 4:
+                       wfs[i].Type = obj.WasmF32
+               case t.IsFloat() && t.Size() == 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 = p.FrameOffset(result)
+       }
+       return wfs
+}
+
+// setupTextLSym initializes the LSym for a with-body text symbol.
+func setupWasmABI(f *ir.Func) {
+       wi := obj.WasmImport{
+               Module: f.WasmImport.Module,
+               Name:   f.WasmImport.Name,
+       }
+       if wi.Module == wasm.GojsModule {
+               // Functions that are imported from the "gojs" module use a special
+               // ABI that just accepts the stack pointer.
+               // Example:
+               //
+               //      //go:wasmimport gojs add
+               //      func importedAdd(a, b uint) uint
+               //
+               // will roughly become
+               //
+               //      (import "gojs" "add" (func (param i32)))
+               wi.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)))
+               abiConfig := AbiForBodylessFuncStackMap(f)
+               abiInfo := abiConfig.ABIAnalyzeFuncType(f.Type().FuncType())
+               wi.Params = toWasmFields(abiInfo, abiInfo.InParams())
+               wi.Results = toWasmFields(abiInfo, abiInfo.OutParams())
+       }
+       f.LSym.Func().WasmImport = &wi
+}
index 547b8264950f45f50a61e1ed424044648bde8026..0364f856cfeb0cad86c2ea5a91eae4f11195c832 100644 (file)
@@ -442,6 +442,7 @@ const (
        AuxPcline
        AuxPcinline
        AuxPcdata
+       AuxWasmImport
 )
 
 func (a *Aux) Type() uint8 { return a[0] }
index d153afbfae0f0afdf14660975603688a06fbaeb8..077562a267cca20f03bd6735da78ccb894b5d162 100644 (file)
@@ -37,6 +37,7 @@ import (
        "cmd/internal/objabi"
        "cmd/internal/src"
        "cmd/internal/sys"
+       "encoding/binary"
        "fmt"
        "sync"
        "sync/atomic"
@@ -499,7 +500,9 @@ type FuncInfo struct {
        WrapInfo           *LSym // for wrapper, info of wrapped function
        JumpTables         []JumpTable
 
-       FuncInfoSym *LSym
+       FuncInfoSym   *LSym
+       WasmImportSym *LSym
+       WasmImport    *WasmImport
 }
 
 // JumpTable represents a table used for implementing multi-way
@@ -558,6 +561,75 @@ func (s *LSym) File() *FileInfo {
        return f
 }
 
+// WasmImport represents a WebAssembly (WASM) imported function with
+// parameters and results translated into WASM types based on the Go function
+// declaration.
+type WasmImport struct {
+       // Module holds the WASM module name specified by the //go:wasmimport
+       // directive.
+       Module string
+       // Name holds the WASM imported function name specified by the
+       // //go:wasmimport directive.
+       Name string
+       // Params holds the imported function parameter fields.
+       Params []WasmField
+       // Results holds the imported function result fields.
+       Results []WasmField
+}
+
+func (wi *WasmImport) CreateSym(ctxt *Link) *LSym {
+       var 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)
+       }
+
+       return &sym
+}
+
+type WasmField struct {
+       Type WasmFieldType
+       // Offset holds the frame-pointer-relative locations for Go's stack-based
+       // ABI. This is used by the src/cmd/internal/wasm package to map WASM
+       // import parameters to the Go stack in a wrapper function.
+       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.
index 73c29d9686590b57237c9e1beb8fae074c89d47b..78fa4c1076e1b4becb53fe8afe01a536bf590063 100644 (file)
@@ -605,7 +605,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)
+               }
        }
 }
 
@@ -703,6 +708,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
 }
@@ -759,8 +770,8 @@ func genFuncInfoSyms(ctxt *Link) {
                fn.FuncInfoSym = isym
                b.Reset()
 
-               dwsyms := []*LSym{fn.dwarfRangesSym, fn.dwarfLocSym, fn.dwarfDebugLinesSym, fn.dwarfInfoSym}
-               for _, s := range dwsyms {
+               auxsyms := []*LSym{fn.dwarfRangesSym, fn.dwarfLocSym, fn.dwarfDebugLinesSym, fn.dwarfInfoSym, fn.WasmImportSym}
+               for _, s := range auxsyms {
                        if s == nil || s.Size == 0 {
                                continue
                        }
index e0817d5f743fe106dc84b5b9d035da5fe246d5fd..4a01af39271f2b08f86fe91aa7cc696020c11663 100644 (file)
@@ -416,16 +416,16 @@ func (ctxt *Link) traverseFuncAux(flag traverseFlag, fsym *LSym, fn func(parent
                }
        }
 
-       dwsyms := []*LSym{fninfo.dwarfRangesSym, fninfo.dwarfLocSym, fninfo.dwarfDebugLinesSym, fninfo.dwarfInfoSym}
-       for _, dws := range dwsyms {
-               if dws == nil || dws.Size == 0 {
+       auxsyms := []*LSym{fninfo.dwarfRangesSym, fninfo.dwarfLocSym, fninfo.dwarfDebugLinesSym, fninfo.dwarfInfoSym, fninfo.WasmImportSym}
+       for _, s := range auxsyms {
+               if s == nil || s.Size == 0 {
                        continue
                }
-               fn(fsym, dws)
+               fn(fsym, s)
                if flag&traverseRefs != 0 {
-                       for _, r := range dws.R {
+                       for _, r := range s.R {
                                if r.Sym != nil {
-                                       fn(dws, r.Sym)
+                                       fn(s, r.Sym)
                                }
                        }
                }
index 83ce0a67385e707a0c2b89acbf71c0c68c121466..0262630d5a78a0ed4cebc33d466c62e61ae4f55f 100644 (file)
@@ -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
index c9bc15d27007bce783c74527cfe044e3984416ef..6f1a662960280061a16763fcb6dbf1482e7d4692 100644 (file)
@@ -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",
index 96a2ef4a6fc8c32be9dc2df8e58863121311991c..fd0faec84b4f7ab91a70601c271019b2976e8c0d 100644 (file)
@@ -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,6 +134,14 @@ const (
        WasmImport = 1 << 0
 )
 
+const (
+       // This is a special wasm module name that when used as the module name
+       // in //go:wasmimport will cause the generated code to pass the stack pointer
+       // directly to the imported function. In other words, any function that
+       // uses the gojs module understands the internal Go WASM ABI directly.
+       GojsModule = "gojs"
+)
+
 func instinit(ctxt *obj.Link) {
        morestack = ctxt.Lookup("runtime.morestack")
        morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt")
@@ -177,7 +184,121 @@ 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)
 
-       if s.Func().Text.From.Sym.Wrapper() {
+       // If the function exits just to call out to a wasmimport, then
+       // generate the code to translate from our internal Go-stack
+       // based call convention to the native webassembly call convention.
+       if wi := s.Func().WasmImport; wi != nil {
+               s.Func().WasmImportSym = wi.CreateSym(ctxt)
+               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 the module that the import is for is our magic "gojs" module, then this
+               // indicates that the called function understands the Go stack-based call convention
+               // so we just pass the stack pointer to it, knowing it will read the params directly
+               // off the stack and push the results into memory based on the stack pointer.
+               if wi.Module == GojsModule {
+                       // The called function has a signature of 'func(sp int)'. It has access to the memory
+                       // value somewhere to be able to address the memory based on the "sp" value.
+
+                       p = appendp(p, AGet, regAddr(REG_SP))
+                       p = appendp(p, ACall, to)
+
+                       p.Mark = WasmImport
+               } else {
+                       if len(wi.Results) > 1 {
+                               // TODO(evanphx) implement support for the multi-value proposal:
+                               // https://github.com/WebAssembly/multi-value/blob/master/proposals/multi-value/Overview.md
+                               panic("invalid results type") // impossible until multi-value proposal has landed
+                       }
+                       if len(wi.Results) == 1 {
+                               // If we have a result (rather than returning nothing at all), then
+                               // we'll write the result to the Go stack relative to the current stack pointer.
+                               // We cache the current stack pointer value on the wasm stack here and then use
+                               // it after the Call instruction to store the result.
+                               p = appendp(p, AGet, regAddr(REG_SP))
+                       }
+                       for _, f := range wi.Params {
+                               // Each load instructions will consume the value of sp on the stack, so
+                               // we need to read sp for each param. WASM appears to not have a stack dup instruction
+                               // (a strange ommission for a stack-based VM), if it did, we'd be using the dup here.
+                               p = appendp(p, AGet, regAddr(REG_SP))
+
+                               // Offset is the location of the param on the Go stack (ie relative to sp).
+                               // Because of our call convention, the parameters are located an additional 8 bytes
+                               // from sp because we store the return address as a int64 at the bottom of the stack.
+                               // Ie the stack looks like [return_addr, param3, param2, param1, etc]
+
+                               // Ergo, we add 8 to the true byte offset of the param to skip the return address.
+                               loadOffset := f.Offset + 8
+
+                               // We're reading the value from the Go stack onto the WASM stack and leaving it there
+                               // for CALL to pick them up.
+                               switch f.Type {
+                               case obj.WasmI32:
+                                       p = appendp(p, AI32Load, constAddr(loadOffset))
+                               case obj.WasmI64:
+                                       p = appendp(p, AI64Load, constAddr(loadOffset))
+                               case obj.WasmF32:
+                                       p = appendp(p, AF32Load, constAddr(loadOffset))
+                               case obj.WasmF64:
+                                       p = appendp(p, AF64Load, constAddr(loadOffset))
+                               case obj.WasmPtr:
+                                       p = appendp(p, AI64Load, constAddr(loadOffset))
+                                       p = appendp(p, AI32WrapI64)
+                               default:
+                                       panic("bad param type")
+                               }
+                       }
+
+                       // The call instruction is marked as being for a wasm import so that a later phase
+                       // will generate relocation information that allows us to patch this with then
+                       // offset of the imported function in the wasm imports.
+                       p = appendp(p, ACall, to)
+                       p.Mark = WasmImport
+
+                       if len(wi.Results) == 1 {
+                               f := wi.Results[0]
+
+                               // Much like with the params, we need to adjust the offset we store the result value
+                               // to by 8 bytes to account for the return address on the Go stack.
+                               storeOffset := f.Offset + 8
+
+                               // This code is paired the code above that reads the stack pointer onto the wasm
+                               // stack. We've done this so we have a consistent view of the sp value as it might
+                               // be manipulated by the call and we want to ignore that manipulation here.
+                               switch f.Type {
+                               case obj.WasmI32:
+                                       p = appendp(p, AI32Store, constAddr(storeOffset))
+                               case obj.WasmI64:
+                                       p = appendp(p, AI64Store, constAddr(storeOffset))
+                               case obj.WasmF32:
+                                       p = appendp(p, AF32Store, constAddr(storeOffset))
+                               case obj.WasmF64:
+                                       p = appendp(p, AF64Store, constAddr(storeOffset))
+                               case obj.WasmPtr:
+                                       p = appendp(p, AI64ExtendI32U)
+                                       p = appendp(p, AI64Store, constAddr(storeOffset))
+                               default:
+                                       panic("bad result type")
+                               }
+                       }
+               }
+
+               p = appendp(p, obj.ARET)
+
+               // It should be 0 already, but we'll set it to 0 anyway just to be sure
+               // that the code below which adds frame expansion code to the function body
+               // isn't run. We don't want the frame expansion code because our function
+               // body is just the code to translate and call the imported function.
+               framesize = 0
+       } else if s.Func().Text.From.Sym.Wrapper() {
                // if g._panic != nil && g._panic.argp == FP {
                //   g._panic.argp = bottom-of-frame
                // }
@@ -241,7 +362,9 @@ func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
                p.Spadj = int32(framesize)
        }
 
-       needMoreStack := !s.Func().Text.From.Sym.NoSplit()
+       // If the framesize is 0, then imply nosplit because it's a specially
+       // generated function.
+       needMoreStack := framesize > 0 && !s.Func().Text.From.Sym.NoSplit()
 
        // If the maymorestack debug option is enabled, insert the
        // call to maymorestack *before* processing resume points so
@@ -707,12 +830,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
                }
        }
 
index e3ee819a9db1b4fa5d9f6b10449838006d597cf3..fa8c0c6b20d6726110c2531f549f24836a96bbe1 100644 (file)
@@ -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
index b5685701f2e7d27f6cb8af68ab1fdf4b8be39836..30d0dc7ff2a3e9724819a16cd2bf69647972a164 100644 (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
+}
index f71e7a2b4a59cddcc36999761b2170e98329fe9d..f87a94a8490fca8c6d00d39c8ce9e7ffcf612f17 100644 (file)
@@ -6,9 +6,7 @@
 
 package runtime
 
-import (
-       _ "unsafe"
-)
+import _ "unsafe" // for go:linkname
 
 // js/wasm has no support for threads yet. There is no preemption.
 
@@ -232,9 +230,13 @@ 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 gojs runtime.scheduleTimeoutEvent
 func scheduleTimeoutEvent(ms int64) int32
 
 // clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent.
+//
+//go:wasmimport gojs 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
index e87c5f26ae23a179c1ab39aa6d9c302ece78e0b6..78eda47b1fbb0f4a8b106208c1812b88003cb93a 100644 (file)
@@ -79,6 +79,8 @@ 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 gojs runtime.resetMemoryDataView
 func resetMemoryDataView()
 
 func sysMapOS(v unsafe.Pointer, n uintptr) {
index 7481fb92bf88365618f203b00d3f8b4ea9034e5e..63a3d95afa5124f40e4320f637910dda18d2e0db 100644 (file)
@@ -26,6 +26,7 @@ func open(name *byte, mode, perm int32) int32        { panic("not implemented")
 func closefd(fd int32) int32                         { panic("not implemented") }
 func read(fd int32, p unsafe.Pointer, n int32) int32 { panic("not implemented") }
 
+//go:wasmimport gojs runtime.wasmWrite
 //go:noescape
 func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
 
@@ -117,6 +118,7 @@ func crash() {
        *(*int32)(nil) = 0
 }
 
+//go:wasmimport gojs runtime.getRandomData
 func getRandomData(r []byte)
 
 func goenvs() {
index 714582a6d56de5c856a84038ce4bae1c8fcfbf06..6f67752d63cde1f97fe94e056f73123057a57aad 100644 (file)
@@ -98,7 +98,6 @@ TEXT runtime·pause(SB), NOSPLIT, $0-8
 TEXT runtime·exit(SB), NOSPLIT, $0-4
        I32Const $0
        Call runtime·wasmExit(SB)
-       Drop
        I32Const $1
        Set PAUSE
        RETUNWIND
index 891663b1109b1981bb664d3d284c9ea8ac347937..95306971b4ba256f2ea0b349b2b1d6cba4977724 100644 (file)
@@ -6,4 +6,5 @@
 
 package runtime
 
+//go:wasmimport gojs runtime.nanotime1
 func nanotime1() int64
index bf5756984ae1d65f7df12a51c01b5aa88d1a1220..27f9432bd45d595e2df396809e96e84892006516 100644 (file)
@@ -21,6 +21,7 @@ func wasmDiv()
 func wasmTruncS()
 func wasmTruncU()
 
+//go:wasmimport gojs runtime.wasmExit
 func wasmExit(code int32)
 
 // adjust Gobuf as it if executed a call to fn with context ctxt
index f706e00ab285b307a6efebead432a902540f6157..bd60e1d419be964555a44100f0dd2626838c9b85 100644 (file)
@@ -101,35 +101,3 @@ TEXT runtime·growMemory(SB), NOSPLIT, $0
        GrowMemory
        I32Store ret+8(FP)
        RET
-
-TEXT ·resetMemoryDataView(SB), NOSPLIT, $0
-       CallImport
-       RET
-
-TEXT ·wasmExit(SB), NOSPLIT, $0
-       CallImport
-       RET
-
-TEXT ·wasmWrite(SB), NOSPLIT, $0
-       CallImport
-       RET
-
-TEXT ·nanotime1(SB), NOSPLIT, $0
-       CallImport
-       RET
-
-TEXT ·walltime(SB), NOSPLIT, $0
-       CallImport
-       RET
-
-TEXT ·scheduleTimeoutEvent(SB), NOSPLIT, $0
-       CallImport
-       RET
-
-TEXT ·clearTimeoutEvent(SB), NOSPLIT, $0
-       CallImport
-       RET
-
-TEXT ·getRandomData(SB), NOSPLIT, $0
-       CallImport
-       RET
index b9a5cc6345ee2d393882bc18ed9f4d37c0c90e88..b0eae502daeac15f1b4a9cacc20fdfa02e147859 100644 (file)
@@ -6,4 +6,5 @@
 
 package runtime
 
+//go:wasmimport gojs runtime.walltime
 func walltime() (sec int64, nsec int32)
index 2f4f5adda02738f0a59f0c236c31ebb2f9d3949b..5fdb14d446ac0656af90829c490fcf5613aa4eb8 100644 (file)
@@ -58,6 +58,7 @@ func makeValue(r ref) Value {
        return Value{ref: r, gcPtr: gcPtr}
 }
 
+//go:wasmimport gojs syscall/js.finalizeRef
 func finalizeRef(r ref)
 
 func predefValue(id uint32, typeFlag byte) Value {
@@ -209,6 +210,7 @@ func ValueOf(x any) Value {
        }
 }
 
+//go:wasmimport gojs syscall/js.stringVal
 func stringVal(x string) ref
 
 // Type represents the JavaScript type of a Value.
@@ -292,6 +294,7 @@ func (v Value) Get(p string) Value {
        return r
 }
 
+//go:wasmimport gojs syscall/js.valueGet
 func valueGet(v ref, p string) ref
 
 // Set sets the JavaScript property p of value v to ValueOf(x).
@@ -306,6 +309,7 @@ func (v Value) Set(p string, x any) {
        runtime.KeepAlive(xv)
 }
 
+//go:wasmimport gojs syscall/js.valueSet
 func valueSet(v ref, p string, x ref)
 
 // Delete deletes the JavaScript property p of value v.
@@ -318,6 +322,7 @@ func (v Value) Delete(p string) {
        runtime.KeepAlive(v)
 }
 
+//go:wasmimport gojs syscall/js.valueDelete
 func valueDelete(v ref, p string)
 
 // Index returns JavaScript index i of value v.
@@ -331,6 +336,7 @@ func (v Value) Index(i int) Value {
        return r
 }
 
+//go:wasmimport gojs syscall/js.valueIndex
 func valueIndex(v ref, i int) ref
 
 // SetIndex sets the JavaScript index i of value v to ValueOf(x).
@@ -345,6 +351,7 @@ func (v Value) SetIndex(i int, x any) {
        runtime.KeepAlive(xv)
 }
 
+//go:wasmimport gojs syscall/js.valueSetIndex
 func valueSetIndex(v ref, i int, x ref)
 
 func makeArgs(args []any) ([]Value, []ref) {
@@ -369,6 +376,7 @@ func (v Value) Length() int {
        return r
 }
 
+//go:wasmimport gojs syscall/js.valueLength
 func valueLength(v ref) int
 
 // Call does a JavaScript call to the method m of value v with the given arguments.
@@ -391,6 +399,8 @@ func (v Value) Call(m string, args ...any) Value {
        return makeValue(res)
 }
 
+//go:wasmimport gojs syscall/js.valueCall
+//go:nosplit
 func valueCall(v ref, m string, args []ref) (ref, bool)
 
 // Invoke does a JavaScript call of the value v with the given arguments.
@@ -410,6 +420,7 @@ func (v Value) Invoke(args ...any) Value {
        return makeValue(res)
 }
 
+//go:wasmimport gojs syscall/js.valueInvoke
 func valueInvoke(v ref, args []ref) (ref, bool)
 
 // New uses JavaScript's "new" operator with value v as constructor and the given arguments.
@@ -429,6 +440,7 @@ func (v Value) New(args ...any) Value {
        return makeValue(res)
 }
 
+//go:wasmimport gojs syscall/js.valueNew
 func valueNew(v ref, args []ref) (ref, bool)
 
 func (v Value) isNumber() bool {
@@ -528,8 +540,10 @@ func jsString(v Value) string {
        return string(b)
 }
 
+//go:wasmimport gojs syscall/js.valuePrepareString
 func valuePrepareString(v ref) (ref, int)
 
+//go:wasmimport gojs syscall/js.valueLoadString
 func valueLoadString(v ref, b []byte)
 
 // InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator.
@@ -540,6 +554,7 @@ func (v Value) InstanceOf(t Value) bool {
        return r
 }
 
+//go:wasmimport gojs syscall/js.valueInstanceOf
 func valueInstanceOf(v ref, t ref) bool
 
 // A ValueError occurs when a Value method is invoked on
@@ -566,6 +581,7 @@ func CopyBytesToGo(dst []byte, src Value) int {
        return n
 }
 
+//go:wasmimport gojs syscall/js.copyBytesToGo
 func copyBytesToGo(dst []byte, src ref) (int, bool)
 
 // CopyBytesToJS copies bytes from src to dst.
@@ -580,4 +596,5 @@ func CopyBytesToJS(dst Value, src []byte) int {
        return n
 }
 
+//go:wasmimport gojs syscall/js.copyBytesToJS
 func copyBytesToJS(dst ref, src []byte) (int, bool)
index 47ad6b83e56398d0e5fe9bb47e564fcfeffd10d9..abdccc9cb0cec76afb5b2b21b11a59d7c4abe5e1 100644 (file)
@@ -2,68 +2,6 @@
 // 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
+// The runtime package uses //go:linkname to push the setEventHandler to this
+// package.  To prevent the go tool from passing -complete to the compile tool,
+// this file must remain stubbed out.
index f860a5bb50581bddc428f2417933652004ba94b1..8823421b894bd05fe5ad58cb5a77fa5c56052fba 100644 (file)
@@ -44,6 +44,18 @@ var dummys = js.Global().Call("eval", `({
        objBooleanFalse: new Boolean(false),
 })`)
 
+//go:wasmimport _gotest add
+func testAdd(uint32, uint32) uint32
+
+func TestWasmImport(t *testing.T) {
+       a := uint32(3)
+       b := uint32(5)
+       want := a + b
+       if got := testAdd(a, b); got != want {
+               t.Errorf("got %v, want %v", got, want)
+       }
+}
+
 func TestBool(t *testing.T) {
        want := true
        o := dummys.Get("someBool")