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]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:84316faef4ea12d34dde3b3e6dab682715a23b1c2bb8ab82cec9ab619766e214"
|
digest = "1:ba70784a3deee74c0ca3c87bcac3c2f93d3b2d27d8f237b768c358b45ba47da8"
|
||||||
name = "golang.org/x/tools"
|
name = "golang.org/x/tools"
|
||||||
packages = [
|
packages = [
|
||||||
"go/ast/astutil",
|
"go/ast/astutil",
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
"go/types/typeutil",
|
"go/types/typeutil",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "3e7aa9e59977626dc60433e9aeadf1bb63d28295"
|
revision = "40960b6deb8ecdb8bcde6a8f44722731939b8ddc"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -25,6 +25,7 @@
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
input-imports = [
|
input-imports = [
|
||||||
|
"golang.org/x/tools/go/ast/astutil",
|
||||||
"golang.org/x/tools/go/ssa",
|
"golang.org/x/tools/go/ssa",
|
||||||
"tinygo.org/x/go-llvm",
|
"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 != "" {
|
if g.linkName != "" {
|
||||||
return g.linkName
|
return g.linkName
|
||||||
}
|
}
|
||||||
|
if name := g.CName(); name != "" {
|
||||||
|
return name
|
||||||
|
}
|
||||||
return g.RelString(nil)
|
return g.RelString(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Global) IsExtern() bool {
|
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 {
|
func (g *Global) Initializer() Value {
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/ast/astutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// fileInfo holds all Cgo-related information of a given *ast.File.
|
// fileInfo holds all Cgo-related information of a given *ast.File.
|
||||||
|
@ -17,6 +19,7 @@ type fileInfo struct {
|
||||||
filename string
|
filename string
|
||||||
functions []*functionInfo
|
functions []*functionInfo
|
||||||
typedefs []*typedefInfo
|
typedefs []*typedefInfo
|
||||||
|
globals []*globalInfo
|
||||||
importCPos token.Pos
|
importCPos token.Pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +44,12 @@ type typedefInfo struct {
|
||||||
size int // size in bytes
|
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
|
// cgoAliases list type aliases between Go and C, for types that are equivalent
|
||||||
// in both languages. See addTypeAliases.
|
// in both languages. See addTypeAliases.
|
||||||
var cgoAliases = map[string]string{
|
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.
|
// Declare functions found by libclang.
|
||||||
info.addFuncDecls()
|
info.addFuncDecls()
|
||||||
|
|
||||||
|
// Declare globals found by libclang.
|
||||||
|
info.addVarDecls()
|
||||||
|
|
||||||
// Forward C types to Go types (like C.uint32_t -> uint32).
|
// Forward C types to Go types (like C.uint32_t -> uint32).
|
||||||
info.addTypeAliases()
|
info.addTypeAliases()
|
||||||
|
|
||||||
|
@ -129,7 +141,7 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro
|
||||||
info.addTypedefs()
|
info.addTypedefs()
|
||||||
|
|
||||||
// Patch the AST to use the declared types and functions.
|
// 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
|
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.
|
// addTypeAliases aliases some built-in Go types with their equivalent C types.
|
||||||
// It adds code like the following to the AST:
|
// It adds code like the following to the AST:
|
||||||
//
|
//
|
||||||
|
@ -299,41 +348,22 @@ func (info *fileInfo) addTypedefs() {
|
||||||
info.Decls = append(info.Decls, gen)
|
info.Decls = append(info.Decls, gen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// walker replaces all "C".<something> call expressions to literal
|
// walker replaces all "C".<something> expressions to literal "C.<something>"
|
||||||
// "C.<something>" expressions. This is impossible to write in Go (a dot cannot
|
// expressions. Such expressions are impossible to write in Go (a dot cannot be
|
||||||
// be used in the middle of a name) so is used as a new namespace for C call
|
// used in the middle of a name) so in practice all C identifiers live in a
|
||||||
// expressions.
|
// separate namespace (no _Cgo_ hacks like in gc).
|
||||||
func (info *fileInfo) walker(node ast.Node) bool {
|
func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
|
||||||
switch node := node.(type) {
|
switch node := cursor.Node().(type) {
|
||||||
case *ast.CallExpr:
|
case *ast.SelectorExpr:
|
||||||
fun, ok := node.Fun.(*ast.SelectorExpr)
|
x, ok := node.X.(*ast.Ident)
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
x, ok := fun.X.(*ast.Ident)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if x.Name == "C" {
|
if x.Name == "C" {
|
||||||
node.Fun = &ast.Ident{
|
cursor.Replace(&ast.Ident{
|
||||||
NamePos: x.NamePos,
|
NamePos: x.NamePos,
|
||||||
Name: mapCgoType(fun.Sel.Name),
|
Name: mapCgoType(node.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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -112,6 +112,14 @@ func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.in
|
||||||
oldName: underlyingTypeName,
|
oldName: underlyingTypeName,
|
||||||
size: int(typeSize),
|
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
|
return C.CXChildVisit_Continue
|
||||||
}
|
}
|
||||||
|
|
2
testdata/cgo/main.c
предоставленный
2
testdata/cgo/main.c
предоставленный
|
@ -1,5 +1,7 @@
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
|
||||||
|
int global = 3;
|
||||||
|
|
||||||
int fortytwo() {
|
int fortytwo() {
|
||||||
return 42;
|
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)))
|
println("myint size:", int(unsafe.Sizeof(x)))
|
||||||
var y C.longlong = -(1 << 40)
|
var y C.longlong = -(1 << 40)
|
||||||
println("longlong:", y)
|
println("longlong:", y)
|
||||||
|
println("global:", C.global)
|
||||||
}
|
}
|
||||||
|
|
1
testdata/cgo/main.h
предоставленный
1
testdata/cgo/main.h
предоставленный
|
@ -1,2 +1,3 @@
|
||||||
typedef short myint;
|
typedef short myint;
|
||||||
int add(int a, int b);
|
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: 3 5
|
||||||
myint size: 2
|
myint size: 2
|
||||||
longlong: -1099511627776
|
longlong: -1099511627776
|
||||||
|
global: 3
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче