]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/cgo: Add support for C function pointers
authorAlberto García Hierro <alberto@garciahierro.com>
Tue, 13 Aug 2013 16:42:21 +0000 (12:42 -0400)
committerRuss Cox <rsc@golang.org>
Tue, 13 Aug 2013 16:42:21 +0000 (12:42 -0400)
* Add a new kind of Name, "fpvar" which stands for function pointer variable
* When walking the AST, find functions used as expressions and create a new Name object for them
* Track functions which are only used in expr contexts, and avoid generating bridge code for them

R=golang-dev, minux.ma, fullung, rsc, iant
CC=golang-dev
https://golang.org/cl/9835047

misc/cgo/test/cgo_test.go
misc/cgo/test/fpvar.go [new file with mode: 0644]
src/cmd/cgo/doc.go
src/cmd/cgo/gcc.go
src/cmd/cgo/main.go
src/cmd/cgo/out.go

index f1cd0b6a95ea0610e3b556d0b2f9a4b2df296cc0..f86305bf65c7f96ce84c539f870e57c6aa035298 100644 (file)
@@ -44,5 +44,6 @@ func Test5548(t *testing.T)                { test5548(t) }
 func Test5603(t *testing.T)                { test5603(t) }
 func Test3250(t *testing.T)                { test3250(t) }
 func TestCallbackStack(t *testing.T)       { testCallbackStack(t) }
+func TestFpVar(t *testing.T)               { testFpVar(t) }
 
 func BenchmarkCgoCall(b *testing.B) { benchCgoCall(b) }
diff --git a/misc/cgo/test/fpvar.go b/misc/cgo/test/fpvar.go
new file mode 100644 (file)
index 0000000..c0ec6f6
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright 2013 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file contains test cases for cgo with function pointer variables.
+
+package cgotest
+
+/*
+typedef int (*intFunc) ();
+
+int
+bridge_int_func(intFunc f)
+{
+       return f();
+}
+
+int fortytwo()
+{
+       return 42;
+}
+
+*/
+import "C"
+import "testing"
+
+func callBridge(f C.intFunc) int {
+       return int(C.bridge_int_func(f))
+}
+
+func callCBridge(f C.intFunc) C.int {
+       return C.bridge_int_func(f)
+}
+
+func testFpVar(t *testing.T) {
+       const expected = 42
+       f := C.intFunc(C.fortytwo)
+       res1 := C.bridge_int_func(f)
+       if r1 := int(res1); r1 != expected {
+               t.Errorf("got %d, want %d", r1, expected)
+       }
+       res2 := callCBridge(f)
+       if r2 := int(res2); r2 != expected {
+               t.Errorf("got %d, want %d", r2, expected)
+       }
+       r3 := callBridge(f)
+       if r3 != expected {
+               t.Errorf("got %d, want %d", r3, expected)
+       }
+}
index 17f01c313ee5c59f6a7c2fa09945891d3d017dd8..63737d4c2b028d9e1c240e9f83f887b445052c82 100644 (file)
@@ -76,6 +76,33 @@ function returns void).  For example:
        n, err := C.sqrt(-1)
        _, err := C.voidFunc()
 
+Calling C function pointers is currently not supported, however you can
+declare Go variables which hold C function pointers and pass them
+back and forth between Go and C. C code may call function pointers
+received from Go. For example:
+
+       package main
+       // typedef int (*intFunc) ();
+       //
+       // int
+       // bridge_int_func(intFunc f)
+       // {
+       //              return f();
+       // }
+       //
+       // int fortytwo()
+       // {
+       //          return 42;
+       // }
+       import "C"
+       import "fmt"
+
+       func main() {
+               f := C.intFunc(C.fortytwo)
+               fmt.Println(int(C.bridge_int_func(f)))
+               // Output: 42
+       }
+
 In C, a function argument written as a fixed size array
 actually requires a pointer to the first element of the array.
 C compilers are aware of this calling convention and adjust
index ab625c4e7e93493133d1fe4620496999072b137f..f4700946634f511d98a0b5c07e63114d09b3bead 100644 (file)
@@ -548,25 +548,40 @@ func (p *Package) loadDWARF(f *File, names []*Name) {
 
 }
 
+// mangleName does name mangling to translate names
+// from the original Go source files to the names
+// used in the final Go files generated by cgo.
+func (p *Package) mangleName(n *Name) {
+       // When using gccgo variables have to be
+       // exported so that they become global symbols
+       // that the C code can refer to.
+       prefix := "_C"
+       if *gccgo && n.IsVar() {
+               prefix = "C"
+       }
+       n.Mangle = prefix + n.Kind + "_" + n.Go
+}
+
 // rewriteRef rewrites all the C.xxx references in f.AST to refer to the
 // Go equivalents, now that we have figured out the meaning of all
 // the xxx.  In *godefs or *cdefs mode, rewriteRef replaces the names
 // with full definitions instead of mangled names.
 func (p *Package) rewriteRef(f *File) {
+       // Keep a list of all the functions, to remove the ones
+       // only used as expressions and avoid generating bridge
+       // code for them.
+       functions := make(map[string]bool)
+
        // Assign mangled names.
        for _, n := range f.Name {
                if n.Kind == "not-type" {
                        n.Kind = "var"
                }
                if n.Mangle == "" {
-                       // When using gccgo variables have to be
-                       // exported so that they become global symbols
-                       // that the C code can refer to.
-                       prefix := "_C"
-                       if *gccgo && n.Kind == "var" {
-                               prefix = "C"
-                       }
-                       n.Mangle = prefix + n.Kind + "_" + n.Go
+                       p.mangleName(n)
+               }
+               if n.Kind == "func" {
+                       functions[n.Go] = false
                }
        }
 
@@ -590,6 +605,7 @@ func (p *Package) rewriteRef(f *File) {
                                error_(r.Pos(), "call of non-function C.%s", r.Name.Go)
                                break
                        }
+                       functions[r.Name.Go] = true
                        if r.Context == "call2" {
                                // Invent new Name for the two-result function.
                                n := f.Name["2"+r.Name.Go]
@@ -606,13 +622,26 @@ func (p *Package) rewriteRef(f *File) {
                        }
                case "expr":
                        if r.Name.Kind == "func" {
-                               error_(r.Pos(), "must call C.%s", r.Name.Go)
-                       }
-                       if r.Name.Kind == "type" {
+                               // Function is being used in an expression, to e.g. pass around a C function pointer.
+                               // Create a new Name for this Ref which causes the variable to be declared in Go land.
+                               fpName := "fp_" + r.Name.Go
+                               name := f.Name[fpName]
+                               if name == nil {
+                                       name = &Name{
+                                               Go:   fpName,
+                                               C:    r.Name.C,
+                                               Kind: "fpvar",
+                                               Type: &Type{Size: p.PtrSize, Align: p.PtrSize, C: c("void*"), Go: ast.NewIdent("unsafe.Pointer")},
+                                       }
+                                       p.mangleName(name)
+                                       f.Name[fpName] = name
+                               }
+                               r.Name = name
+                               expr = ast.NewIdent(name.Mangle)
+                       } else if r.Name.Kind == "type" {
                                // Okay - might be new(T)
                                expr = r.Name.Type.Go
-                       }
-                       if r.Name.Kind == "var" {
+                       } else if r.Name.Kind == "var" {
                                expr = &ast.StarExpr{X: expr}
                        }
 
@@ -644,6 +673,14 @@ func (p *Package) rewriteRef(f *File) {
                }
                *r.Expr = expr
        }
+
+       // Remove functions only used as expressions, so their respective
+       // bridge functions are not generated.
+       for name, used := range functions {
+               if !used {
+                       delete(f.Name, name)
+               }
+       }
 }
 
 // gccBaseCmd returns the start of the compiler command line.
index 9bd326e1d4ec74d8d2242ba0eca963e6b8d8ad7a..319398907fc5c170d241aa49577a9e04ba4d7790 100644 (file)
@@ -80,13 +80,18 @@ type Name struct {
        Mangle   string // name used in generated Go
        C        string // name used in C
        Define   string // #define expansion
-       Kind     string // "const", "type", "var", "func", "not-type"
+       Kind     string // "const", "type", "var", "fpvar", "func", "not-type"
        Type     *Type  // the type of xxx
        FuncType *FuncType
        AddError bool
        Const    string // constant definition
 }
 
+// IsVar returns true if Kind is either "var" or "fpvar"
+func (n *Name) IsVar() bool {
+       return n.Kind == "var" || n.Kind == "fpvar"
+}
+
 // A ExpFunc is an exported function, callable from C.
 // Such functions are identified in the Go input file
 // by doc comments containing the line //export ExpName
index fcb4277ced5a74de9aa1bb3291994aa67ea1f1fe..012e0365bb283945d5ac71d7f449240976207c81 100644 (file)
@@ -97,7 +97,7 @@ func (p *Package) writeDefs() {
        cVars := make(map[string]bool)
        for _, key := range nameKeys(p.Name) {
                n := p.Name[key]
-               if n.Kind != "var" {
+               if !n.IsVar() {
                        continue
                }
 
@@ -113,17 +113,26 @@ func (p *Package) writeDefs() {
 
                        cVars[n.C] = true
                }
-
+               var amp string
+               var node ast.Node
+               if n.Kind == "var" {
+                       amp = "&"
+                       node = &ast.StarExpr{X: n.Type.Go}
+               } else if n.Kind == "fpvar" {
+                       node = n.Type.Go
+               } else {
+                       panic(fmt.Errorf("invalid var kind %q", n.Kind))
+               }
                if *gccgo {
                        fmt.Fprintf(fc, `extern void *%s __asm__("%s.%s");`, n.Mangle, gccgoSymbolPrefix, n.Mangle)
-                       fmt.Fprintf(&gccgoInit, "\t%s = &%s;\n", n.Mangle, n.C)
+                       fmt.Fprintf(&gccgoInit, "\t%s = %s%s;\n", n.Mangle, amp, n.C)
                } else {
-                       fmt.Fprintf(fc, "void *·%s = &%s;\n", n.Mangle, n.C)
+                       fmt.Fprintf(fc, "void *·%s = %s%s;\n", n.Mangle, amp, n.C)
                }
                fmt.Fprintf(fc, "\n")
 
                fmt.Fprintf(fgo2, "var %s ", n.Mangle)
-               conf.Fprint(fgo2, fset, &ast.StarExpr{X: n.Type.Go})
+               conf.Fprint(fgo2, fset, node)
                fmt.Fprintf(fgo2, "\n")
        }
        fmt.Fprintf(fc, "\n")