Browse Source

loader/cgo: add support for function pointers

pull/174/head
Ayke van Laethem 6 years ago
parent
commit
95d895646a
No known key found for this signature in database GPG Key ID: E97FF5335DFDFDED
  1. 10
      compiler/compiler.go
  2. 14
      ir/ir.go
  3. 73
      loader/cgo.go
  4. 26
      loader/libclang.go
  5. 4
      testdata/cgo/main.c
  6. 10
      testdata/cgo/main.go
  7. 2
      testdata/cgo/main.h
  8. 2
      testdata/cgo/out.txt

10
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) {

14
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.

73
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,
})
}
}

26
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.

4
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;
}

10
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
}

2
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);

2
testdata/cgo/out.txt

@ -6,3 +6,5 @@ longlong: -1099511627776
global: 3
15: 15
25: 25
callback 1: 50
callback 2: 600

Loading…
Cancel
Save