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);
|
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 getInt64 = (addr) => {
|
||||||
const low = this.mem.getUint32(addr + 0, true);
|
const low = this.mem.getUint32(addr + 0, true);
|
||||||
const high = this.mem.getInt32(addr + 4, true);
|
const high = this.mem.getInt32(addr + 4, true);
|
||||||
|
|
@ -206,7 +210,10 @@
|
||||||
|
|
||||||
const timeOrigin = Date.now() - performance.now();
|
const timeOrigin = Date.now() - performance.now();
|
||||||
this.importObject = {
|
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)
|
// 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
|
// 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).
|
// 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
|
return // we'll get this as part of its enclosing function
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ssagen.CreateWasmImportWrapper(fn) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if len(fn.Body) == 0 {
|
if len(fn.Body) == 0 {
|
||||||
// Initialize ABI wrappers if necessary.
|
// Initialize ABI wrappers if necessary.
|
||||||
ir.InitLSym(fn, false)
|
ir.InitLSym(fn, false)
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,16 @@ type Func struct {
|
||||||
// For wrapper functions, WrappedFunc point to the original Func.
|
// For wrapper functions, WrappedFunc point to the original Func.
|
||||||
// Currently only used for go/defer wrappers.
|
// Currently only used for go/defer wrappers.
|
||||||
WrappedFunc *Func
|
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 {
|
func NewFunc(pos src.XPos) *Func {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ func TestSizeof(t *testing.T) {
|
||||||
_32bit uintptr // size on 32bit platforms
|
_32bit uintptr // size on 32bit platforms
|
||||||
_64bit uintptr // size on 64bit platforms
|
_64bit uintptr // size on 64bit platforms
|
||||||
}{
|
}{
|
||||||
{Func{}, 184, 320},
|
{Func{}, 188, 328},
|
||||||
{Name{}, 100, 176},
|
{Name{}, 100, 176},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
package noder
|
package noder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"internal/buildcfg"
|
||||||
"internal/pkgbits"
|
"internal/pkgbits"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
|
@ -269,6 +270,16 @@ func (l *linker) relocFuncExt(w *pkgbits.Encoder, name *ir.Name) {
|
||||||
l.pragmaFlag(w, name.Func.Pragma)
|
l.pragmaFlag(w, name.Func.Pragma)
|
||||||
l.linkname(w, name)
|
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.
|
// Relocated extension data.
|
||||||
w.Bool(true)
|
w.Bool(true)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ package noder
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"internal/buildcfg"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
@ -169,6 +170,14 @@ type pragmas struct {
|
||||||
Flag ir.PragmaFlag // collected bits
|
Flag ir.PragmaFlag // collected bits
|
||||||
Pos []pragmaPos // position of each individual flag
|
Pos []pragmaPos // position of each individual flag
|
||||||
Embeds []pragmaEmbed
|
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 {
|
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"})
|
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.
|
// 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 {
|
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 "):
|
case strings.HasPrefix(text, "go:linkname "):
|
||||||
f := strings.Fields(text)
|
f := strings.Fields(text)
|
||||||
if !(2 <= len(f) && len(f) <= 3) {
|
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()
|
fn.Pragma = r.pragmaFlag()
|
||||||
r.linkname(name)
|
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)
|
typecheck.Func(fn)
|
||||||
|
|
||||||
if r.Bool() {
|
if r.Bool() {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ package noder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"internal/buildcfg"
|
||||||
"internal/pkgbits"
|
"internal/pkgbits"
|
||||||
|
|
||||||
"cmd/compile/internal/base"
|
"cmd/compile/internal/base"
|
||||||
|
|
@ -1003,11 +1004,15 @@ func (w *writer) funcExt(obj *types2.Func) {
|
||||||
if pragma&ir.Systemstack != 0 && pragma&ir.Nosplit != 0 {
|
if pragma&ir.Systemstack != 0 && pragma&ir.Nosplit != 0 {
|
||||||
w.p.errorf(decl, "go:nosplit and go:systemstack cannot be combined")
|
w.p.errorf(decl, "go:nosplit and go:systemstack cannot be combined")
|
||||||
}
|
}
|
||||||
|
wi := asWasmImport(decl.Pragma)
|
||||||
|
|
||||||
if decl.Body != nil {
|
if decl.Body != nil {
|
||||||
if pragma&ir.Noescape != 0 {
|
if pragma&ir.Noescape != 0 {
|
||||||
w.p.errorf(decl, "can only use //go:noescape with external func implementations")
|
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 {
|
if (pragma&ir.UintptrKeepAlive != 0 && pragma&ir.UintptrEscapes == 0) && pragma&ir.Nosplit == 0 {
|
||||||
// Stack growth can't handle uintptr arguments that may
|
// Stack growth can't handle uintptr arguments that may
|
||||||
// be pointers (as we don't know which are pointers
|
// 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" {
|
if base.Flag.Complete || decl.Name.Value == "init" {
|
||||||
// Linknamed functions are allowed to have no body. Hopefully
|
// Linknamed functions are allowed to have no body. Hopefully
|
||||||
// the linkname target has a body. See issue 23311.
|
// 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")
|
w.p.errorf(decl, "missing function body")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1041,6 +1047,17 @@ func (w *writer) funcExt(obj *types2.Func) {
|
||||||
w.Sync(pkgbits.SyncFuncExt)
|
w.Sync(pkgbits.SyncFuncExt)
|
||||||
w.pragmaFlag(pragma)
|
w.pragmaFlag(pragma)
|
||||||
w.linkname(obj)
|
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.Bool(false) // stub extension
|
||||||
w.Reloc(pkgbits.RelocBody, body)
|
w.Reloc(pkgbits.RelocBody, body)
|
||||||
w.Sync(pkgbits.SyncEOF)
|
w.Sync(pkgbits.SyncEOF)
|
||||||
|
|
@ -2728,6 +2745,13 @@ func asPragmaFlag(p syntax.Pragma) ir.PragmaFlag {
|
||||||
return p.(*pragmas).Flag
|
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.
|
// isPtrTo reports whether from is the type *to.
|
||||||
func isPtrTo(from, to types2.Type) bool {
|
func isPtrTo(from, to types2.Type) bool {
|
||||||
ptr, ok := from.(*types2.Pointer)
|
ptr, ok := from.(*types2.Pointer)
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,14 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"cmd/compile/internal/abi"
|
||||||
"cmd/compile/internal/base"
|
"cmd/compile/internal/base"
|
||||||
"cmd/compile/internal/ir"
|
"cmd/compile/internal/ir"
|
||||||
|
"cmd/compile/internal/objw"
|
||||||
"cmd/compile/internal/typecheck"
|
"cmd/compile/internal/typecheck"
|
||||||
"cmd/compile/internal/types"
|
"cmd/compile/internal/types"
|
||||||
"cmd/internal/obj"
|
"cmd/internal/obj"
|
||||||
|
"cmd/internal/obj/wasm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SymABIs records information provided by the assembler about symbol
|
// SymABIs records information provided by the assembler about symbol
|
||||||
|
|
@ -336,3 +339,88 @@ func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) {
|
||||||
typecheck.DeclContext = savedclcontext
|
typecheck.DeclContext = savedclcontext
|
||||||
ir.CurFunc = savedcurfn
|
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
|
AuxPcline
|
||||||
AuxPcinline
|
AuxPcinline
|
||||||
AuxPcdata
|
AuxPcdata
|
||||||
|
AuxWasmImport
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *Aux) Type() uint8 { return a[0] }
|
func (a *Aux) Type() uint8 { return a[0] }
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import (
|
||||||
"cmd/internal/objabi"
|
"cmd/internal/objabi"
|
||||||
"cmd/internal/src"
|
"cmd/internal/src"
|
||||||
"cmd/internal/sys"
|
"cmd/internal/sys"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
@ -500,6 +501,8 @@ type FuncInfo struct {
|
||||||
JumpTables []JumpTable
|
JumpTables []JumpTable
|
||||||
|
|
||||||
FuncInfoSym *LSym
|
FuncInfoSym *LSym
|
||||||
|
WasmImportSym *LSym
|
||||||
|
WasmImport *WasmImport
|
||||||
}
|
}
|
||||||
|
|
||||||
// JumpTable represents a table used for implementing multi-way
|
// JumpTable represents a table used for implementing multi-way
|
||||||
|
|
@ -558,6 +561,75 @@ func (s *LSym) File() *FileInfo {
|
||||||
return f
|
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 {
|
type InlMark struct {
|
||||||
// When unwinding from an instruction in an inlined body, mark
|
// When unwinding from an instruction in an inlined body, mark
|
||||||
// where we should unwind to.
|
// where we should unwind to.
|
||||||
|
|
|
||||||
|
|
@ -605,7 +605,12 @@ func (w *writer) Aux(s *LSym) {
|
||||||
for _, pcSym := range fn.Pcln.Pcdata {
|
for _, pcSym := range fn.Pcln.Pcdata {
|
||||||
w.aux1(goobj.AuxPcdata, pcSym)
|
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++
|
||||||
}
|
}
|
||||||
n += len(fn.Pcln.Pcdata)
|
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
|
return n
|
||||||
}
|
}
|
||||||
|
|
@ -759,8 +770,8 @@ func genFuncInfoSyms(ctxt *Link) {
|
||||||
fn.FuncInfoSym = isym
|
fn.FuncInfoSym = isym
|
||||||
b.Reset()
|
b.Reset()
|
||||||
|
|
||||||
dwsyms := []*LSym{fn.dwarfRangesSym, fn.dwarfLocSym, fn.dwarfDebugLinesSym, fn.dwarfInfoSym}
|
auxsyms := []*LSym{fn.dwarfRangesSym, fn.dwarfLocSym, fn.dwarfDebugLinesSym, fn.dwarfInfoSym, fn.WasmImportSym}
|
||||||
for _, s := range dwsyms {
|
for _, s := range auxsyms {
|
||||||
if s == nil || s.Size == 0 {
|
if s == nil || s.Size == 0 {
|
||||||
continue
|
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}
|
auxsyms := []*LSym{fninfo.dwarfRangesSym, fninfo.dwarfLocSym, fninfo.dwarfDebugLinesSym, fninfo.dwarfInfoSym, fninfo.WasmImportSym}
|
||||||
for _, dws := range dwsyms {
|
for _, s := range auxsyms {
|
||||||
if dws == nil || dws.Size == 0 {
|
if s == nil || s.Size == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fn(fsym, dws)
|
fn(fsym, s)
|
||||||
if flag&traverseRefs != 0 {
|
if flag&traverseRefs != 0 {
|
||||||
for _, r := range dws.R {
|
for _, r := range s.R {
|
||||||
if r.Sym != nil {
|
if r.Sym != nil {
|
||||||
fn(dws, r.Sym)
|
fn(s, r.Sym)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,7 @@ const (
|
||||||
* wasm
|
* wasm
|
||||||
*/
|
*/
|
||||||
const (
|
const (
|
||||||
ACallImport = obj.ABaseWasm + obj.A_ARCHSPECIFIC + iota
|
AGet = obj.ABaseWasm + obj.A_ARCHSPECIFIC + iota
|
||||||
AGet
|
|
||||||
ASet
|
ASet
|
||||||
ATee
|
ATee
|
||||||
ANot // alias for I32Eqz
|
ANot // alias for I32Eqz
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,7 @@ package wasm
|
||||||
import "cmd/internal/obj"
|
import "cmd/internal/obj"
|
||||||
|
|
||||||
var Anames = []string{
|
var Anames = []string{
|
||||||
obj.A_ARCHSPECIFIC: "CallImport",
|
obj.A_ARCHSPECIFIC: "Get",
|
||||||
"Get",
|
|
||||||
"Set",
|
"Set",
|
||||||
"Tee",
|
"Tee",
|
||||||
"Not",
|
"Not",
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,6 @@ var unaryDst = map[obj.As]bool{
|
||||||
ATee: true,
|
ATee: true,
|
||||||
ACall: true,
|
ACall: true,
|
||||||
ACallIndirect: true,
|
ACallIndirect: true,
|
||||||
ACallImport: true,
|
|
||||||
ABr: true,
|
ABr: true,
|
||||||
ABrIf: true,
|
ABrIf: true,
|
||||||
ABrTable: true,
|
ABrTable: true,
|
||||||
|
|
@ -135,6 +134,14 @@ const (
|
||||||
WasmImport = 1 << 0
|
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) {
|
func instinit(ctxt *obj.Link) {
|
||||||
morestack = ctxt.Lookup("runtime.morestack")
|
morestack = ctxt.Lookup("runtime.morestack")
|
||||||
morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt")
|
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().Args = s.Func().Text.To.Val.(int32)
|
||||||
s.Func().Locals = int32(framesize)
|
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 {
|
// if g._panic != nil && g._panic.argp == FP {
|
||||||
// g._panic.argp = bottom-of-frame
|
// 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)
|
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
|
// If the maymorestack debug option is enabled, insert the
|
||||||
// call to maymorestack *before* processing resume points so
|
// call to maymorestack *before* processing resume points so
|
||||||
|
|
@ -707,12 +830,6 @@ func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
|
||||||
default:
|
default:
|
||||||
panic("bad MOV type")
|
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}
|
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
|
// GetFuncDwarfAuxSyms collects and returns the auxiliary DWARF
|
||||||
// symbols associated with a given function symbol. Prior to the
|
// symbols associated with a given function symbol. Prior to the
|
||||||
// introduction of the loader, this was done purely using name
|
// introduction of the loader, this was done purely using name
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,14 @@ package wasm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"cmd/internal/obj"
|
||||||
|
"cmd/internal/obj/wasm"
|
||||||
"cmd/internal/objabi"
|
"cmd/internal/objabi"
|
||||||
"cmd/link/internal/ld"
|
"cmd/link/internal/ld"
|
||||||
"cmd/link/internal/loader"
|
"cmd/link/internal/loader"
|
||||||
"cmd/link/internal/sym"
|
"cmd/link/internal/sym"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"internal/buildcfg"
|
"internal/buildcfg"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
@ -44,6 +48,7 @@ func gentext(ctxt *ld.Link, ldr *loader.Loader) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type wasmFunc struct {
|
type wasmFunc struct {
|
||||||
|
Module string
|
||||||
Name string
|
Name string
|
||||||
Type uint32
|
Type uint32
|
||||||
Code []byte
|
Code []byte
|
||||||
|
|
@ -54,6 +59,59 @@ type wasmFuncType struct {
|
||||||
Results []byte
|
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{
|
var wasmFuncTypes = map[string]*wasmFuncType{
|
||||||
"_rt0_wasm_js": {Params: []byte{}}, //
|
"_rt0_wasm_js": {Params: []byte{}}, //
|
||||||
"wasm_export_run": {Params: []byte{I32, I32}}, // argc, argv
|
"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)
|
// collect host imports (functions that get imported from the WebAssembly host, usually JavaScript)
|
||||||
hostImports := []*wasmFunc{
|
// we store the import index of each imported function, so the R_WASMIMPORT relocation
|
||||||
{
|
// can write the correct index after a "call" instruction
|
||||||
Name: "debug",
|
// these are added as import statements to the top of the WebAssembly binary
|
||||||
Type: lookupType(&wasmFuncType{Params: []byte{I32}}, &types),
|
var hostImports []*wasmFunc
|
||||||
},
|
|
||||||
}
|
|
||||||
hostImportMap := make(map[loader.Sym]int64)
|
hostImportMap := make(map[loader.Sym]int64)
|
||||||
for _, fn := range ctxt.Textp {
|
for _, fn := range ctxt.Textp {
|
||||||
relocs := ldr.Relocs(fn)
|
relocs := ldr.Relocs(fn)
|
||||||
for ri := 0; ri < relocs.Count(); ri++ {
|
for ri := 0; ri < relocs.Count(); ri++ {
|
||||||
r := relocs.At(ri)
|
r := relocs.At(ri)
|
||||||
if r.Type() == objabi.R_WASMIMPORT {
|
if r.Type() == objabi.R_WASMIMPORT {
|
||||||
hostImportMap[r.Sym()] = int64(len(hostImports))
|
if lsym, ok := ldr.WasmImportSym(fn); ok {
|
||||||
|
wi := readWasmImport(ldr, lsym)
|
||||||
|
hostImportMap[fn] = int64(len(hostImports))
|
||||||
hostImports = append(hostImports, &wasmFunc{
|
hostImports = append(hostImports, &wasmFunc{
|
||||||
Name: ldr.SymName(r.Sym()),
|
Module: wi.Module,
|
||||||
Type: lookupType(&wasmFuncType{Params: []byte{I32}}, &types),
|
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
|
writeUleb128(ctxt.Out, uint64(len(hostImports))) // number of imports
|
||||||
for _, fn := range hostImports {
|
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)
|
writeName(ctxt.Out, fn.Name)
|
||||||
ctxt.Out.WriteByte(0x00) // func import
|
ctxt.Out.WriteByte(0x00) // func import
|
||||||
writeUleb128(ctxt.Out, uint64(fn.Type))
|
writeUleb128(ctxt.Out, uint64(fn.Type))
|
||||||
|
|
@ -610,3 +679,22 @@ func writeSleb128(w io.ByteWriter, v int64) {
|
||||||
w.WriteByte(c)
|
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
|
package runtime
|
||||||
|
|
||||||
import (
|
import _ "unsafe" // for go:linkname
|
||||||
_ "unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// js/wasm has no support for threads yet. There is no preemption.
|
// 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.
|
// scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds.
|
||||||
// It returns a timer id that can be used with clearTimeoutEvent.
|
// It returns a timer id that can be used with clearTimeoutEvent.
|
||||||
|
//
|
||||||
|
//go:wasmimport gojs runtime.scheduleTimeoutEvent
|
||||||
func scheduleTimeoutEvent(ms int64) int32
|
func scheduleTimeoutEvent(ms int64) int32
|
||||||
|
|
||||||
// clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent.
|
// clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent.
|
||||||
|
//
|
||||||
|
//go:wasmimport gojs runtime.clearTimeoutEvent
|
||||||
func clearTimeoutEvent(id int32)
|
func clearTimeoutEvent(id int32)
|
||||||
|
|
||||||
// handleEvent gets invoked on a call from JavaScript into Go. It calls the event handler of the syscall/js package
|
// 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.
|
// 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.
|
// This allows the front-end to replace the old DataView object with a new one.
|
||||||
|
//
|
||||||
|
//go:wasmimport gojs runtime.resetMemoryDataView
|
||||||
func resetMemoryDataView()
|
func resetMemoryDataView()
|
||||||
|
|
||||||
func sysMapOS(v unsafe.Pointer, n uintptr) {
|
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 closefd(fd int32) int32 { panic("not implemented") }
|
||||||
func read(fd int32, p unsafe.Pointer, n 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
|
//go:noescape
|
||||||
func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
||||||
|
|
||||||
|
|
@ -117,6 +118,7 @@ func crash() {
|
||||||
*(*int32)(nil) = 0
|
*(*int32)(nil) = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs runtime.getRandomData
|
||||||
func getRandomData(r []byte)
|
func getRandomData(r []byte)
|
||||||
|
|
||||||
func goenvs() {
|
func goenvs() {
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,6 @@ TEXT runtime·pause(SB), NOSPLIT, $0-8
|
||||||
TEXT runtime·exit(SB), NOSPLIT, $0-4
|
TEXT runtime·exit(SB), NOSPLIT, $0-4
|
||||||
I32Const $0
|
I32Const $0
|
||||||
Call runtime·wasmExit(SB)
|
Call runtime·wasmExit(SB)
|
||||||
Drop
|
|
||||||
I32Const $1
|
I32Const $1
|
||||||
Set PAUSE
|
Set PAUSE
|
||||||
RETUNWIND
|
RETUNWIND
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,5 @@
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
|
//go:wasmimport gojs runtime.nanotime1
|
||||||
func nanotime1() int64
|
func nanotime1() int64
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ func wasmDiv()
|
||||||
func wasmTruncS()
|
func wasmTruncS()
|
||||||
func wasmTruncU()
|
func wasmTruncU()
|
||||||
|
|
||||||
|
//go:wasmimport gojs runtime.wasmExit
|
||||||
func wasmExit(code int32)
|
func wasmExit(code int32)
|
||||||
|
|
||||||
// adjust Gobuf as it if executed a call to fn with context ctxt
|
// adjust Gobuf as it if executed a call to fn with context ctxt
|
||||||
|
|
|
||||||
|
|
@ -101,35 +101,3 @@ TEXT runtime·growMemory(SB), NOSPLIT, $0
|
||||||
GrowMemory
|
GrowMemory
|
||||||
I32Store ret+8(FP)
|
I32Store ret+8(FP)
|
||||||
RET
|
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
|
package runtime
|
||||||
|
|
||||||
|
//go:wasmimport gojs runtime.walltime
|
||||||
func walltime() (sec int64, nsec int32)
|
func walltime() (sec int64, nsec int32)
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ func makeValue(r ref) Value {
|
||||||
return Value{ref: r, gcPtr: gcPtr}
|
return Value{ref: r, gcPtr: gcPtr}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.finalizeRef
|
||||||
func finalizeRef(r ref)
|
func finalizeRef(r ref)
|
||||||
|
|
||||||
func predefValue(id uint32, typeFlag byte) Value {
|
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
|
func stringVal(x string) ref
|
||||||
|
|
||||||
// Type represents the JavaScript type of a Value.
|
// Type represents the JavaScript type of a Value.
|
||||||
|
|
@ -292,6 +294,7 @@ func (v Value) Get(p string) Value {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.valueGet
|
||||||
func valueGet(v ref, p string) ref
|
func valueGet(v ref, p string) ref
|
||||||
|
|
||||||
// Set sets the JavaScript property p of value v to ValueOf(x).
|
// 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)
|
runtime.KeepAlive(xv)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.valueSet
|
||||||
func valueSet(v ref, p string, x ref)
|
func valueSet(v ref, p string, x ref)
|
||||||
|
|
||||||
// Delete deletes the JavaScript property p of value v.
|
// Delete deletes the JavaScript property p of value v.
|
||||||
|
|
@ -318,6 +322,7 @@ func (v Value) Delete(p string) {
|
||||||
runtime.KeepAlive(v)
|
runtime.KeepAlive(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.valueDelete
|
||||||
func valueDelete(v ref, p string)
|
func valueDelete(v ref, p string)
|
||||||
|
|
||||||
// Index returns JavaScript index i of value v.
|
// Index returns JavaScript index i of value v.
|
||||||
|
|
@ -331,6 +336,7 @@ func (v Value) Index(i int) Value {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.valueIndex
|
||||||
func valueIndex(v ref, i int) ref
|
func valueIndex(v ref, i int) ref
|
||||||
|
|
||||||
// SetIndex sets the JavaScript index i of value v to ValueOf(x).
|
// 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)
|
runtime.KeepAlive(xv)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.valueSetIndex
|
||||||
func valueSetIndex(v ref, i int, x ref)
|
func valueSetIndex(v ref, i int, x ref)
|
||||||
|
|
||||||
func makeArgs(args []any) ([]Value, []ref) {
|
func makeArgs(args []any) ([]Value, []ref) {
|
||||||
|
|
@ -369,6 +376,7 @@ func (v Value) Length() int {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.valueLength
|
||||||
func valueLength(v ref) int
|
func valueLength(v ref) int
|
||||||
|
|
||||||
// Call does a JavaScript call to the method m of value v with the given arguments.
|
// 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)
|
return makeValue(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.valueCall
|
||||||
|
//go:nosplit
|
||||||
func valueCall(v ref, m string, args []ref) (ref, bool)
|
func valueCall(v ref, m string, args []ref) (ref, bool)
|
||||||
|
|
||||||
// Invoke does a JavaScript call of the value v with the given arguments.
|
// 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)
|
return makeValue(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.valueInvoke
|
||||||
func valueInvoke(v ref, args []ref) (ref, bool)
|
func valueInvoke(v ref, args []ref) (ref, bool)
|
||||||
|
|
||||||
// New uses JavaScript's "new" operator with value v as constructor and the given arguments.
|
// 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)
|
return makeValue(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.valueNew
|
||||||
func valueNew(v ref, args []ref) (ref, bool)
|
func valueNew(v ref, args []ref) (ref, bool)
|
||||||
|
|
||||||
func (v Value) isNumber() bool {
|
func (v Value) isNumber() bool {
|
||||||
|
|
@ -528,8 +540,10 @@ func jsString(v Value) string {
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.valuePrepareString
|
||||||
func valuePrepareString(v ref) (ref, int)
|
func valuePrepareString(v ref) (ref, int)
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.valueLoadString
|
||||||
func valueLoadString(v ref, b []byte)
|
func valueLoadString(v ref, b []byte)
|
||||||
|
|
||||||
// InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator.
|
// 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
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.valueInstanceOf
|
||||||
func valueInstanceOf(v ref, t ref) bool
|
func valueInstanceOf(v ref, t ref) bool
|
||||||
|
|
||||||
// A ValueError occurs when a Value method is invoked on
|
// A ValueError occurs when a Value method is invoked on
|
||||||
|
|
@ -566,6 +581,7 @@ func CopyBytesToGo(dst []byte, src Value) int {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.copyBytesToGo
|
||||||
func copyBytesToGo(dst []byte, src ref) (int, bool)
|
func copyBytesToGo(dst []byte, src ref) (int, bool)
|
||||||
|
|
||||||
// CopyBytesToJS copies bytes from src to dst.
|
// CopyBytesToJS copies bytes from src to dst.
|
||||||
|
|
@ -580,4 +596,5 @@ func CopyBytesToJS(dst Value, src []byte) int {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport gojs syscall/js.copyBytesToJS
|
||||||
func copyBytesToJS(dst ref, src []byte) (int, bool)
|
func copyBytesToJS(dst ref, src []byte) (int, bool)
|
||||||
|
|
|
||||||
|
|
@ -2,68 +2,6 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
#include "textflag.h"
|
// 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,
|
||||||
TEXT ·finalizeRef(SB), NOSPLIT, $0
|
// this file must remain stubbed out.
|
||||||
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
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,18 @@ var dummys = js.Global().Call("eval", `({
|
||||||
objBooleanFalse: new Boolean(false),
|
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) {
|
func TestBool(t *testing.T) {
|
||||||
want := true
|
want := true
|
||||||
o := dummys.Get("someBool")
|
o := dummys.Get("someBool")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue