Commit 12e7ce95 authored by Ian Lance Taylor's avatar Ian Lance Taylor

Add //export to cgo.

The new //export comment marks a Go function as callable from
C.  The syntax is "//export NAME" where NAME is the name of
the function as seen from C.  If such a comment is seen, cgo
will generate two new files: _cgo_export.h and _cgo_export.c.
The _cgo_export.h file provides declarations which C code may
use to call Go functions.  The _cgo_export.c file contains
wrappers, and is to be compiled with gcc.

The changes to Make.pkg support using this from a Go Makefile,
though it could probably be more convenient.

R=rsc
CC=golang-dev
https://golang.org/cl/853042
parent 2e20386f
......@@ -44,7 +44,7 @@ coverage:
$(QUOTED_GOBIN)/gotest
$(QUOTED_GOBIN)/6cov -g $(shell pwd) $O.out | grep -v '_test\.go:'
CLEANFILES+=*.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go *.so _obj _test _testmain.go
CLEANFILES+=*.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* *.so _obj _test _testmain.go
test:
$(QUOTED_GOBIN)/gotest
......@@ -92,9 +92,10 @@ dir:
#
# to the main Makefile. This signals that cgo should process x.go
# and y.go when building the package.
# There are two optional variables to set, CGO_CFLAGS and CGO_LDFLAGS,
# which specify compiler and linker flags to use when compiling
# (using gcc) the C support for x.go and y.go.
# There are three optional variables to set, CGO_CFLAGS, CGO_LDFLAGS,
# and CGO_DEPS, which specify compiler flags, linker flags, and linker
# dependencies to use when compiling (using gcc) the C support for
# x.go and y.go.
# Cgo translates each x.go file listed in $(CGOFILES) into a basic
# translation of x.go, called x.cgo1.go. Additionally, three other
......@@ -118,6 +119,9 @@ _cgo_defun.c _cgo_gotypes.go: $(CGOFILES)
%.cgo2.o: %.cgo2.c
gcc $(_CGO_CFLAGS_$(GOARCH)) -fPIC -O2 -o $@ -c $(CGO_CFLAGS) $*.cgo2.c
_cgo_export.o: _cgo_export.c _cgo_export.h
gcc $(_CGO_CFLAGS_$(GOARCH)) -fPIC -O2 -o $@ -c $(CGO_CFLAGS) _cgo_export.c
# The rules above added x.cgo1.go and _cgo_gotypes.go to $(GOFILES),
# added _cgo_defun.$O to $OFILES, and added the installed copy of
# package_x.so (built from x.cgo2.c) to $(INSTALLFILES).
......@@ -139,7 +143,7 @@ _CGO_LDFLAGS_darwin=-dynamiclib -Wl,-undefined,dynamic_lookup
_cgo_defun.$O: _cgo_defun.c
$(QUOTED_GOBIN)/$(CC) $(CFLAGS) $(RUNTIME_CFLAGS) _cgo_defun.c
_cgo_.so: $(GCC_OFILES)
_cgo_.so: $(GCC_OFILES) $(CGO_DEPS)
gcc $(_CGO_CFLAGS_$(GOARCH)) -o $@ $(GCC_OFILES) $(CGO_LDFLAGS) $(_CGO_LDFLAGS_$(GOOS))
$(pkgdir)/$(TARG).so: _cgo_.so
......
......@@ -13,6 +13,7 @@ import (
"go/parser"
"go/scanner"
"os"
"strings"
)
// A Cref refers to an expression of the form C.xxx in the AST.
......@@ -25,6 +26,12 @@ type Cref struct {
FuncType *FuncType
}
// A ExpFunc is an exported function, callable from C.
type ExpFunc struct {
Func *ast.FuncDecl
ExpName string // name to use from C
}
// A Prog collects information about a cgo program.
type Prog struct {
AST *ast.File // parsed AST
......@@ -37,6 +44,7 @@ type Prog struct {
Funcdef map[string]*FuncType
Enumdef map[string]int64
Constdef map[string]string
ExpFuncs []*ExpFunc
PtrSize int64
GccOptions []string
OutDefs map[string]bool
......@@ -303,6 +311,8 @@ func walk(x interface{}, p *Prog, context string) {
walk(n.Body, p, "stmt")
}
checkExpFunc(n, p)
case *ast.File:
walk(n.Decls, p, "decl")
......@@ -329,3 +339,38 @@ func walk(x interface{}, p *Prog, context string) {
}
}
}
// If a function should be exported add it to ExpFuncs.
func checkExpFunc(n *ast.FuncDecl, p *Prog) {
if n.Doc == nil {
return
}
for _, c := range n.Doc.List {
if string(c.Text[0:9]) != "//export " {
continue
}
name := strings.TrimSpace(string(c.Text[9:]))
if name == "" {
error(c.Position, "export missing name")
}
if p.ExpFuncs == nil {
p.ExpFuncs = make([]*ExpFunc, 0, 8)
}
i := len(p.ExpFuncs)
if i >= cap(p.ExpFuncs) {
new := make([]*ExpFunc, 2*i)
for j, v := range p.ExpFuncs {
new[j] = v
}
p.ExpFuncs = new
}
p.ExpFuncs = p.ExpFuncs[0 : i+1]
p.ExpFuncs[i] = &ExpFunc{
Func: n,
ExpName: name,
}
break
}
}
......@@ -8,6 +8,7 @@ import (
"fmt"
"go/ast"
"go/printer"
"go/token"
"os"
"strings"
)
......@@ -47,7 +48,7 @@ func (p *Prog) writeDefs() {
}
fmt.Fprintf(fgo2, "type _C_void [0]byte\n")
fmt.Fprintf(fc, cProlog, pkgroot, pkgroot, pkgroot, pkgroot)
fmt.Fprintf(fc, cProlog, pkgroot, pkgroot, pkgroot, pkgroot, pkgroot)
for name, def := range p.Vardef {
fmt.Fprintf(fc, "#pragma dynimport ·_C_%s %s \"%s.so\"\n", name, name, path)
......@@ -140,6 +141,8 @@ func (p *Prog) writeDefs() {
fmt.Fprintf(fc, "\n")
}
p.writeExports(fgo2, fc)
fgo2.Close()
fc.Close()
}
......@@ -245,6 +248,294 @@ func (p *Prog) writeOutput(srcfile string) {
fgcc.Close()
}
// Write out the various stubs we need to support functions exported
// from Go so that they are callable from C.
func (p *Prog) writeExports(fgo2, fc *os.File) {
if len(p.ExpFuncs) == 0 {
return
}
fgcc := creat("_cgo_export.c")
fgcch := creat("_cgo_export.h")
fmt.Fprintf(fgcch, "/* Created by cgo - DO NOT EDIT. */\n")
fmt.Fprintf(fgcch, "%s\n", gccExportHeaderProlog)
fmt.Fprintf(fgcc, "/* Created by cgo - DO NOT EDIT. */\n")
fmt.Fprintf(fgcc, "#include \"_cgo_export.h\"\n")
for _, exp := range p.ExpFuncs {
fn := exp.Func
// Construct a gcc struct matching the 6c argument and
// result frame.
structType := "struct {\n"
off := int64(0)
npad := 0
if fn.Recv != nil {
t := p.cgoType(fn.Recv.List[0].Type)
structType += fmt.Sprintf("\t\t%s recv;\n", t.C)
off += t.Size
}
fntype := fn.Type
forFieldList(fntype.Params,
func(i int, atype ast.Expr) {
t := p.cgoType(atype)
if off%t.Align != 0 {
pad := t.Align - off%t.Align
structType += fmt.Sprintf("\t\tchar __pad%d[%d];\n", npad, pad)
off += pad
npad++
}
structType += fmt.Sprintf("\t\t%s p%d;\n", t.C, i)
off += t.Size
})
if off%p.PtrSize != 0 {
pad := p.PtrSize - off%p.PtrSize
structType += fmt.Sprintf("\t\tchar __pad%d[%d];\n", npad, pad)
off += pad
npad++
}
forFieldList(fntype.Results,
func(i int, atype ast.Expr) {
t := p.cgoType(atype)
if off%t.Align != 0 {
pad := t.Align - off%t.Align
structType += fmt.Sprintf("\t\tchar __pad%d[%d]\n", npad, pad)
off += pad
npad++
}
structType += fmt.Sprintf("\t\t%s r%d;\n", t.C, i)
off += t.Size
})
if off%p.PtrSize != 0 {
pad := p.PtrSize - off%p.PtrSize
structType += fmt.Sprintf("\t\tchar __pad%d[%d];\n", npad, pad)
off += pad
npad++
}
if structType == "struct {\n" {
structType += "\t\tchar unused;\n" // avoid empty struct
off++
}
structType += "\t}"
// Get the return type of the wrapper function
// compiled by gcc.
gccResult := ""
if fntype.Results == nil || len(fntype.Results.List) == 0 {
gccResult = "void"
} else if len(fntype.Results.List) == 1 && len(fntype.Results.List[0].Names) <= 1 {
gccResult = p.cgoType(fntype.Results.List[0].Type).C
} else {
fmt.Fprintf(fgcch, "\n/* Return type for %s */\n", exp.ExpName)
fmt.Fprintf(fgcch, "struct %s_return {\n", exp.ExpName)
forFieldList(fntype.Results,
func(i int, atype ast.Expr) {
fmt.Fprintf(fgcch, "\t%s r%d;\n", p.cgoType(atype).C, i)
})
fmt.Fprintf(fgcch, "};\n")
gccResult = "struct " + exp.ExpName + "_return"
}
// Build the wrapper function compiled by gcc.
s := fmt.Sprintf("%s %s(", gccResult, exp.ExpName)
if fn.Recv != nil {
s += p.cgoType(fn.Recv.List[0].Type).C
s += " recv"
}
forFieldList(fntype.Params,
func(i int, atype ast.Expr) {
if i > 0 || fn.Recv != nil {
s += ", "
}
s += fmt.Sprintf("%s p%d", p.cgoType(atype).C, i)
})
s += ")"
fmt.Fprintf(fgcch, "\nextern %s;\n", s)
fmt.Fprintf(fgcc, "extern _cgoexp_%s(void *, int);\n", exp.ExpName)
fmt.Fprintf(fgcc, "\n%s\n", s)
fmt.Fprintf(fgcc, "{\n")
fmt.Fprintf(fgcc, "\t%s a;\n", structType)
if gccResult != "void" && (len(fntype.Results.List) > 1 || len(fntype.Results.List[0].Names) > 1) {
fmt.Fprintf(fgcc, "\t%s r;\n", gccResult)
}
if fn.Recv != nil {
fmt.Fprintf(fgcc, "\ta.recv = recv;\n")
}
forFieldList(fntype.Params,
func(i int, atype ast.Expr) {
fmt.Fprintf(fgcc, "\ta.p%d = p%d;\n", i, i)
})
fmt.Fprintf(fgcc, "\tcrosscall2(_cgoexp_%s, &a, (int) sizeof a);\n", exp.ExpName)
if gccResult != "void" {
if len(fntype.Results.List) == 1 && len(fntype.Results.List[0].Names) <= 1 {
fmt.Fprintf(fgcc, "\treturn a.r0;\n")
} else {
forFieldList(fntype.Results,
func(i int, atype ast.Expr) {
fmt.Fprintf(fgcc, "\tr.r%d = a.r%d;\n", i, i)
})
fmt.Fprintf(fgcc, "\treturn r;\n")
}
}
fmt.Fprintf(fgcc, "}\n")
// Build the wrapper function compiled by 6c/8c
goname := exp.Func.Name.Name()
if fn.Recv != nil {
goname = "_cgoexpwrap_" + fn.Recv.List[0].Names[0].Name() + "_" + goname
}
fmt.Fprintf(fc, "#pragma dynexport _cgoexp_%s _cgoexp_%s\n", exp.ExpName, exp.ExpName)
fmt.Fprintf(fc, "extern void ·%s();\n", goname)
fmt.Fprintf(fc, "\nvoid\n")
fmt.Fprintf(fc, "_cgoexp_%s(void *a, int32 n)\n", exp.ExpName)
fmt.Fprintf(fc, "{\n")
fmt.Fprintf(fc, "\tcgocallback(·%s, a, n);\n", goname)
fmt.Fprintf(fc, "}\n")
// Calling a function with a receiver from C requires
// a Go wrapper function.
if fn.Recv != nil {
fmt.Fprintf(fgo2, "func %s(recv ", goname)
printer.Fprint(fgo2, fn.Recv.List[0].Type)
forFieldList(fntype.Params,
func(i int, atype ast.Expr) {
fmt.Fprintf(fgo2, ", p%d", i)
printer.Fprint(fgo2, atype)
})
fmt.Fprintf(fgo2, ")")
if gccResult != "void" {
fmt.Fprint(fgo2, " (")
forFieldList(fntype.Results,
func(i int, atype ast.Expr) {
if i > 0 {
fmt.Fprint(fgo2, ", ")
}
printer.Fprint(fgo2, atype)
})
fmt.Fprint(fgo2, ")")
}
fmt.Fprint(fgo2, " {\n")
fmt.Fprint(fgo2, "\t")
if gccResult != "void" {
fmt.Fprint(fgo2, "return ")
}
fmt.Fprintf(fgo2, "recv.%s(", exp.Func.Name)
forFieldList(fntype.Params,
func(i int, atype ast.Expr) {
if i > 0 {
fmt.Fprint(fgo2, ", ")
}
fmt.Fprintf(fgo2, "p%d", i)
})
fmt.Fprint(fgo2, ")\n")
fmt.Fprint(fgo2, "}\n")
}
}
}
// Call a function for each entry in an ast.FieldList, passing the
// index into the list and the type.
func forFieldList(fl *ast.FieldList, fn func(int, ast.Expr)) {
if fl == nil {
return
}
i := 0
for _, r := range fl.List {
if r.Names == nil {
fn(i, r.Type)
i++
} else {
for _ = range r.Names {
fn(i, r.Type)
i++
}
}
}
}
// Map predeclared Go types to Type.
var goTypes = map[string]*Type{
"int": &Type{Size: 4, Align: 4, C: "int"},
"uint": &Type{Size: 4, Align: 4, C: "uint"},
"int8": &Type{Size: 1, Align: 1, C: "schar"},
"uint8": &Type{Size: 1, Align: 1, C: "uchar"},
"int16": &Type{Size: 2, Align: 2, C: "short"},
"uint16": &Type{Size: 2, Align: 2, C: "ushort"},
"int32": &Type{Size: 4, Align: 4, C: "int"},
"uint32": &Type{Size: 4, Align: 4, C: "uint"},
"int64": &Type{Size: 8, Align: 8, C: "int64"},
"uint64": &Type{Size: 8, Align: 8, C: "uint64"},
"float": &Type{Size: 4, Align: 4, C: "float"},
"float32": &Type{Size: 4, Align: 4, C: "float"},
"float64": &Type{Size: 8, Align: 8, C: "double"},
"complex": &Type{Size: 8, Align: 8, C: "__complex float"},
"complex64": &Type{Size: 8, Align: 8, C: "__complex float"},
"complex128": &Type{Size: 16, Align: 16, C: "__complex double"},
}
// Map an ast type to a Type.
func (p *Prog) cgoType(e ast.Expr) *Type {
switch t := e.(type) {
case *ast.StarExpr:
x := p.cgoType(t.X)
return &Type{Size: p.PtrSize, Align: p.PtrSize, C: x.C + "*"}
case *ast.ArrayType:
if t.Len == nil {
return &Type{Size: p.PtrSize + 8, Align: p.PtrSize, C: "GoSlice"}
}
case *ast.StructType:
// TODO
case *ast.FuncType:
return &Type{Size: p.PtrSize, Align: p.PtrSize, C: "void*"}
case *ast.InterfaceType:
return &Type{Size: 3 * p.PtrSize, Align: p.PtrSize, C: "GoInterface"}
case *ast.MapType:
return &Type{Size: p.PtrSize, Align: p.PtrSize, C: "GoMap"}
case *ast.ChanType:
return &Type{Size: p.PtrSize, Align: p.PtrSize, C: "GoChan"}
case *ast.Ident:
// Look up the type in the top level declarations.
// TODO: Handle types defined within a function.
for _, d := range p.AST.Decls {
gd, ok := d.(*ast.GenDecl)
if !ok || gd.Tok != token.TYPE {
continue
}
for _, spec := range gd.Specs {
ts, ok := spec.(*ast.TypeSpec)
if !ok {
continue
}
if ts.Name.Name() == t.Name() {
return p.cgoType(ts.Type)
}
}
}
for name, def := range p.Typedef {
if name == t.Name() {
return p.cgoType(def)
}
}
if t.Name() == "uintptr" {
return &Type{Size: p.PtrSize, Align: p.PtrSize, C: "uintptr"}
}
if t.Name() == "string" {
return &Type{Size: p.PtrSize + 4, Align: p.PtrSize, C: "GoString"}
}
if r, ok := goTypes[t.Name()]; ok {
if r.Align > p.PtrSize {
r.Align = p.PtrSize
}
return r
}
}
error(e.Pos(), "unrecognized Go type %v", e)
return &Type{Size: 4, Align: 4, C: "int"}
}
const gccProlog = `
// Usual nonsense: if x and y are not equal, the type will be invalid
// (have a negative array count) and an inscrutable error will come
......@@ -275,6 +566,7 @@ const cProlog = `
#pragma dynimport initcgo initcgo "%s/libcgo.so"
#pragma dynimport libcgo_thread_start libcgo_thread_start "%s/libcgo.so"
#pragma dynimport libcgo_set_scheduler libcgo_set_scheduler "%s/libcgo.so"
#pragma dynimport _cgo_malloc _cgo_malloc "%s/libcgo.so"
#pragma dynimport _cgo_free free "%s/libcgo.so"
......@@ -294,3 +586,17 @@ void
FLUSH(&p);
}
`
const gccExportHeaderProlog = `
typedef unsigned int uint;
typedef signed char schar;
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef long long int64;
typedef unsigned long long uint64;
typedef struct { char *p; int n; } GoString;
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
`
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment