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.
Этот коммит содержится в:
Ayke 2019-02-08 13:04:03 +01:00 коммит произвёл Ron Evans
родитель 7dd5839f47
коммит 01f6aff422
8 изменённых файлов: 92 добавлений и 34 удалений

5
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 {

Просмотреть файл

@ -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

Просмотреть файл

@ -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
}

2
testdata/cgo/main.c предоставленный
Просмотреть файл

@ -1,5 +1,7 @@
#include "main.h"
int global = 3;
int fortytwo() {
return 42;
}

1
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)
}

1
testdata/cgo/main.h предоставленный
Просмотреть файл

@ -1,2 +1,3 @@
typedef short myint;
int add(int a, int b);
extern int global;

1
testdata/cgo/out.txt предоставленный
Просмотреть файл

@ -3,3 +3,4 @@ add: 8
myint: 3 5
myint size: 2
longlong: -1099511627776
global: 3