diff options
author | Ayke <[email protected]> | 2019-02-08 13:04:03 +0100 |
---|---|---|
committer | Ron Evans <[email protected]> | 2019-02-08 13:04:03 +0100 |
commit | 01f6aff4227120abf7dc98baef26f09d6576d856 (patch) | |
tree | 0ca0fd7ec3c4f2aa49a2369db3cbea7630265eeb | |
parent | 7dd5839f4741a2bf697ae9438019028a4058aa56 (diff) | |
download | tinygo-01f6aff4227120abf7dc98baef26f09d6576d856.tar.gz tinygo-01f6aff4227120abf7dc98baef26f09d6576d856.zip |
loader: support global variables in CGo (#173)
Global variables (like functions) must be declared in the import "C" preamble and can then be used from Go.
-rw-r--r-- | Gopkg.lock | 5 | ||||
-rw-r--r-- | ir/ir.go | 16 | ||||
-rw-r--r-- | loader/cgo.go | 92 | ||||
-rw-r--r-- | loader/libclang.go | 8 | ||||
-rw-r--r-- | testdata/cgo/main.c | 2 | ||||
-rw-r--r-- | testdata/cgo/main.go | 1 | ||||
-rw-r--r-- | testdata/cgo/main.h | 1 | ||||
-rw-r--r-- | testdata/cgo/out.txt | 1 |
8 files changed, 92 insertions, 34 deletions
diff --git a/Gopkg.lock b/Gopkg.lock index 040848202..aae7e7a5c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -3,7 +3,7 @@ [[projects]] branch = "master" - digest = "1:84316faef4ea12d34dde3b3e6dab682715a23b1c2bb8ab82cec9ab619766e214" + digest = "1:ba70784a3deee74c0ca3c87bcac3c2f93d3b2d27d8f237b768c358b45ba47da8" name = "golang.org/x/tools" packages = [ "go/ast/astutil", @@ -11,7 +11,7 @@ "go/types/typeutil", ] pruneopts = "UT" - revision = "3e7aa9e59977626dc60433e9aeadf1bb63d28295" + revision = "40960b6deb8ecdb8bcde6a8f44722731939b8ddc" [[projects]] branch = "master" @@ -25,6 +25,7 @@ analyzer-name = "dep" analyzer-version = 1 input-imports = [ + "golang.org/x/tools/go/ast/astutil", "golang.org/x/tools/go/ssa", "tinygo.org/x/go-llvm", ] @@ -389,11 +389,25 @@ func (g *Global) LinkName() string { if g.linkName != "" { return g.linkName } + if name := g.CName(); name != "" { + return name + } return g.RelString(nil) } func (g *Global) IsExtern() bool { - return g.extern + return g.extern || g.CName() != "" +} + +// Return the name of the C global if this is a CGo wrapper. Otherwise, return a +// zero-length string. +func (g *Global) CName() string { + name := g.Name() + if strings.HasPrefix(name, "C.") { + // created by ../loader/cgo.go + return name[2:] + } + return "" } func (g *Global) Initializer() Value { diff --git a/loader/cgo.go b/loader/cgo.go index f95e578bf..6aeae648f 100644 --- a/loader/cgo.go +++ b/loader/cgo.go @@ -9,6 +9,8 @@ import ( "sort" "strconv" "strings" + + "golang.org/x/tools/go/ast/astutil" ) // fileInfo holds all Cgo-related information of a given *ast.File. @@ -17,6 +19,7 @@ type fileInfo struct { filename string functions []*functionInfo typedefs []*typedefInfo + globals []*globalInfo importCPos token.Pos } @@ -41,6 +44,12 @@ type typedefInfo struct { size int // size in bytes } +// globalInfo contains information about a declared global variable in C. +type globalInfo struct { + name string + typeName string +} + // cgoAliases list type aliases between Go and C, for types that are equivalent // in both languages. See addTypeAliases. var cgoAliases = map[string]string{ @@ -122,6 +131,9 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro // Declare functions found by libclang. info.addFuncDecls() + // Declare globals found by libclang. + info.addVarDecls() + // Forward C types to Go types (like C.uint32_t -> uint32). info.addTypeAliases() @@ -129,7 +141,7 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro info.addTypedefs() // Patch the AST to use the declared types and functions. - ast.Inspect(f, info.walker) + f = astutil.Apply(f, info.walker, nil).(*ast.File) return nil } @@ -194,6 +206,43 @@ func (info *fileInfo) addFuncDecls() { } } +// addVarDecls declares external C globals in the Go source. +// It adds code like the following to the AST: +// +// var ( +// C.globalInt int +// C.globalBool bool +// // ... +// ) +func (info *fileInfo) addVarDecls() { + gen := &ast.GenDecl{ + TokPos: info.importCPos, + Tok: token.VAR, + Lparen: info.importCPos, + Rparen: info.importCPos, + } + for _, global := range info.globals { + obj := &ast.Object{ + Kind: ast.Typ, + Name: mapCgoType(global.name), + } + valueSpec := &ast.ValueSpec{ + Names: []*ast.Ident{&ast.Ident{ + NamePos: info.importCPos, + Name: mapCgoType(global.name), + Obj: obj, + }}, + Type: &ast.Ident{ + NamePos: info.importCPos, + Name: mapCgoType(global.typeName), + }, + } + obj.Decl = valueSpec + gen.Specs = append(gen.Specs, valueSpec) + } + info.Decls = append(info.Decls, gen) +} + // addTypeAliases aliases some built-in Go types with their equivalent C types. // It adds code like the following to the AST: // @@ -299,41 +348,22 @@ func (info *fileInfo) addTypedefs() { info.Decls = append(info.Decls, gen) } -// walker replaces all "C".<something> call expressions to literal -// "C.<something>" expressions. This is impossible to write in Go (a dot cannot -// be used in the middle of a name) so is used as a new namespace for C call -// expressions. -func (info *fileInfo) walker(node ast.Node) bool { - switch node := node.(type) { - case *ast.CallExpr: - fun, ok := node.Fun.(*ast.SelectorExpr) - if !ok { - return true - } - x, ok := fun.X.(*ast.Ident) +// walker replaces all "C".<something> expressions to literal "C.<something>" +// expressions. Such expressions are impossible to write in Go (a dot cannot be +// used in the middle of a name) so in practice all C identifiers live in a +// separate namespace (no _Cgo_ hacks like in gc). +func (info *fileInfo) walker(cursor *astutil.Cursor) bool { + switch node := cursor.Node().(type) { + case *ast.SelectorExpr: + x, ok := node.X.(*ast.Ident) if !ok { return true } if x.Name == "C" { - node.Fun = &ast.Ident{ + cursor.Replace(&ast.Ident{ NamePos: x.NamePos, - Name: mapCgoType(fun.Sel.Name), - } - } - case *ast.ValueSpec: - typ, ok := node.Type.(*ast.SelectorExpr) - if !ok { - return true - } - x, ok := typ.X.(*ast.Ident) - if !ok { - return true - } - if x.Name == "C" { - node.Type = &ast.Ident{ - NamePos: x.NamePos, - Name: mapCgoType(typ.Sel.Name), - } + Name: mapCgoType(node.Sel.Name), + }) } } return true diff --git a/loader/libclang.go b/loader/libclang.go index e92e31faa..370c8b1e8 100644 --- a/loader/libclang.go +++ b/loader/libclang.go @@ -112,6 +112,14 @@ func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.in oldName: underlyingTypeName, size: int(typeSize), }) + case C.CXCursor_VarDecl: + name := getString(C.clang_getCursorSpelling(c)) + cursorType := C.clang_getCursorType(c) + cursorTypeName := getString(C.clang_getTypeSpelling(cursorType)) + info.globals = append(info.globals, &globalInfo{ + name: name, + typeName: cursorTypeName, + }) } return C.CXChildVisit_Continue } diff --git a/testdata/cgo/main.c b/testdata/cgo/main.c index b0d4ba562..5fa47db98 100644 --- a/testdata/cgo/main.c +++ b/testdata/cgo/main.c @@ -1,5 +1,7 @@ #include "main.h" +int global = 3; + int fortytwo() { return 42; } diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index b8903a7dd..ba962e04c 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -16,4 +16,5 @@ func main() { println("myint size:", int(unsafe.Sizeof(x))) var y C.longlong = -(1 << 40) println("longlong:", y) + println("global:", C.global) } diff --git a/testdata/cgo/main.h b/testdata/cgo/main.h index f41711f40..6c1202253 100644 --- a/testdata/cgo/main.h +++ b/testdata/cgo/main.h @@ -1,2 +1,3 @@ typedef short myint; int add(int a, int b); +extern int global; diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index d6d208731..e0cec2f4a 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -3,3 +3,4 @@ add: 8 myint: 3 5 myint size: 2 longlong: -1099511627776 +global: 3 |