Skip to content

Commit

Permalink
compiler: disallow most types in //go:wasmimport
Browse files Browse the repository at this point in the history
This is for compatibility with upstream Go.
See golang/go#59149 for more context.
  • Loading branch information
aykevl authored and deadprogram committed May 20, 2023
1 parent 41e787d commit b08ff17
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 14 deletions.
63 changes: 59 additions & 4 deletions compiler/symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package compiler
// pragmas, determines the link name, etc.

import (
"fmt"
"go/ast"
"go/token"
"go/types"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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.
Expand Down
42 changes: 42 additions & 0 deletions compiler/testdata/errors.go
Original file line number Diff line number Diff line change
@@ -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
4 changes: 0 additions & 4 deletions compiler/testdata/pragma.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 0 additions & 6 deletions compiler/testdata/pragma.ll
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down

0 comments on commit b08ff17

Please sign in to comment.