]> Cypherpunks repositories - gostls13.git/commitdiff
Add //export to cgo.
authorIan Lance Taylor <iant@golang.org>
Fri, 9 Apr 2010 20:31:05 +0000 (13:31 -0700)
committerIan Lance Taylor <iant@golang.org>
Fri, 9 Apr 2010 20:31:05 +0000 (13:31 -0700)
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

src/Make.pkg
src/cmd/cgo/ast.go
src/cmd/cgo/out.go

index d77564166557a91311557bd9478832113f2ef38e..e16c521d38352613d560769c8e1a8fdf26c2ca62 100644 (file)
@@ -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
index 0dfe9217a88f8476f65d0642f5003a1ad8a835a6..580a72a95848ff51f305f709d47addec41a74068 100644 (file)
@@ -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
+       }
+}
index 31df5a0c457f0b806f7ace98df2387e4b6bbfb4f..2fae48fe47b52dac0bb7e80101b7efa78c32eeba 100644 (file)
@@ -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;
+`