Skip to content

Commit 95d8956

Browse files
committed
loader/cgo: add support for function pointers
1 parent 35fb594 commit 95d8956

File tree

8 files changed

+134
-7
lines changed

8 files changed

+134
-7
lines changed

compiler/compiler.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2990,6 +2990,16 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
29902990
if c.targetData.TypeAllocSize(x.Type().ElementType()) == 0 {
29912991
// zero-length data
29922992
return c.getZeroValue(x.Type().ElementType())
2993+
} else if strings.HasSuffix(unop.X.String(), "$funcaddr") {
2994+
// CGo function pointer. The cgo part has rewritten CGo function
2995+
// pointers as stub global variables of the form:
2996+
// var C.add unsafe.Pointer
2997+
// Instead of a load from the global, create a bitcast of the
2998+
// function pointer itself.
2999+
global := c.ir.GetGlobal(unop.X.(*ssa.Global))
3000+
name := global.LinkName()[:len(global.LinkName())-len("$funcaddr")]
3001+
fn := c.mod.NamedFunction(name)
3002+
return c.builder.CreateBitCast(fn, c.i8ptrType, ""), nil
29933003
} else {
29943004
load := c.builder.CreateLoad(x, "")
29953005
if c.ir.IsVolatile(valType) {

ir/ir.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,16 @@ func (f *Function) parsePragmas() {
269269
}
270270
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
271271
for _, comment := range decl.Doc.List {
272-
if !strings.HasPrefix(comment.Text, "//go:") {
272+
text := comment.Text
273+
if strings.HasPrefix(text, "//export ") {
274+
// Rewrite '//export' to '//go:export' for compatibility with
275+
// gc.
276+
text = "//go:" + text[2:]
277+
}
278+
if !strings.HasPrefix(text, "//go:") {
273279
continue
274280
}
275-
parts := strings.Fields(comment.Text)
281+
parts := strings.Fields(text)
276282
switch parts[0] {
277283
case "//go:export":
278284
if len(parts) != 2 {
@@ -322,15 +328,15 @@ func (f *Function) IsNoBounds() bool {
322328

323329
// Return true iff this function is externally visible.
324330
func (f *Function) IsExported() bool {
325-
return f.exported
331+
return f.exported || f.CName() != ""
326332
}
327333

328334
// Return true for functions annotated with //go:interrupt. The function name is
329335
// already customized in LinkName() to hook up in the interrupt vector.
330336
//
331337
// On some platforms (like AVR), interrupts need a special compiler flag.
332338
func (f *Function) IsInterrupt() bool {
333-
return f.exported
339+
return f.interrupt
334340
}
335341

336342
// Return the link name for this function.

loader/cgo.go

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro
130130
// Declare functions found by libclang.
131131
info.addFuncDecls()
132132

133+
// Declare stub function pointer values found by libclang.
134+
info.addFuncPtrDecls()
135+
133136
// Declare globals found by libclang.
134137
info.addVarDecls()
135138

@@ -199,6 +202,55 @@ func (info *fileInfo) addFuncDecls() {
199202
}
200203
}
201204

205+
// addFuncPtrDecls creates stub declarations of function pointer values. These
206+
// values will later be replaced with the real values in the compiler.
207+
// It adds code like the following to the AST:
208+
//
209+
// var (
210+
// C.add unsafe.Pointer
211+
// C.mul unsafe.Pointer
212+
// // ...
213+
// )
214+
func (info *fileInfo) addFuncPtrDecls() {
215+
gen := &ast.GenDecl{
216+
TokPos: info.importCPos,
217+
Tok: token.VAR,
218+
Lparen: info.importCPos,
219+
Rparen: info.importCPos,
220+
}
221+
names := make([]string, 0, len(info.functions))
222+
for name := range info.functions {
223+
names = append(names, name)
224+
}
225+
sort.Strings(names)
226+
for _, name := range names {
227+
obj := &ast.Object{
228+
Kind: ast.Typ,
229+
Name: "C." + name + "$funcaddr",
230+
}
231+
valueSpec := &ast.ValueSpec{
232+
Names: []*ast.Ident{&ast.Ident{
233+
NamePos: info.importCPos,
234+
Name: "C." + name + "$funcaddr",
235+
Obj: obj,
236+
}},
237+
Type: &ast.SelectorExpr{
238+
X: &ast.Ident{
239+
NamePos: info.importCPos,
240+
Name: "unsafe",
241+
},
242+
Sel: &ast.Ident{
243+
NamePos: info.importCPos,
244+
Name: "Pointer",
245+
},
246+
},
247+
}
248+
obj.Decl = valueSpec
249+
gen.Specs = append(gen.Specs, valueSpec)
250+
}
251+
info.Decls = append(info.Decls, gen)
252+
}
253+
202254
// addVarDecls declares external C globals in the Go source.
203255
// It adds code like the following to the AST:
204256
//
@@ -327,15 +379,34 @@ func (info *fileInfo) addTypedefs() {
327379
// separate namespace (no _Cgo_ hacks like in gc).
328380
func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
329381
switch node := cursor.Node().(type) {
382+
case *ast.CallExpr:
383+
fun, ok := node.Fun.(*ast.SelectorExpr)
384+
if !ok {
385+
return true
386+
}
387+
x, ok := fun.X.(*ast.Ident)
388+
if !ok {
389+
return true
390+
}
391+
if _, ok := info.functions[fun.Sel.Name]; ok && x.Name == "C" {
392+
node.Fun = &ast.Ident{
393+
NamePos: x.NamePos,
394+
Name: "C." + fun.Sel.Name,
395+
}
396+
}
330397
case *ast.SelectorExpr:
331398
x, ok := node.X.(*ast.Ident)
332399
if !ok {
333400
return true
334401
}
335402
if x.Name == "C" {
403+
name := "C." + node.Sel.Name
404+
if _, ok := info.functions[node.Sel.Name]; ok {
405+
name += "$funcaddr"
406+
}
336407
cursor.Replace(&ast.Ident{
337408
NamePos: x.NamePos,
338-
Name: "C." + node.Sel.Name,
409+
Name: name,
339410
})
340411
}
341412
}

loader/libclang.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package loader
66
import (
77
"errors"
88
"go/ast"
9+
"go/token"
10+
"strconv"
911
"strings"
1012
"unsafe"
1113
)
@@ -90,13 +92,16 @@ func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.in
9092
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
9193
return C.CXChildVisit_Continue // not supported
9294
}
93-
numArgs := C.clang_Cursor_getNumArguments(c)
95+
numArgs := int(C.clang_Cursor_getNumArguments(c))
9496
fn := &functionInfo{}
9597
info.functions[name] = fn
96-
for i := C.int(0); i < numArgs; i++ {
98+
for i := 0; i < numArgs; i++ {
9799
arg := C.clang_Cursor_getArgument(c, C.uint(i))
98100
argName := getString(C.clang_getCursorSpelling(arg))
99101
argType := C.clang_getArgType(cursorType, C.uint(i))
102+
if argName == "" {
103+
argName = "$" + strconv.Itoa(i)
104+
}
100105
fn.args = append(fn.args, paramInfo{
101106
name: argName,
102107
typeExpr: info.makeASTType(argType),
@@ -196,6 +201,23 @@ func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
196201
Star: info.importCPos,
197202
X: info.makeASTType(C.clang_getPointeeType(typ)),
198203
}
204+
case C.CXType_FunctionProto:
205+
// Be compatible with gc, which uses the *[0]byte type for function
206+
// pointer types.
207+
// Return type [0]byte because this is a function type, not a pointer to
208+
// this function type.
209+
return &ast.ArrayType{
210+
Lbrack: info.importCPos,
211+
Len: &ast.BasicLit{
212+
ValuePos: info.importCPos,
213+
Kind: token.INT,
214+
Value: "0",
215+
},
216+
Elt: &ast.Ident{
217+
NamePos: info.importCPos,
218+
Name: "byte",
219+
},
220+
}
199221
default:
200222
// Fallback, probably incorrect but at least the error points to an odd
201223
// type name.

testdata/cgo/main.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ int add(int a, int b) {
1010
return a + b;
1111
}
1212

13+
int doCallback(int a, int b, binop_t callback) {
14+
return callback(a, b);
15+
}
16+
1317
void store(int value, int *ptr) {
1418
*ptr = value;
1519
}

testdata/cgo/main.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
/*
44
int fortytwo(void);
55
#include "main.h"
6+
int mul(int, int);
67
*/
78
import "C"
89

@@ -23,4 +24,13 @@ func main() {
2324
println("15:", *ptr)
2425
C.store(25, &n)
2526
println("25:", *ptr)
27+
cb := C.binop_t(C.add)
28+
println("callback 1:", C.doCallback(20, 30, cb))
29+
cb = C.binop_t(C.mul)
30+
println("callback 2:", C.doCallback(20, 30, cb))
31+
}
32+
33+
//export mul
34+
func mul(a, b C.int) C.int {
35+
return a * b
2636
}

testdata/cgo/main.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
typedef short myint;
22
int add(int a, int b);
3+
typedef int (*binop_t) (int, int);
4+
int doCallback(int a, int b, binop_t cb);
35
typedef int * intPointer;
46
extern int global;
57
void store(int value, int *ptr);

testdata/cgo/out.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ longlong: -1099511627776
66
global: 3
77
15: 15
88
25: 25
9+
callback 1: 50
10+
callback 2: 600

0 commit comments

Comments
 (0)