Allow cgo to accept multiple .go inputs for a package

Fixes #342.

R=rsc
CC=golang-dev
https://golang.org/cl/179062
This commit is contained in:
Devon H. O'Dell 2009-12-17 13:20:56 -08:00 committed by Russ Cox
parent 7a5f4be97e
commit 9277b02537
4 changed files with 195 additions and 100 deletions

View File

@ -36,18 +36,21 @@ INSTALLFILES=$(pkgdir)/$(TARG).a
# The rest of the cgo rules are below, but these variable updates # The rest of the cgo rules are below, but these variable updates
# must be done here so they apply to the main rules. # must be done here so they apply to the main rules.
ifdef CGOFILES
GOFILES+=$(patsubst %.go,%.cgo1.go,$(CGOFILES)) GOFILES+=$(patsubst %.go,%.cgo1.go,$(CGOFILES))
GOFILES+=$(patsubst %.go,%.cgo2.go,$(CGOFILES)) GOFILES+=_cgo_gotypes.go
OFILES+=$(patsubst %.go,%.cgo3.$O,$(CGOFILES)) OFILES+=_cgo_defun.$O
INSTALLFILES+=$(patsubst %.go,$(pkgdir)/$(dir)/$(elem)_%.so,$(CGOFILES)) GCC_OFILES=$(patsubst %.go,%.cgo2.o,$(CGOFILES))
INSTALLFILES+=$(pkgdir)/$(dir)/$(TARG).so
PREREQ+=$(patsubst %,%.make,$(DEPS)) PREREQ+=$(patsubst %,%.make,$(DEPS))
endif
coverage: coverage:
$(QUOTED_GOBIN)/gotest $(QUOTED_GOBIN)/gotest
$(QUOTED_GOBIN)/6cov -g $(shell pwd) $O.out | grep -v '_test\.go:' $(QUOTED_GOBIN)/6cov -g $(shell pwd) $O.out | grep -v '_test\.go:'
clean: clean:
rm -rf *.[$(OS)o] *.a [$(OS)].out *.cgo[12].go *.cgo[34].c *.so _obj _test _testmain.go $(CLEANFILES) rm -rf *.[$(OS)o] *.a [$(OS)].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go *.so _obj _test _testmain.go $(CLEANFILES)
test: test:
$(QUOTED_GOBIN)/gotest $(QUOTED_GOBIN)/gotest
@ -91,32 +94,42 @@ dir:
# To use cgo in a Go package, add a line # To use cgo in a Go package, add a line
# #
# CGOFILES=x.go # CGOFILES=x.go y.go
# #
# to the main Makefile. This signals that cgo should process x.go. # 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, # There are two optional variables to set, CGO_CFLAGS and CGO_LDFLAGS,
# which specify compiler and linker flags to use when compiling # which specify compiler and linker flags to use when compiling
# (using gcc) the C support for x.go. # (using gcc) the C support for x.go and y.go.
# Cgo translates each x.go file listed in $(CGOFILES) into # Cgo translates each x.go file listed in $(CGOFILES) into a basic
# translation of x.go, called x.cgo1.go. Additionally, three other
# files are created:
# #
# x.cgo1.go - basic translation of x.go # _cgo_gotypes.go - declarations needed for all .go files in the package; imports "unsafe"
# x.cgo2.go - declarations needed for x.cgo1.go; imports "unsafe" # _cgo_defun.c - C trampoline code to be compiled with 6c and linked into the package
# x.cgo3.c - C trampoline code to be compiled with 6c and linked into the package # x.cgo2.c - C implementations compiled with gcc to create a dynamic library
# x.cgo4.c - C implementations compiled with gcc to create dynamic library
# #
%.cgo1.go %.cgo2.go %.cgo3.c %.cgo4.c: %.go
CGOPKGPATH=$(dir) $(QUOTED_GOBIN)/cgo $(CGO_CFLAGS) $*.go
# The rules above added x.cgo1.go and x.cgo2.go to $(GOFILES), _cgo_defun.c _cgo_gotypes.go: $(CGOFILES)
# added x.cgo3.$O to $OFILES, and added the installed copy of CGOPKGPATH=$(dir) $(QUOTED_GOBIN)/cgo $(CGO_CFLAGS) $(CGOFILES)
# package_x.so (built from x.cgo4.c) to $(INSTALLFILES).
# Ugly but necessary
%.cgo1.go: _cgo_defun.c _cgo_gotypes.go
@true
%.cgo2.c: _cgo_defun.c _cgo_gotypes.go
@true
%.cgo2.o: %.cgo2.c
gcc $(_CGO_CFLAGS_$(GOARCH)) -fPIC -O2 -o $@ -c $(CGO_CFLAGS) $*.cgo2.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).
# Compile x.cgo3.c with 6c; needs access to the runtime headers.
RUNTIME_CFLAGS_amd64=-D_64BIT RUNTIME_CFLAGS_amd64=-D_64BIT
RUNTIME_CFLAGS=-I"$(GOROOT)/src/pkg/runtime" $(RUNTIME_CFLAGS_$(GOARCH)) RUNTIME_CFLAGS=-I"$(GOROOT)/src/pkg/runtime" $(RUNTIME_CFLAGS_$(GOARCH))
%.cgo3.$O: %.cgo3.c
$(QUOTED_GOBIN)/$(CC) $(CFLAGS) $(RUNTIME_CFLAGS) $*.cgo3.c
# Have to run gcc with the right size argument on hybrid 32/64 machines. # Have to run gcc with the right size argument on hybrid 32/64 machines.
_CGO_CFLAGS_386=-m32 _CGO_CFLAGS_386=-m32
@ -127,15 +140,17 @@ _CGO_LDFLAGS_darwin=-dynamiclib -Wl,-undefined,dynamic_lookup
# Compile x.cgo4.c with gcc to make package_x.so. # Compile x.cgo4.c with gcc to make package_x.so.
%.cgo4.o: %.cgo4.c
gcc $(_CGO_CFLAGS_$(GOARCH)) -fPIC -O2 -o $@ -c $(CGO_CFLAGS) $*.cgo4.c
$(elem)_%.so: %.cgo4.o # Compile _cgo_defun.c with 6c; needs access to the runtime headers.
gcc $(_CGO_CFLAGS_$(GOARCH)) -o $@ $*.cgo4.o $(CGO_LDFLAGS) $(_CGO_LDFLAGS_$(GOOS)) _cgo_defun.$O: _cgo_defun.c
$(QUOTED_GOBIN)/$(CC) $(CFLAGS) $(RUNTIME_CFLAGS) _cgo_defun.c
$(pkgdir)/$(dir)/$(elem)_%.so: $(elem)_%.so _cgo_.so: $(GCC_OFILES)
gcc $(_CGO_CFLAGS_$(GOARCH)) -o $@ $(GCC_OFILES) $(CGO_LDFLAGS) $(_CGO_LDFLAGS_$(GOOS))
$(pkgdir)/$(dir)/$(TARG).so: _cgo_.so
@test -d $(QUOTED_GOROOT/pkg && mkdir -p $(pkgdir)/$(dir) @test -d $(QUOTED_GOROOT/pkg && mkdir -p $(pkgdir)/$(dir)
cp $(elem)_$*.so "$@" cp _cgo_.so "$@"
# Generic build rules. # Generic build rules.
# These come last so that the rules above can override them # These come last so that the rules above can override them

View File

@ -38,6 +38,7 @@ type Prog struct {
Enumdef map[string]int64 Enumdef map[string]int64
PtrSize int64 PtrSize int64
GccOptions []string GccOptions []string
OutDefs map[string]bool
} }
// A Type collects information about a type in both the C and Go worlds. // A Type collects information about a type in both the C and Go worlds.
@ -56,8 +57,7 @@ type FuncType struct {
Go *ast.FuncType Go *ast.FuncType
} }
func openProg(name string) *Prog { func openProg(name string, p *Prog) {
p := new(Prog)
var err os.Error var err os.Error
p.AST, err = parser.ParsePkgFile("", name, parser.ParseComments) p.AST, err = parser.ParsePkgFile("", name, parser.ParseComments)
if err != nil { if err != nil {
@ -120,7 +120,6 @@ func openProg(name string) *Prog {
// Accumulate pointers to uses of C.x. // Accumulate pointers to uses of C.x.
p.Crefs = make([]*Cref, 0, 8) p.Crefs = make([]*Cref, 0, 8)
walk(p.AST, p, "prog") walk(p.AST, p, "prog")
return p
} }
func walk(x interface{}, p *Prog, context string) { func walk(x interface{}, p *Prog, context string) {

View File

@ -14,9 +14,10 @@ import (
"fmt" "fmt"
"go/ast" "go/ast"
"os" "os"
"strings"
) )
func usage() { fmt.Fprint(os.Stderr, "usage: cgo [compiler options] file.go\n") } func usage() { fmt.Fprint(os.Stderr, "usage: cgo [compiler options] file.go ...\n") }
var ptrSizeMap = map[string]int64{ var ptrSizeMap = map[string]int64{
"386": 4, "386": 4,
@ -40,8 +41,19 @@ func main() {
usage() usage()
os.Exit(2) os.Exit(2)
} }
gccOptions := args[1 : len(args)-1]
input := args[len(args)-1] // Find first arg that looks like a go file and assume everything before
// that are options to pass to gcc.
var i int
for i = len(args) - 1; i > 0; i-- {
if !strings.HasSuffix(args[i], ".go") {
break
}
}
i += 1
gccOptions, goFiles := args[1:i], args[i:]
arch := os.Getenv("GOARCH") arch := os.Getenv("GOARCH")
if arch == "" { if arch == "" {
@ -57,59 +69,66 @@ func main() {
os.Setenv("LC_ALL", "C") os.Setenv("LC_ALL", "C")
os.Setenv("LC_CTYPE", "C") os.Setenv("LC_CTYPE", "C")
p := openProg(input) p := new(Prog)
for _, cref := range p.Crefs {
// Convert C.ulong to C.unsigned long, etc.
if expand, ok := expandName[cref.Name]; ok {
cref.Name = expand
}
}
p.PtrSize = ptrSize p.PtrSize = ptrSize
p.Preamble = p.Preamble + "\n" + builtinProlog
p.GccOptions = gccOptions p.GccOptions = gccOptions
p.loadDebugInfo()
p.Vardef = make(map[string]*Type) p.Vardef = make(map[string]*Type)
p.Funcdef = make(map[string]*FuncType) p.Funcdef = make(map[string]*FuncType)
p.Enumdef = make(map[string]int64) p.Enumdef = make(map[string]int64)
p.OutDefs = make(map[string]bool)
for _, cref := range p.Crefs { for _, input := range goFiles {
switch cref.Context { // Reset p.Preamble so that we don't end up with conflicting headers / defines
case "call": p.Preamble = builtinProlog
if !cref.TypeName { openProg(input, p)
// Is an actual function call. for _, cref := range p.Crefs {
*cref.Expr = &ast.Ident{Value: "_C_" + cref.Name} // Convert C.ulong to C.unsigned long, etc.
p.Funcdef[cref.Name] = cref.FuncType if expand, ok := expandName[cref.Name]; ok {
break cref.Name = expand
} }
*cref.Expr = cref.Type.Go
case "expr":
if cref.TypeName {
error((*cref.Expr).Pos(), "type C.%s used as expression", cref.Name)
}
// If the expression refers to an enumerated value, then
// place the identifier for the value and add it to Enumdef so
// it will be declared as a constant in the later stage.
if cref.Type.EnumValues != nil {
*cref.Expr = &ast.Ident{Value: cref.Name}
p.Enumdef[cref.Name] = cref.Type.EnumValues[cref.Name]
break
}
// Reference to C variable.
// We declare a pointer and arrange to have it filled in.
*cref.Expr = &ast.StarExpr{X: &ast.Ident{Value: "_C_" + cref.Name}}
p.Vardef[cref.Name] = cref.Type
case "type":
if !cref.TypeName {
error((*cref.Expr).Pos(), "expression C.%s used as type", cref.Name)
}
*cref.Expr = cref.Type.Go
} }
} p.loadDebugInfo()
if nerrors > 0 { for _, cref := range p.Crefs {
os.Exit(2) switch cref.Context {
case "call":
if !cref.TypeName {
// Is an actual function call.
*cref.Expr = &ast.Ident{Value: "_C_" + cref.Name}
p.Funcdef[cref.Name] = cref.FuncType
break
}
*cref.Expr = cref.Type.Go
case "expr":
if cref.TypeName {
error((*cref.Expr).Pos(), "type C.%s used as expression", cref.Name)
}
// If the expression refers to an enumerated value, then
// place the identifier for the value and add it to Enumdef so
// it will be declared as a constant in the later stage.
if cref.Type.EnumValues != nil {
*cref.Expr = &ast.Ident{Value: cref.Name}
p.Enumdef[cref.Name] = cref.Type.EnumValues[cref.Name]
break
}
// Reference to C variable.
// We declare a pointer and arrange to have it filled in.
*cref.Expr = &ast.StarExpr{X: &ast.Ident{Value: "_C_" + cref.Name}}
p.Vardef[cref.Name] = cref.Type
case "type":
if !cref.TypeName {
error((*cref.Expr).Pos(), "expression C.%s used as type", cref.Name)
}
*cref.Expr = cref.Type.Go
}
}
if nerrors > 0 {
os.Exit(2)
}
p.PackagePath = os.Getenv("CGOPKGPATH") + "/" + p.Package
p.writeOutput(input)
} }
p.PackagePath = os.Getenv("CGOPKGPATH") + "/" + p.Package p.writeDefs()
p.writeOutput(input)
} }

View File

@ -20,24 +20,13 @@ func creat(name string) *os.File {
return f return f
} }
// writeOutput creates output files to be compiled by 6g, 6c, and gcc. // writeDefs creates output files to be compiled by 6g, 6c, and gcc.
// (The comments here say 6g and 6c but the code applies to the 8 and 5 tools too.) // (The comments here say 6g and 6c but the code applies to the 8 and 5 tools too.)
func (p *Prog) writeOutput(srcfile string) { func (p *Prog) writeDefs() {
pkgroot := os.Getenv("GOROOT") + "/pkg/" + os.Getenv("GOOS") + "_" + os.Getenv("GOARCH") pkgroot := os.Getenv("GOROOT") + "/pkg/" + os.Getenv("GOOS") + "_" + os.Getenv("GOARCH")
base := srcfile fgo2 := creat("_cgo_gotypes.go")
if strings.HasSuffix(base, ".go") { fc := creat("_cgo_defun.c")
base = base[0 : len(base)-3]
}
fgo1 := creat(base + ".cgo1.go")
fgo2 := creat(base + ".cgo2.go")
fc := creat(base + ".cgo3.c")
fgcc := creat(base + ".cgo4.c")
// Write Go output: Go input with rewrites of C.xxx to _C_xxx.
fmt.Fprintf(fgo1, "// Created by cgo - DO NOT EDIT\n")
fmt.Fprintf(fgo1, "//line %s:1\n", srcfile)
printer.Fprint(fgo1, p.AST)
// Write second Go output: definitions of _C_xxx. // Write second Go output: definitions of _C_xxx.
// In a separate file so that the import of "unsafe" does not // In a separate file so that the import of "unsafe" does not
@ -54,15 +43,10 @@ func (p *Prog) writeOutput(srcfile string) {
} }
fmt.Fprintf(fgo2, "type _C_void [0]byte\n") fmt.Fprintf(fgo2, "type _C_void [0]byte\n")
// While we process the vars and funcs, also write 6c and gcc output.
// Gcc output starts with the preamble.
fmt.Fprintf(fgcc, "%s\n", p.Preamble)
fmt.Fprintf(fgcc, "%s\n", gccProlog)
fmt.Fprintf(fc, cProlog, pkgroot, pkgroot, pkgroot, pkgroot, p.Package, p.Package) fmt.Fprintf(fc, cProlog, pkgroot, pkgroot, pkgroot, pkgroot, p.Package, p.Package)
for name, def := range p.Vardef { for name, def := range p.Vardef {
fmt.Fprintf(fc, "#pragma dynld %s·_C_%s %s \"%s/%s_%s.so\"\n", p.Package, name, name, pkgroot, p.PackagePath, base) fmt.Fprintf(fc, "#pragma dynld %s·_C_%s %s \"%s/%s.so\"\n", p.Package, name, name, pkgroot, p.PackagePath)
fmt.Fprintf(fgo2, "var _C_%s ", name) fmt.Fprintf(fgo2, "var _C_%s ", name)
printer.Fprint(fgo2, &ast.StarExpr{X: def.Go}) printer.Fprint(fgo2, &ast.StarExpr{X: def.Go})
fmt.Fprintf(fgo2, "\n") fmt.Fprintf(fgo2, "\n")
@ -137,7 +121,7 @@ func (p *Prog) writeOutput(srcfile string) {
// C wrapper calls into gcc, passing a pointer to the argument frame. // C wrapper calls into gcc, passing a pointer to the argument frame.
// Also emit #pragma to get a pointer to the gcc wrapper. // Also emit #pragma to get a pointer to the gcc wrapper.
fmt.Fprintf(fc, "#pragma dynld _cgo_%s _cgo_%s \"%s/%s_%s.so\"\n", name, name, pkgroot, p.PackagePath, base) fmt.Fprintf(fc, "#pragma dynld _cgo_%s _cgo_%s \"%s/%s.so\"\n", name, name, pkgroot, p.PackagePath)
fmt.Fprintf(fc, "void (*_cgo_%s)(void*);\n", name) fmt.Fprintf(fc, "void (*_cgo_%s)(void*);\n", name)
fmt.Fprintf(fc, "\n") fmt.Fprintf(fc, "\n")
fmt.Fprintf(fc, "void\n") fmt.Fprintf(fc, "void\n")
@ -146,6 +130,86 @@ func (p *Prog) writeOutput(srcfile string) {
fmt.Fprintf(fc, "\tcgocall(_cgo_%s, &p);\n", name) fmt.Fprintf(fc, "\tcgocall(_cgo_%s, &p);\n", name)
fmt.Fprintf(fc, "}\n") fmt.Fprintf(fc, "}\n")
fmt.Fprintf(fc, "\n") fmt.Fprintf(fc, "\n")
}
fgo2.Close()
fc.Close()
}
// writeOutput creates stubs for a specific source file to be compiled by 6g
// (The comments here say 6g and 6c but the code applies to the 8 and 5 tools too.)
func (p *Prog) writeOutput(srcfile string) {
base := srcfile
if strings.HasSuffix(base, ".go") {
base = base[0 : len(base)-3]
}
fgo1 := creat(base + ".cgo1.go")
fgcc := creat(base + ".cgo2.c")
// Write Go output: Go input with rewrites of C.xxx to _C_xxx.
fmt.Fprintf(fgo1, "// Created by cgo - DO NOT EDIT\n")
fmt.Fprintf(fgo1, "//line %s:1\n", srcfile)
printer.Fprint(fgo1, p.AST)
// While we process the vars and funcs, also write 6c and gcc output.
// Gcc output starts with the preamble.
fmt.Fprintf(fgcc, "%s\n", p.Preamble)
fmt.Fprintf(fgcc, "%s\n", gccProlog)
for name, def := range p.Funcdef {
_, ok := p.OutDefs[name]
if name == "CString" || name == "GoString" || ok {
// The builtins are already defined in the C prolog, and we don't
// want to duplicate function definitions we've already done.
continue
}
p.OutDefs[name] = true
// Construct a gcc struct matching the 6c argument frame.
// Assumes that in gcc, char is 1 byte, short 2 bytes, int 4 bytes, long long 8 bytes.
// These assumptions are checked by the gccProlog.
// Also assumes that 6c convention is to word-align the
// input and output parameters.
structType := "struct {\n"
off := int64(0)
npad := 0
for i, t := range def.Params {
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++
}
if t := def.Result; t != nil {
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;\n", t.C)
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 len(def.Params) == 0 && def.Result == nil {
structType += "\t\tchar unused;\n" // avoid empty struct
off++
}
structType += "\t}"
// Gcc wrapper unpacks the C argument struct // Gcc wrapper unpacks the C argument struct
// and calls the actual C function. // and calls the actual C function.
@ -170,8 +234,6 @@ func (p *Prog) writeOutput(srcfile string) {
} }
fgo1.Close() fgo1.Close()
fgo2.Close()
fc.Close()
fgcc.Close() fgcc.Close()
} }