From: Alberto García Hierro Date: Tue, 13 Aug 2013 16:42:21 +0000 (-0400) Subject: cmd/cgo: Add support for C function pointers X-Git-Tag: go1.2rc2~625 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=c18dc11ef210ca91b251996ff2a2546d0bcde848;p=gostls13.git cmd/cgo: Add support for C function pointers * 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 --- diff --git a/misc/cgo/test/cgo_test.go b/misc/cgo/test/cgo_test.go index f1cd0b6a95..f86305bf65 100644 --- a/misc/cgo/test/cgo_test.go +++ b/misc/cgo/test/cgo_test.go @@ -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 index 0000000000..c0ec6f68d7 --- /dev/null +++ b/misc/cgo/test/fpvar.go @@ -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) + } +} diff --git a/src/cmd/cgo/doc.go b/src/cmd/cgo/doc.go index 17f01c313e..63737d4c2b 100644 --- a/src/cmd/cgo/doc.go +++ b/src/cmd/cgo/doc.go @@ -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 diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index ab625c4e7e..f470094663 100644 --- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -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. diff --git a/src/cmd/cgo/main.go b/src/cmd/cgo/main.go index 9bd326e1d4..319398907f 100644 --- a/src/cmd/cgo/main.go +++ b/src/cmd/cgo/main.go @@ -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 diff --git a/src/cmd/cgo/out.go b/src/cmd/cgo/out.go index fcb4277ced..012e0365bb 100644 --- a/src/cmd/cgo/out.go +++ b/src/cmd/cgo/out.go @@ -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")