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.
Этот коммит содержится в:
родитель
7dd5839f47
коммит
01f6aff422
8 изменённых файлов: 92 добавлений и 34 удалений
5
Gopkg.lock
сгенерированный
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",
|
||||
]
|
||||
|
|
16
ir/ir.go
16
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 {
|
||||
|
|
|
@ -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
предоставленный
2
testdata/cgo/main.c
предоставленный
|
@ -1,5 +1,7 @@
|
|||
#include "main.h"
|
||||
|
||||
int global = 3;
|
||||
|
||||
int fortytwo() {
|
||||
return 42;
|
||||
}
|
||||
|
|
1
testdata/cgo/main.go
предоставленный
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
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
предоставленный
1
testdata/cgo/out.txt
предоставленный
|
@ -3,3 +3,4 @@ add: 8
|
|||
myint: 3 5
|
||||
myint size: 2
|
||||
longlong: -1099511627776
|
||||
global: 3
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче