aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke <[email protected]>2019-02-08 13:04:03 +0100
committerRon Evans <[email protected]>2019-02-08 13:04:03 +0100
commit01f6aff4227120abf7dc98baef26f09d6576d856 (patch)
tree0ca0fd7ec3c4f2aa49a2369db3cbea7630265eeb
parent7dd5839f4741a2bf697ae9438019028a4058aa56 (diff)
downloadtinygo-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.lock5
-rw-r--r--ir/ir.go16
-rw-r--r--loader/cgo.go92
-rw-r--r--loader/libclang.go8
-rw-r--r--testdata/cgo/main.c2
-rw-r--r--testdata/cgo/main.go1
-rw-r--r--testdata/cgo/main.h1
-rw-r--r--testdata/cgo/out.txt1
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",
]
diff --git a/ir/ir.go b/ir/ir.go
index bcbf128fa..884b78bea 100644
--- a/ir/ir.go
+++ b/ir/ir.go
@@ -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