diff --git a/compiler/symbol.go b/compiler/symbol.go index 7d9fcc2ffd..6426604ec1 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -4,6 +4,7 @@ package compiler // pragmas, determines the link name, etc. import ( + "fmt" "go/ast" "go/token" "go/types" @@ -247,14 +248,14 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo { linkName: f.RelString(nil), } // Check for //go: pragmas, which may change the link name (among others). - info.parsePragmas(f) + c.parsePragmas(&info, f) c.functionInfos[f] = info return info } // parsePragmas is used by getFunctionInfo to parse function pragmas such as // //export or //go:noinline. -func (info *functionInfo) parsePragmas(f *ssa.Function) { +func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { if f.Syntax() == nil { return } @@ -294,10 +295,12 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) { info.module = parts[1] case "//go:wasmimport": // Import a WebAssembly function, for example a WASI function. - // For details, see: https://github.com/golang/go/issues/38248 - if len(parts) != 3 || len(f.Blocks) != 0 { + // Original proposal: https://github.com/golang/go/issues/38248 + // Allow globally: https://github.com/golang/go/issues/59149 + if len(parts) != 3 { continue } + c.checkWasmImport(f, comment.Text) info.exported = true info.module = parts[1] info.importName = parts[2] @@ -358,6 +361,58 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) { } } +// Check whether this function cannot be used in //go:wasmimport. It will add an +// error if this is the case. +// +// The list of allowed types is based on this proposal: +// https://github.com/golang/go/issues/59149 +func (c *compilerContext) checkWasmImport(f *ssa.Function, pragma string) { + if c.pkg.Path() == "runtime" { + // The runtime is a special case. Allow all kinds of parameters + // (importantly, including pointers). + return + } + if f.Blocks != nil { + // Defined functions cannot be exported. + c.addError(f.Pos(), fmt.Sprintf("can only use //go:wasmimport on declarations")) + return + } + if f.Signature.Results().Len() > 1 { + c.addError(f.Signature.Results().At(1).Pos(), fmt.Sprintf("%s: too many return values", pragma)) + } else if f.Signature.Results().Len() == 1 { + result := f.Signature.Results().At(0) + if !isValidWasmType(result.Type(), true) { + c.addError(result.Pos(), fmt.Sprintf("%s: unsupported result type %s", pragma, result.Type().String())) + } + } + for _, param := range f.Params { + // Check whether the type is allowed. + // Only a very limited number of types can be mapped to WebAssembly. + if !isValidWasmType(param.Type(), false) { + c.addError(param.Pos(), fmt.Sprintf("%s: unsupported parameter type %s", pragma, param.Type().String())) + } + } +} + +// Check whether the type maps directly to a WebAssembly type, according to: +// https://github.com/golang/go/issues/59149 +func isValidWasmType(typ types.Type, isReturn bool) bool { + switch typ := typ.Underlying().(type) { + case *types.Basic: + switch typ.Kind() { + case types.Int32, types.Uint32, types.Int64, types.Uint64: + return true + case types.Float32, types.Float64: + return true + case types.UnsafePointer: + if !isReturn { + return true + } + } + } + return false +} + // getParams returns the function parameters, including the receiver at the // start. This is an alternative to the Params member of *ssa.Function, which is // not yet populated when the package has not yet been built. diff --git a/compiler/testdata/errors.go b/compiler/testdata/errors.go index 06ab7d0f9a..5778a931e1 100644 --- a/compiler/testdata/errors.go +++ b/compiler/testdata/errors.go @@ -1 +1,43 @@ package main + +import "unsafe" + +//go:wasmimport modulename empty +func empty() + +// ERROR: can only use //go:wasmimport on declarations +// +//go:wasmimport modulename implementation +func implementation() { +} + +type Uint uint32 + +//go:wasmimport modulename validparam +func validparam(a int32, b uint64, c float64, d unsafe.Pointer, e Uint) + +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type int +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type string +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type []byte +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type *int32 +// +//go:wasmimport modulename invalidparam +func invalidparam(a int, b string, c []byte, d *int32) + +//go:wasmimport modulename validreturn +func validreturn() int32 + +// ERROR: //go:wasmimport modulename manyreturns: too many return values +// +//go:wasmimport modulename manyreturns +func manyreturns() (int32, int32) + +// ERROR: //go:wasmimport modulename invalidreturn: unsupported result type int +// +//go:wasmimport modulename invalidreturn +func invalidreturn() int + +// ERROR: //go:wasmimport modulename invalidUnsafePointerReturn: unsupported result type unsafe.Pointer +// +//go:wasmimport modulename invalidUnsafePointerReturn +func invalidUnsafePointerReturn() unsafe.Pointer diff --git a/compiler/testdata/pragma.go b/compiler/testdata/pragma.go index 0ff9688781..0a4be11aea 100644 --- a/compiler/testdata/pragma.go +++ b/compiler/testdata/pragma.go @@ -62,10 +62,6 @@ func exportedFunctionInSection() { //go:wasmimport modulename import1 func declaredImport() -//go:wasmimport modulename import2 -func definedImport() { -} - // This function should not: it's only a declaration and not a definition. // //go:section .special_function_section diff --git a/compiler/testdata/pragma.ll b/compiler/testdata/pragma.ll index f2a279a0cd..78cc29457d 100644 --- a/compiler/testdata/pragma.ll +++ b/compiler/testdata/pragma.ll @@ -62,12 +62,6 @@ entry: declare void @main.declaredImport() #7 -; Function Attrs: nounwind -define hidden void @main.definedImport(ptr %context) unnamed_addr #2 { -entry: - ret void -} - declare void @main.undefinedFunctionNotInSection(ptr) #1 attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }