Skip to content

Commit

Permalink
cmd/compile: replace CallImport with go:wasmimport directive
Browse files Browse the repository at this point in the history
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.

Change-Id: Ibf712348f20ed9a484e3ac22d318401425d9c168
  • Loading branch information
neelance committed Mar 19, 2020
1 parent a7f918c commit 40aec9a
Show file tree
Hide file tree
Showing 23 changed files with 306 additions and 160 deletions.
42 changes: 36 additions & 6 deletions src/cmd/compile/internal/gc/noder.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,12 @@ type noder struct {
base *src.PosBase
}

file *syntax.File
linknames []linkname
pragcgobuf [][]string
err chan syntax.Error
scope ScopeID
file *syntax.File
linknames []linkname
wasmimports []*wasmimport
pragcgobuf [][]string
err chan syntax.Error
scope ScopeID

// scopeVars is a stack tracking the number of variables declared in the
// current function at the moment each open scope was opened.
Expand Down Expand Up @@ -499,6 +500,16 @@ func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node {
yyerrorl(f.Pos, "go:nosplit and go:systemstack cannot be combined")
}

isWasmImport := false
for _, wi := range p.wasmimports {
if f.funcname() == wi.local {
f.Func.wasmimport = wi
f.Func.Pragma |= Noescape
isWasmImport = true
break
}
}

if fun.Recv == nil {
declare(f.Func.Nname, PFUNC)
}
Expand All @@ -520,7 +531,7 @@ func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node {
break
}
}
if !isLinknamed {
if !isLinknamed && !isWasmImport {
yyerrorl(f.Pos, "missing function body")
}
}
Expand Down Expand Up @@ -1503,6 +1514,25 @@ func (p *noder) pragma(pos syntax.Pos, text string) syntax.Pragma {
}
p.linknames = append(p.linknames, linkname{pos, f[1], target})

case strings.HasPrefix(text, "go:wasmimport "):
f := strings.Fields(text)
if !(4 <= len(f) && len(f) <= 5) {
p.error(syntax.Error{Pos: pos, Msg: "usage: //go:wasmimport localname importmodule importname [abi0]"})
break
}
abi0 := false
if len(f) == 5 {
flag := f[4]
switch flag {
case "abi0":
abi0 = true
default:
p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("invalid flag %q in wasmimport directive", flag)})
break
}
}
p.wasmimports = append(p.wasmimports, &wasmimport{local: f[1], module: f[2], name: f[3], abi0: abi0})

case strings.HasPrefix(text, "go:cgo_import_dynamic "):
// This is permitted for general use because Solaris
// code relies on it in golang.org/x/sys/unix and others.
Expand Down
56 changes: 56 additions & 0 deletions src/cmd/compile/internal/gc/pgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,39 @@ func funccompile(fn *Node) {
// assign parameter offsets
dowidth(fn.Type)

if fn.Func.wasmimport != nil {
lineno = fn.Pos
if fn.Nbody.Len() != 0 {
Fatalf("wasm import can not have a function body")
}

lsym := fn.Func.Nname.Sym.Linksym()
lsym.Type = objabi.STEXT
lsym.SetABI(obj.ABI0)
if fn.Func.wasmimport.abi0 {
lsym.Func = &obj.FuncInfo{
WasmImport: &obj.WasmImport{
Module: fn.Func.wasmimport.module,
Name: fn.Func.wasmimport.name,
Params: []obj.WasmField{{Type: obj.WasmI32}},
ABI0: true,
},
}
} else {
lsym.Func = &obj.FuncInfo{
WasmImport: &obj.WasmImport{
Module: fn.Func.wasmimport.module,
Name: fn.Func.wasmimport.name,
Params: toWasmFields(fn.Type.Params().FieldSlice()),
Results: toWasmFields(fn.Type.Results().FieldSlice()),
},
}
}
Ctxt.Data = append(Ctxt.Data, lsym)

return
}

if fn.Nbody.Len() == 0 {
// Initialize ABI wrappers if necessary.
fn.Func.initLSym(false)
Expand Down Expand Up @@ -772,3 +805,26 @@ type symByName []*types.Sym
func (a symByName) Len() int { return len(a) }
func (a symByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
func (a symByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

func toWasmFields(fields []*types.Field) []obj.WasmField {
wfs := make([]obj.WasmField, len(fields))
for i, f := range fields {
t := f.Type
switch {
case t.IsInteger() && t.Width == 4:
wfs[i].Type = obj.WasmI32
case t.IsInteger() && t.Width == 8:
wfs[i].Type = obj.WasmI64
case t.IsFloat() && t.Width == 4:
wfs[i].Type = obj.WasmF32
case t.IsFloat() && t.Width == 8:
wfs[i].Type = obj.WasmF64
case t.IsPtr():
wfs[i].Type = obj.WasmPtr
default:
Fatalf("wasm import has bad function signature")
}
wfs[i].Offset = f.Offset
}
return wfs
}
9 changes: 9 additions & 0 deletions src/cmd/compile/internal/gc/syntax.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,8 @@ type Func struct {
// function for go:nowritebarrierrec analysis. Only filled in
// if nowritebarrierrecCheck != nil.
nwbrCalls *[]nowritebarrierrecCallSym

wasmimport *wasmimport
}

// An Inline holds fields used for function bodies that can be inlined.
Expand All @@ -571,6 +573,13 @@ type Mark struct {
// A ScopeID represents a lexical scope within a function.
type ScopeID int32

type wasmimport struct {
local string
module string
name string
abi0 bool
}

const (
funcDupok = 1 << iota // duplicate definitions ok
funcWrapper // is method wrapper
Expand Down
27 changes: 27 additions & 0 deletions src/cmd/internal/obj/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,35 @@ type FuncInfo struct {
OpenCodedDeferInfo *LSym

FuncInfoSym *LSym

WasmImport *WasmImport
}

// WasmImport contains extra fields for wasm import functions.
type WasmImport struct {
Module string
Name string
Params []WasmField
Results []WasmField
ABI0 bool
}

// WasmField describes a parameter or result of a wasm import function.
type WasmField struct {
Type WasmFieldType
Offset int64
}

type WasmFieldType byte

const (
WasmI32 WasmFieldType = iota
WasmI64
WasmF32
WasmF64
WasmPtr
)

type InlMark struct {
// When unwinding from an instruction in an inlined body, mark
// where we should unwind to.
Expand Down
21 changes: 21 additions & 0 deletions src/cmd/internal/obj/objfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,12 @@ func (w *objWriter) writeSym(s *LSym) {
if s.TopFrame() {
flags |= 1 << 4
}
if s.Func.WasmImport != nil {
flags |= 1 << 5
if s.Func.WasmImport.ABI0 {
flags |= 1 << 6
}
}
w.writeInt(flags)
w.writeInt(int64(0)) // TODO: remove at next object file rev

Expand Down Expand Up @@ -397,6 +403,21 @@ func (w *objWriter) writeSym(s *LSym) {
w.writeRefIndex(call.Func)
w.writeInt(int64(call.ParentPC))
}

if wi := s.Func.WasmImport; wi != nil {
w.writeString(wi.Module)
w.writeString(wi.Name)
w.writeInt(int64(len(wi.Params)))
for _, f := range wi.Params {
w.writeInt(int64(f.Type))
w.writeInt(f.Offset)
}
w.writeInt(int64(len(wi.Results)))
for _, f := range wi.Results {
w.writeInt(int64(f.Type))
w.writeInt(f.Offset)
}
}
}

func (w *objWriter) writeBool(b bool) {
Expand Down
3 changes: 1 addition & 2 deletions src/cmd/internal/obj/wasm/a.out.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions src/cmd/internal/obj/wasm/anames.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 58 additions & 15 deletions src/cmd/internal/obj/wasm/wasmobj.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ var unaryDst = map[obj.As]bool{
ATee: true,
ACall: true,
ACallIndirect: true,
ACallImport: true,
ABr: true,
ABrIf: true,
ABrTable: true,
Expand Down Expand Up @@ -134,11 +133,6 @@ var (
jmpdefer *obj.LSym
)

const (
/* mark flags */
WasmImport = 1 << 0
)

func instinit(ctxt *obj.Link) {
morestack = ctxt.Lookup("runtime.morestack")
morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt")
Expand Down Expand Up @@ -422,6 +416,64 @@ func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
call := *p
p.As = obj.ANOP

var wasmImport *obj.WasmImport
if call.To.Type == obj.TYPE_MEM && call.To.Sym.Func != nil {
wasmImport = call.To.Sym.Func.WasmImport
}
if wasmImport != nil && wasmImport.ABI0 {
p = appendp(p, AGet, regAddr(REG_SP))
p = appendp(p, AI32Const, constAddr(8)) // TODO(neelance): move SP delta into wasm_exec.js
p = appendp(p, AI32Sub)
p = appendp(p, ACall, call.To)
break
} else if wasmImport != nil && !wasmImport.ABI0 {
wi := call.To.Sym.Func.WasmImport
if len(wi.Results) == 1 {
p = appendp(p, AGet, regAddr(REG_SP)) // address has to be before the value
}
if len(wi.Results) > 1 {
panic("invalid results type") // impossible until multi-value proposal has landed
}
for _, f := range wi.Params {
p = appendp(p, AGet, regAddr(REG_SP))
switch f.Type {
case obj.WasmI32:
p = appendp(p, AI32Load, constAddr(f.Offset))
case obj.WasmI64:
p = appendp(p, AI64Load, constAddr(f.Offset))
case obj.WasmF32:
p = appendp(p, AF32Load, constAddr(f.Offset))
case obj.WasmF64:
p = appendp(p, AF64Load, constAddr(f.Offset))
case obj.WasmPtr:
p = appendp(p, AI64Load, constAddr(f.Offset))
p = appendp(p, AI32WrapI64)
default:
panic("bad param type")
}
}
p = appendp(p, ACall, call.To)
if len(wi.Results) == 1 {
f := wi.Results[0]
switch f.Type {
case obj.WasmI32:
p = appendp(p, AI32Store, constAddr(f.Offset))
case obj.WasmI64:
p = appendp(p, AI64Store, constAddr(f.Offset))
case obj.WasmF32:
p = appendp(p, AF32Store, constAddr(f.Offset))
case obj.WasmF64:
p = appendp(p, AF64Store, constAddr(f.Offset))
case obj.WasmPtr:
p = appendp(p, AI64ExtendI32U)
p = appendp(p, AI64Store, constAddr(f.Offset))
default:
panic("bad result type")
}
}
break
}

pcAfterCall := call.Link.Pc
if call.To.Sym == sigpanic {
pcAfterCall-- // sigpanic expects to be called without advancing the pc
Expand Down Expand Up @@ -702,12 +754,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
}
}

Expand Down Expand Up @@ -1009,9 +1055,6 @@ func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
r := obj.Addrel(s)
r.Off = int32(w.Len())
r.Type = objabi.R_CALL
if p.Mark&WasmImport != 0 {
r.Type = objabi.R_WASMIMPORT
}
r.Sym = p.To.Sym
if hasLocalSP {
// The stack may have moved, which changes SP. Update the local SP variable.
Expand Down
3 changes: 0 additions & 3 deletions src/cmd/internal/objabi/reloctype.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,6 @@ const (
// symbol's DWARF compile unit.
R_ADDRCUOFF

// R_WASMIMPORT resolves to the index of the WebAssembly function import.
R_WASMIMPORT

// R_XCOFFREF (only used on aix/ppc64) prevents garbage collection by ld
// of a symbol. This isn't a real relocation, it can be placed in anywhere
// in a symbol and target any symbols.
Expand Down
18 changes: 18 additions & 0 deletions src/cmd/link/internal/objfile/objfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,24 @@ overwrite:
r.lib.DupTextSyms = append(r.lib.DupTextSyms, dup)
}
}

if flags&(1<<5) != 0 {
wi := &obj.WasmImport{}
pc.WasmImport = wi
wi.Module = r.readString()
wi.Name = r.readString()
wi.Params = make([]obj.WasmField, r.readInt64())
for i := range wi.Params {
wi.Params[i].Type = obj.WasmFieldType(r.readUint8())
wi.Params[i].Offset = r.readInt64()
}
wi.Results = make([]obj.WasmField, r.readInt64())
for i := range wi.Results {
wi.Results[i].Type = obj.WasmFieldType(r.readUint8())
wi.Results[i].Offset = r.readInt64()
}
wi.ABI0 = flags&(1<<6) != 0
}
}
if s.Type == sym.SDWARFINFO {
r.patchDWARFName(s)
Expand Down
Loading

0 comments on commit 40aec9a

Please sign in to comment.