From 95d895646a8005dc2afed425061865034a73450b Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 7 Feb 2019 13:50:09 +0100 Subject: [PATCH] loader/cgo: add support for function pointers --- compiler/compiler.go | 10 ++++++ ir/ir.go | 14 ++++++--- loader/cgo.go | 73 +++++++++++++++++++++++++++++++++++++++++++- loader/libclang.go | 26 ++++++++++++++-- testdata/cgo/main.c | 4 +++ testdata/cgo/main.go | 10 ++++++ testdata/cgo/main.h | 2 ++ testdata/cgo/out.txt | 2 ++ 8 files changed, 134 insertions(+), 7 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 77a6dbe1a..e07d83f06 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -2990,6 +2990,16 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) { if c.targetData.TypeAllocSize(x.Type().ElementType()) == 0 { // zero-length data return c.getZeroValue(x.Type().ElementType()) + } else if strings.HasSuffix(unop.X.String(), "$funcaddr") { + // CGo function pointer. The cgo part has rewritten CGo function + // pointers as stub global variables of the form: + // var C.add unsafe.Pointer + // Instead of a load from the global, create a bitcast of the + // function pointer itself. + global := c.ir.GetGlobal(unop.X.(*ssa.Global)) + name := global.LinkName()[:len(global.LinkName())-len("$funcaddr")] + fn := c.mod.NamedFunction(name) + return c.builder.CreateBitCast(fn, c.i8ptrType, ""), nil } else { load := c.builder.CreateLoad(x, "") if c.ir.IsVolatile(valType) { diff --git a/ir/ir.go b/ir/ir.go index 884b78bea..9f663424c 100644 --- a/ir/ir.go +++ b/ir/ir.go @@ -269,10 +269,16 @@ func (f *Function) parsePragmas() { } if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil { for _, comment := range decl.Doc.List { - if !strings.HasPrefix(comment.Text, "//go:") { + text := comment.Text + if strings.HasPrefix(text, "//export ") { + // Rewrite '//export' to '//go:export' for compatibility with + // gc. + text = "//go:" + text[2:] + } + if !strings.HasPrefix(text, "//go:") { continue } - parts := strings.Fields(comment.Text) + parts := strings.Fields(text) switch parts[0] { case "//go:export": if len(parts) != 2 { @@ -322,7 +328,7 @@ func (f *Function) IsNoBounds() bool { // Return true iff this function is externally visible. func (f *Function) IsExported() bool { - return f.exported + return f.exported || f.CName() != "" } // Return true for functions annotated with //go:interrupt. The function name is @@ -330,7 +336,7 @@ func (f *Function) IsExported() bool { // // On some platforms (like AVR), interrupts need a special compiler flag. func (f *Function) IsInterrupt() bool { - return f.exported + return f.interrupt } // Return the link name for this function. diff --git a/loader/cgo.go b/loader/cgo.go index 5e09da61f..626d3dbc4 100644 --- a/loader/cgo.go +++ b/loader/cgo.go @@ -130,6 +130,9 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro // Declare functions found by libclang. info.addFuncDecls() + // Declare stub function pointer values found by libclang. + info.addFuncPtrDecls() + // Declare globals found by libclang. info.addVarDecls() @@ -199,6 +202,55 @@ func (info *fileInfo) addFuncDecls() { } } +// addFuncPtrDecls creates stub declarations of function pointer values. These +// values will later be replaced with the real values in the compiler. +// It adds code like the following to the AST: +// +// var ( +// C.add unsafe.Pointer +// C.mul unsafe.Pointer +// // ... +// ) +func (info *fileInfo) addFuncPtrDecls() { + gen := &ast.GenDecl{ + TokPos: info.importCPos, + Tok: token.VAR, + Lparen: info.importCPos, + Rparen: info.importCPos, + } + names := make([]string, 0, len(info.functions)) + for name := range info.functions { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + obj := &ast.Object{ + Kind: ast.Typ, + Name: "C." + name + "$funcaddr", + } + valueSpec := &ast.ValueSpec{ + Names: []*ast.Ident{&ast.Ident{ + NamePos: info.importCPos, + Name: "C." + name + "$funcaddr", + Obj: obj, + }}, + Type: &ast.SelectorExpr{ + X: &ast.Ident{ + NamePos: info.importCPos, + Name: "unsafe", + }, + Sel: &ast.Ident{ + NamePos: info.importCPos, + Name: "Pointer", + }, + }, + } + obj.Decl = valueSpec + gen.Specs = append(gen.Specs, valueSpec) + } + info.Decls = append(info.Decls, gen) +} + // addVarDecls declares external C globals in the Go source. // It adds code like the following to the AST: // @@ -327,15 +379,34 @@ func (info *fileInfo) addTypedefs() { // separate namespace (no _Cgo_ hacks like in gc). func (info *fileInfo) walker(cursor *astutil.Cursor) bool { switch node := cursor.Node().(type) { + case *ast.CallExpr: + fun, ok := node.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + x, ok := fun.X.(*ast.Ident) + if !ok { + return true + } + if _, ok := info.functions[fun.Sel.Name]; ok && x.Name == "C" { + node.Fun = &ast.Ident{ + NamePos: x.NamePos, + Name: "C." + fun.Sel.Name, + } + } case *ast.SelectorExpr: x, ok := node.X.(*ast.Ident) if !ok { return true } if x.Name == "C" { + name := "C." + node.Sel.Name + if _, ok := info.functions[node.Sel.Name]; ok { + name += "$funcaddr" + } cursor.Replace(&ast.Ident{ NamePos: x.NamePos, - Name: "C." + node.Sel.Name, + Name: name, }) } } diff --git a/loader/libclang.go b/loader/libclang.go index 706f2ba23..57e573b21 100644 --- a/loader/libclang.go +++ b/loader/libclang.go @@ -6,6 +6,8 @@ package loader import ( "errors" "go/ast" + "go/token" + "strconv" "strings" "unsafe" ) @@ -90,13 +92,16 @@ func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.in if C.clang_isFunctionTypeVariadic(cursorType) != 0 { return C.CXChildVisit_Continue // not supported } - numArgs := C.clang_Cursor_getNumArguments(c) + numArgs := int(C.clang_Cursor_getNumArguments(c)) fn := &functionInfo{} info.functions[name] = fn - for i := C.int(0); i < numArgs; i++ { + for i := 0; i < numArgs; i++ { arg := C.clang_Cursor_getArgument(c, C.uint(i)) argName := getString(C.clang_getCursorSpelling(arg)) argType := C.clang_getArgType(cursorType, C.uint(i)) + if argName == "" { + argName = "$" + strconv.Itoa(i) + } fn.args = append(fn.args, paramInfo{ name: argName, typeExpr: info.makeASTType(argType), @@ -196,6 +201,23 @@ func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr { Star: info.importCPos, X: info.makeASTType(C.clang_getPointeeType(typ)), } + case C.CXType_FunctionProto: + // Be compatible with gc, which uses the *[0]byte type for function + // pointer types. + // Return type [0]byte because this is a function type, not a pointer to + // this function type. + return &ast.ArrayType{ + Lbrack: info.importCPos, + Len: &ast.BasicLit{ + ValuePos: info.importCPos, + Kind: token.INT, + Value: "0", + }, + Elt: &ast.Ident{ + NamePos: info.importCPos, + Name: "byte", + }, + } default: // Fallback, probably incorrect but at least the error points to an odd // type name. diff --git a/testdata/cgo/main.c b/testdata/cgo/main.c index 8eb3d50ae..2f3269ea0 100644 --- a/testdata/cgo/main.c +++ b/testdata/cgo/main.c @@ -10,6 +10,10 @@ int add(int a, int b) { return a + b; } +int doCallback(int a, int b, binop_t callback) { + return callback(a, b); +} + void store(int value, int *ptr) { *ptr = value; } diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index 0416a1b88..0733d226e 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -3,6 +3,7 @@ package main /* int fortytwo(void); #include "main.h" +int mul(int, int); */ import "C" @@ -23,4 +24,13 @@ func main() { println("15:", *ptr) C.store(25, &n) println("25:", *ptr) + cb := C.binop_t(C.add) + println("callback 1:", C.doCallback(20, 30, cb)) + cb = C.binop_t(C.mul) + println("callback 2:", C.doCallback(20, 30, cb)) +} + +//export mul +func mul(a, b C.int) C.int { + return a * b } diff --git a/testdata/cgo/main.h b/testdata/cgo/main.h index d4d205b84..be529d4fb 100644 --- a/testdata/cgo/main.h +++ b/testdata/cgo/main.h @@ -1,5 +1,7 @@ typedef short myint; int add(int a, int b); +typedef int (*binop_t) (int, int); +int doCallback(int a, int b, binop_t cb); typedef int * intPointer; extern int global; void store(int value, int *ptr); diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index 6414dfd3e..8d6845ee7 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -6,3 +6,5 @@ longlong: -1099511627776 global: 3 15: 15 25: 25 +callback 1: 50 +callback 2: 600