mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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:
parent
af9f21289f
commit
02411bcd7c
29 changed files with 585 additions and 145 deletions
|
|
@ -113,6 +113,10 @@
|
|||
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);
|
||||
|
|
@ -206,7 +210,10 @@
|
|||
|
||||
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).
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -442,6 +442,7 @@ const (
|
|||
AuxPcline
|
||||
AuxPcinline
|
||||
AuxPcdata
|
||||
AuxWasmImport
|
||||
)
|
||||
|
||||
func (a *Aux) Type() uint8 { return a[0] }
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@
|
|||
|
||||
package runtime
|
||||
|
||||
//go:wasmimport gojs runtime.nanotime1
|
||||
func nanotime1() int64
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@
|
|||
|
||||
package runtime
|
||||
|
||||
//go:wasmimport gojs runtime.walltime
|
||||
func walltime() (sec int64, nsec int32)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue