Skip to content

Commit 0d6ebcb

Browse files
decibelcoopervedantroyneelance
authored andcommitted
cmd/compile: replace CallImport with go:wasmimport directive
This change replaces the special assembler instruction CallImport of the wasm architecture with a new go:wasmimport directive. This new directive is cleaner and has more flexibility with regards to how parameters get passed to WebAssembly function imports. This is a preparation for adding support for wasi (WebAssembly System Interface). The default mode of the directive passes Go parameters as individual WebAssembly parameters. This mode will be used with wasi. The second mode "abi0" only passes the current SP as a single parameter. The called function then reads its arguments from memory. This is the method currently used by wasm_exec.js and the goal is to eventually remove this mode. * Fixes golang#38248 Co-authored-by: Vedant Roy <[email protected]> Co-authored-by: Richard Musiol <[email protected]> Co-authored-by: David Blyth <[email protected]> Change-Id: I2baee4cca5d6c6ecfa26042a5aa233e33ea6f06f
1 parent 245e95d commit 0d6ebcb

31 files changed

+525
-146
lines changed

misc/wasm/go_js_wasm_exec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/usr/bin/env bash
22
# Copyright 2018 The Go Authors. All rights reserved.
33
# Use of this source code is governed by a BSD-style
44
# license that can be found in the LICENSE file.

misc/wasm/wasm_exec.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@
206206

207207
const timeOrigin = Date.now() - performance.now();
208208
this.importObject = {
209+
_gotest: {
210+
add: (a, b) => a + b,
211+
},
209212
go: {
210213
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
211214
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported

src/cmd/compile/internal/gc/compile.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ func enqueueFunc(fn *ir.Func) {
4242
return // we'll get this as part of its enclosing function
4343
}
4444

45+
if ssagen.CreateWasmImportWrapper(fn) {
46+
return
47+
}
48+
4549
if len(fn.Body) == 0 {
4650
// Initialize ABI wrappers if necessary.
4751
ir.InitLSym(fn, false)

src/cmd/compile/internal/ir/func.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ type Func struct {
133133
// For wrapper functions, WrappedFunc point to the original Func.
134134
// Currently only used for go/defer wrappers.
135135
WrappedFunc *Func
136+
137+
// WasmImport is used by the //go:wasmimport directive to store info about
138+
// a WebAssembly function import.
139+
WasmImport *WasmImport
136140
}
137141

138142
func NewFunc(pos src.XPos) *Func {

src/cmd/compile/internal/ir/node.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,12 @@ const (
462462

463463
)
464464

465+
// WasmImport stores metadata associated with the //go:wasmimport pragma
466+
type WasmImport struct {
467+
Module string
468+
Name string
469+
}
470+
465471
func AsNode(n types.Object) Node {
466472
if n == nil {
467473
return nil

src/cmd/compile/internal/ir/sizeof_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ func TestSizeof(t *testing.T) {
2020
_32bit uintptr // size on 32bit platforms
2121
_64bit uintptr // size on 64bit platforms
2222
}{
23-
{Func{}, 184, 320},
24-
{Name{}, 100, 176},
23+
{Func{}, 196, 328},
24+
{Name{}, 112, 176},
2525
}
2626

2727
for _, tt := range tests {

src/cmd/compile/internal/noder/decl.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,22 @@ func (g *irgen) funcDecl(out *ir.Nodes, decl *syntax.FuncDecl) {
125125
}
126126
}
127127

128+
if p, ok := decl.Pragma.(*pragmas); ok && p.WasmImport != nil {
129+
if decl.Body != nil {
130+
base.ErrorfAt(fn.Pos(), "can only use //go:wasmimport with external func implementations")
131+
}
132+
name := typecheck.Lookup(decl.Name.Value).Def.(*ir.Name)
133+
f := name.Defn.(*ir.Func)
134+
f.WasmImport = &ir.WasmImport{
135+
Module: p.WasmImport.Module,
136+
Name: p.WasmImport.Name,
137+
}
138+
// While functions annotated with //go:wasmimport are
139+
// bodyless, the compiler generates a WebAssembly body for
140+
// them. However, the body will never grow the Go stack.
141+
f.Pragma |= ir.Nosplit
142+
}
143+
128144
if decl.Body != nil {
129145
if fn.Pragma&ir.Noescape != 0 {
130146
base.ErrorfAt(fn.Pos(), "can only use //go:noescape with external func implementations")
@@ -349,4 +365,7 @@ func (g *irgen) reportUnused(pragma *pragmas) {
349365
base.ErrorfAt(g.makeXPos(e.Pos), "misplaced go:embed directive")
350366
}
351367
}
368+
if pragma.WasmImport != nil {
369+
base.ErrorfAt(g.makeXPos(pragma.WasmImport.Pos), "misplaced go:wasmimport directive")
370+
}
352371
}

src/cmd/compile/internal/noder/irgen.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ Outer:
368368
if base.Flag.Complete {
369369
for _, n := range g.target.Decls {
370370
if fn, ok := n.(*ir.Func); ok {
371-
if fn.Body == nil && fn.Nname.Sym().Linkname == "" {
371+
if fn.Body == nil && fn.Nname.Sym().Linkname == "" && fn.WasmImport == nil {
372372
base.ErrorfAt(fn.Pos(), "missing function body")
373373
}
374374
}

src/cmd/compile/internal/noder/noder.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package noder
77
import (
88
"errors"
99
"fmt"
10+
"internal/buildcfg"
1011
"os"
1112
"path/filepath"
1213
"runtime"
@@ -219,9 +220,17 @@ var allowedStdPragmas = map[string]bool{
219220

220221
// *pragmas is the value stored in a syntax.pragmas during parsing.
221222
type pragmas struct {
222-
Flag ir.PragmaFlag // collected bits
223-
Pos []pragmaPos // position of each individual flag
224-
Embeds []pragmaEmbed
223+
Flag ir.PragmaFlag // collected bits
224+
Pos []pragmaPos // position of each individual flag
225+
Embeds []pragmaEmbed
226+
WasmImport *WasmImport
227+
}
228+
229+
// WasmImport stores metadata associated with the //go:wasmimport pragma
230+
type WasmImport struct {
231+
Pos syntax.Pos
232+
Module string
233+
Name string
225234
}
226235

227236
type pragmaPos struct {
@@ -245,6 +254,9 @@ func (p *noder) checkUnusedDuringParse(pragma *pragmas) {
245254
p.error(syntax.Error{Pos: e.Pos, Msg: "misplaced go:embed directive"})
246255
}
247256
}
257+
if pragma.WasmImport != nil {
258+
p.error(syntax.Error{Pos: pragma.WasmImport.Pos, Msg: "misplaced go:wasmimport directive"})
259+
}
248260
}
249261

250262
// pragma is called concurrently if files are parsed concurrently.
@@ -272,6 +284,22 @@ func (p *noder) pragma(pos syntax.Pos, blankLine bool, text string, old syntax.P
272284
}
273285

274286
switch {
287+
case strings.HasPrefix(text, "go:wasmimport "):
288+
if buildcfg.GOARCH == "wasm" {
289+
f := strings.Fields(text)
290+
if len(f) != 3 {
291+
p.error(syntax.Error{Pos: pos, Msg: "usage: //go:wasmimport module_name import_name"})
292+
}
293+
if !base.Flag.CompilingRuntime && base.Ctxt.Pkgpath != "syscall/js" && base.Ctxt.Pkgpath != "syscall/js_test" {
294+
p.error(syntax.Error{Pos: pos, Msg: "//go:wasmimport directive cannot be used outside of runtime or syscall/js"})
295+
}
296+
pragma.WasmImport = &WasmImport{
297+
Pos: pos,
298+
Module: f[1],
299+
Name: f[2],
300+
}
301+
}
302+
275303
case strings.HasPrefix(text, "go:linkname "):
276304
f := strings.Fields(text)
277305
if !(2 <= len(f) && len(f) <= 3) {

src/cmd/compile/internal/noder/writer.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1003,11 +1003,15 @@ func (w *writer) funcExt(obj *types2.Func) {
10031003
if pragma&ir.Systemstack != 0 && pragma&ir.Nosplit != 0 {
10041004
w.p.errorf(decl, "go:nosplit and go:systemstack cannot be combined")
10051005
}
1006+
wi := asWasmImport(decl.Pragma)
10061007

10071008
if decl.Body != nil {
10081009
if pragma&ir.Noescape != 0 {
10091010
w.p.errorf(decl, "can only use //go:noescape with external func implementations")
10101011
}
1012+
if wi != nil {
1013+
w.p.errorf(decl, "can only use //go:wasmimport with external func implementations")
1014+
}
10111015
if (pragma&ir.UintptrKeepAlive != 0 && pragma&ir.UintptrEscapes == 0) && pragma&ir.Nosplit == 0 {
10121016
// Stack growth can't handle uintptr arguments that may
10131017
// be pointers (as we don't know which are pointers
@@ -1028,7 +1032,8 @@ func (w *writer) funcExt(obj *types2.Func) {
10281032
if base.Flag.Complete || decl.Name.Value == "init" {
10291033
// Linknamed functions are allowed to have no body. Hopefully
10301034
// the linkname target has a body. See issue 23311.
1031-
if _, ok := w.p.linknames[obj]; !ok {
1035+
// Wasmimport functions are also allowed to have no body.
1036+
if _, ok := w.p.linknames[obj]; !ok && wi == nil {
10321037
w.p.errorf(decl, "missing function body")
10331038
}
10341039
}
@@ -2728,6 +2733,13 @@ func asPragmaFlag(p syntax.Pragma) ir.PragmaFlag {
27282733
return p.(*pragmas).Flag
27292734
}
27302735

2736+
func asWasmImport(p syntax.Pragma) *WasmImport {
2737+
if p == nil {
2738+
return nil
2739+
}
2740+
return p.(*pragmas).WasmImport
2741+
}
2742+
27312743
// isPtrTo reports whether from is the type *to.
27322744
func isPtrTo(from, to types2.Type) bool {
27332745
ptr, ok := from.(*types2.Pointer)

src/cmd/compile/internal/ssagen/abi.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import (
1111
"os"
1212
"strings"
1313

14+
"cmd/compile/internal/abi"
1415
"cmd/compile/internal/base"
1516
"cmd/compile/internal/ir"
17+
"cmd/compile/internal/objw"
1618
"cmd/compile/internal/typecheck"
1719
"cmd/compile/internal/types"
1820
"cmd/internal/obj"
@@ -339,3 +341,129 @@ func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) {
339341
typecheck.DeclContext = savedclcontext
340342
ir.CurFunc = savedcurfn
341343
}
344+
345+
// CreateWasmImportWrapper creates a wrapper for imported WASM functions to
346+
// adapt them to the Go calling convention. The body for this function is
347+
// generated in cmd/internal/obj/wasm/wasmobj.go
348+
func CreateWasmImportWrapper(fn *ir.Func) bool {
349+
if fn.WasmImport == nil {
350+
return false
351+
}
352+
if buildcfg.GOARCH != "wasm" {
353+
base.FatalfAt(fn.Pos(), "CreateWasmImportWrapper call not supported on %s: func was %v", buildcfg.GOARCH, fn)
354+
}
355+
356+
ir.InitLSym(fn, true)
357+
358+
pp := objw.NewProgs(fn, 0)
359+
defer pp.Free()
360+
pp.Text.To.Type = obj.TYPE_TEXTSIZE
361+
pp.Text.To.Val = int32(types.RoundUp(fn.Type().ArgWidth(), int64(types.RegSize)))
362+
// Wrapper functions never need their own stack frame
363+
pp.Text.To.Offset = 0
364+
pp.Flush()
365+
366+
return true
367+
}
368+
369+
func toWasmFields(result *abi.ABIParamResultInfo, abiParams []abi.ABIParamAssignment) []obj.WasmField {
370+
wfs := make([]obj.WasmField, len(abiParams))
371+
for i, p := range abiParams {
372+
t := p.Type
373+
switch {
374+
case t.IsInteger() && t.Size() == 4:
375+
wfs[i].Type = obj.WasmI32
376+
case t.IsInteger() && t.Size() == 8:
377+
wfs[i].Type = obj.WasmI64
378+
case t.IsFloat() && t.Size() == 4:
379+
wfs[i].Type = obj.WasmF32
380+
case t.IsFloat() && t.Size() == 8:
381+
wfs[i].Type = obj.WasmF64
382+
case t.IsPtr():
383+
wfs[i].Type = obj.WasmPtr
384+
default:
385+
base.Fatalf("wasm import has bad function signature")
386+
}
387+
wfs[i].Offset = p.FrameOffset(result)
388+
}
389+
return wfs
390+
}
391+
392+
// setupTextLSym initializes the LSym for a with-body text symbol.
393+
func setupTextLSym(f *ir.Func, flag int) {
394+
if f.Dupok() {
395+
flag |= obj.DUPOK
396+
}
397+
if f.Wrapper() {
398+
flag |= obj.WRAPPER
399+
}
400+
if f.ABIWrapper() {
401+
flag |= obj.ABIWRAPPER
402+
}
403+
if f.Needctxt() {
404+
flag |= obj.NEEDCTXT
405+
}
406+
if f.Pragma&ir.Nosplit != 0 {
407+
flag |= obj.NOSPLIT
408+
}
409+
if f.ReflectMethod() {
410+
flag |= obj.REFLECTMETHOD
411+
}
412+
413+
// Clumsy but important.
414+
// For functions that could be on the path of invoking a deferred
415+
// function that can recover (runtime.reflectcall, reflect.callReflect,
416+
// and reflect.callMethod), we want the panic+recover special handling.
417+
// See test/recover.go for test cases and src/reflect/value.go
418+
// for the actual functions being considered.
419+
//
420+
// runtime.reflectcall is an assembly function which tailcalls
421+
// WRAPPER functions (runtime.callNN). Its ABI wrapper needs WRAPPER
422+
// flag as well.
423+
fnname := f.Sym().Name
424+
if base.Ctxt.Pkgpath == "runtime" && fnname == "reflectcall" {
425+
flag |= obj.WRAPPER
426+
} else if base.Ctxt.Pkgpath == "reflect" {
427+
switch fnname {
428+
case "callReflect", "callMethod":
429+
flag |= obj.WRAPPER
430+
}
431+
}
432+
433+
base.Ctxt.InitTextSym(f.LSym, flag, f.Pos())
434+
435+
if f.WasmImport != nil {
436+
wi := obj.WasmImport{
437+
Module: f.WasmImport.Module,
438+
Name: f.WasmImport.Name,
439+
}
440+
if wi.Module == "go" {
441+
// Functions that are imported from the "go" module use a special
442+
// ABI that just accepts the stack pointer.
443+
// Example:
444+
//
445+
// //go:wasmimport go add
446+
// func importedAdd(a, b uint) uint
447+
//
448+
// will roughly become
449+
//
450+
// (import "go" "add" (func (param i32)))
451+
wi.Params = []obj.WasmField{{Type: obj.WasmI32}}
452+
} else {
453+
// All other imported functions use the normal WASM ABI.
454+
// Example:
455+
//
456+
// //go:wasmimport a_module add
457+
// func importedAdd(a, b uint) uint
458+
//
459+
// will roughly become
460+
//
461+
// (import "a_module" "add" (func (param i32 i32) (result i32)))
462+
abiConfig := AbiForBodylessFuncStackMap(f)
463+
abiInfo := abiConfig.ABIAnalyzeFuncType(f.Type().FuncType())
464+
wi.Params = toWasmFields(abiInfo, abiInfo.InParams())
465+
wi.Results = toWasmFields(abiInfo, abiInfo.OutParams())
466+
}
467+
f.LSym.Func().WasmImport = &wi
468+
}
469+
}

src/cmd/internal/goobj/objfile.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ const (
440440
AuxPcline
441441
AuxPcinline
442442
AuxPcdata
443+
AuxWasmImport
443444
)
444445

445446
func (a *Aux) Type() uint8 { return a[0] }

0 commit comments

Comments
 (0)