Skip to content

Commit 01f6aff

Browse files
aykevldeadprogram
authored andcommitted
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.
1 parent 7dd5839 commit 01f6aff

File tree

8 files changed

+92
-34
lines changed

8 files changed

+92
-34
lines changed

Gopkg.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ir/ir.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,11 +389,25 @@ func (g *Global) LinkName() string {
389389
if g.linkName != "" {
390390
return g.linkName
391391
}
392+
if name := g.CName(); name != "" {
393+
return name
394+
}
392395
return g.RelString(nil)
393396
}
394397

395398
func (g *Global) IsExtern() bool {
396-
return g.extern
399+
return g.extern || g.CName() != ""
400+
}
401+
402+
// Return the name of the C global if this is a CGo wrapper. Otherwise, return a
403+
// zero-length string.
404+
func (g *Global) CName() string {
405+
name := g.Name()
406+
if strings.HasPrefix(name, "C.") {
407+
// created by ../loader/cgo.go
408+
return name[2:]
409+
}
410+
return ""
397411
}
398412

399413
func (g *Global) Initializer() Value {

loader/cgo.go

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"sort"
1010
"strconv"
1111
"strings"
12+
13+
"golang.org/x/tools/go/ast/astutil"
1214
)
1315

1416
// fileInfo holds all Cgo-related information of a given *ast.File.
@@ -17,6 +19,7 @@ type fileInfo struct {
1719
filename string
1820
functions []*functionInfo
1921
typedefs []*typedefInfo
22+
globals []*globalInfo
2023
importCPos token.Pos
2124
}
2225

@@ -41,6 +44,12 @@ type typedefInfo struct {
4144
size int // size in bytes
4245
}
4346

47+
// globalInfo contains information about a declared global variable in C.
48+
type globalInfo struct {
49+
name string
50+
typeName string
51+
}
52+
4453
// cgoAliases list type aliases between Go and C, for types that are equivalent
4554
// in both languages. See addTypeAliases.
4655
var cgoAliases = map[string]string{
@@ -122,14 +131,17 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro
122131
// Declare functions found by libclang.
123132
info.addFuncDecls()
124133

134+
// Declare globals found by libclang.
135+
info.addVarDecls()
136+
125137
// Forward C types to Go types (like C.uint32_t -> uint32).
126138
info.addTypeAliases()
127139

128140
// Add type declarations for C types, declared using typeef in C.
129141
info.addTypedefs()
130142

131143
// Patch the AST to use the declared types and functions.
132-
ast.Inspect(f, info.walker)
144+
f = astutil.Apply(f, info.walker, nil).(*ast.File)
133145

134146
return nil
135147
}
@@ -194,6 +206,43 @@ func (info *fileInfo) addFuncDecls() {
194206
}
195207
}
196208

209+
// addVarDecls declares external C globals in the Go source.
210+
// It adds code like the following to the AST:
211+
//
212+
// var (
213+
// C.globalInt int
214+
// C.globalBool bool
215+
// // ...
216+
// )
217+
func (info *fileInfo) addVarDecls() {
218+
gen := &ast.GenDecl{
219+
TokPos: info.importCPos,
220+
Tok: token.VAR,
221+
Lparen: info.importCPos,
222+
Rparen: info.importCPos,
223+
}
224+
for _, global := range info.globals {
225+
obj := &ast.Object{
226+
Kind: ast.Typ,
227+
Name: mapCgoType(global.name),
228+
}
229+
valueSpec := &ast.ValueSpec{
230+
Names: []*ast.Ident{&ast.Ident{
231+
NamePos: info.importCPos,
232+
Name: mapCgoType(global.name),
233+
Obj: obj,
234+
}},
235+
Type: &ast.Ident{
236+
NamePos: info.importCPos,
237+
Name: mapCgoType(global.typeName),
238+
},
239+
}
240+
obj.Decl = valueSpec
241+
gen.Specs = append(gen.Specs, valueSpec)
242+
}
243+
info.Decls = append(info.Decls, gen)
244+
}
245+
197246
// addTypeAliases aliases some built-in Go types with their equivalent C types.
198247
// It adds code like the following to the AST:
199248
//
@@ -299,41 +348,22 @@ func (info *fileInfo) addTypedefs() {
299348
info.Decls = append(info.Decls, gen)
300349
}
301350

302-
// walker replaces all "C".<something> call expressions to literal
303-
// "C.<something>" expressions. This is impossible to write in Go (a dot cannot
304-
// be used in the middle of a name) so is used as a new namespace for C call
305-
// expressions.
306-
func (info *fileInfo) walker(node ast.Node) bool {
307-
switch node := node.(type) {
308-
case *ast.CallExpr:
309-
fun, ok := node.Fun.(*ast.SelectorExpr)
310-
if !ok {
311-
return true
312-
}
313-
x, ok := fun.X.(*ast.Ident)
351+
// walker replaces all "C".<something> expressions to literal "C.<something>"
352+
// expressions. Such expressions are impossible to write in Go (a dot cannot be
353+
// used in the middle of a name) so in practice all C identifiers live in a
354+
// separate namespace (no _Cgo_ hacks like in gc).
355+
func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
356+
switch node := cursor.Node().(type) {
357+
case *ast.SelectorExpr:
358+
x, ok := node.X.(*ast.Ident)
314359
if !ok {
315360
return true
316361
}
317362
if x.Name == "C" {
318-
node.Fun = &ast.Ident{
363+
cursor.Replace(&ast.Ident{
319364
NamePos: x.NamePos,
320-
Name: mapCgoType(fun.Sel.Name),
321-
}
322-
}
323-
case *ast.ValueSpec:
324-
typ, ok := node.Type.(*ast.SelectorExpr)
325-
if !ok {
326-
return true
327-
}
328-
x, ok := typ.X.(*ast.Ident)
329-
if !ok {
330-
return true
331-
}
332-
if x.Name == "C" {
333-
node.Type = &ast.Ident{
334-
NamePos: x.NamePos,
335-
Name: mapCgoType(typ.Sel.Name),
336-
}
365+
Name: mapCgoType(node.Sel.Name),
366+
})
337367
}
338368
}
339369
return true

loader/libclang.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.in
112112
oldName: underlyingTypeName,
113113
size: int(typeSize),
114114
})
115+
case C.CXCursor_VarDecl:
116+
name := getString(C.clang_getCursorSpelling(c))
117+
cursorType := C.clang_getCursorType(c)
118+
cursorTypeName := getString(C.clang_getTypeSpelling(cursorType))
119+
info.globals = append(info.globals, &globalInfo{
120+
name: name,
121+
typeName: cursorTypeName,
122+
})
115123
}
116124
return C.CXChildVisit_Continue
117125
}

testdata/cgo/main.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include "main.h"
22

3+
int global = 3;
4+
35
int fortytwo() {
46
return 42;
57
}

testdata/cgo/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ func main() {
1616
println("myint size:", int(unsafe.Sizeof(x)))
1717
var y C.longlong = -(1 << 40)
1818
println("longlong:", y)
19+
println("global:", C.global)
1920
}

testdata/cgo/main.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
typedef short myint;
22
int add(int a, int b);
3+
extern int global;

testdata/cgo/out.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ add: 8
33
myint: 3 5
44
myint size: 2
55
longlong: -1099511627776
6+
global: 3

0 commit comments

Comments
 (0)