Browse Source

cgo: add support for #define constants

These are converted to Go constants where possible.
pull/346/head
Ayke van Laethem 6 years ago
committed by Ron Evans
parent
commit
99587fe073
  1. 50
      loader/cgo.go
  2. 83
      loader/libclang.go
  3. 4
      loader/libclang_stubs.c
  4. 4
      testdata/cgo/main.go
  5. 7
      testdata/cgo/main.h
  6. 4
      testdata/cgo/out.txt

50
loader/cgo.go

@ -18,6 +18,7 @@ type fileInfo struct {
*ast.File
*Package
filename string
constants map[string]*ast.BasicLit
functions map[string]*functionInfo
globals map[string]*globalInfo
typedefs map[string]*typedefInfo
@ -103,6 +104,7 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []er
File: f,
Package: p,
filename: filename,
constants: map[string]*ast.BasicLit{},
functions: map[string]*functionInfo{},
globals: map[string]*globalInfo{},
typedefs: map[string]*typedefInfo{},
@ -163,6 +165,9 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []er
// Declare stub function pointer values found by libclang.
info.addFuncPtrDecls()
// Declare globals found by libclang.
info.addConstDecls()
// Declare globals found by libclang.
info.addVarDecls()
@ -287,6 +292,49 @@ func (info *fileInfo) addFuncPtrDecls() {
info.Decls = append(info.Decls, gen)
}
// addConstDecls declares external C constants in the Go source.
// It adds code like the following to the AST:
//
// const (
// C.CONST_INT = 5
// C.CONST_FLOAT = 5.8
// // ...
// )
func (info *fileInfo) addConstDecls() {
if len(info.constants) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: info.importCPos,
Tok: token.CONST,
Lparen: info.importCPos,
Rparen: info.importCPos,
}
names := make([]string, 0, len(info.constants))
for name := range info.constants {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
constVal := info.constants[name]
obj := &ast.Object{
Kind: ast.Con,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{
NamePos: info.importCPos,
Name: "C." + name,
Obj: obj,
}},
Values: []ast.Expr{constVal},
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
}
info.Decls = append(info.Decls, gen)
}
// addVarDecls declares external C globals in the Go source.
// It adds code like the following to the AST:
//
@ -313,7 +361,7 @@ func (info *fileInfo) addVarDecls() {
for _, name := range names {
global := info.globals[name]
obj := &ast.Object{
Kind: ast.Typ,
Kind: ast.Var,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{

83
loader/libclang.go

@ -47,6 +47,7 @@ CXType tinygo_clang_getCursorResultType(GoCXCursor c);
int tinygo_clang_Cursor_getNumArguments(GoCXCursor c);
GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
@ -101,7 +102,7 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilenam
filenameC,
(**C.char)(cmdargsC), C.int(len(cflags)), // command line args
&unsavedFile, 1, // unsaved files
C.CXTranslationUnit_None,
C.CXTranslationUnit_DetailedPreprocessingRecord,
&unit)
if errCode != 0 {
panic("loader: failed to parse source with libclang")
@ -220,6 +221,86 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
info.globals[name] = &globalInfo{
typeExpr: info.makeASTType(cursorType, pos),
}
case C.CXCursor_MacroDefinition:
name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := info.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
sourceRange := C.tinygo_clang_getCursorExtent(c)
start := C.clang_getRangeStart(sourceRange)
end := C.clang_getRangeEnd(sourceRange)
var file, endFile C.CXFile
var startOffset, endOffset C.unsigned
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
if file == nil {
panic("could not find file where macro is defined")
}
C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset)
if file != endFile {
panic("expected start and end location of a #define to be in the same file")
}
if startOffset > endOffset {
panic("startOffset > endOffset")
}
// read file contents and extract the relevant byte range
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size)
if endOffset >= C.uint(size) {
panic("endOffset lies after end of file")
}
source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset])
if !strings.HasPrefix(source, name) {
panic(fmt.Sprintf("expected #define value to start with %#v, got %#v", name, source))
}
value := strings.TrimSpace(source[len(name):])
for len(value) != 0 && value[0] == '(' && value[len(value)-1] == ')' {
value = strings.TrimSpace(value[1 : len(value)-1])
}
if len(value) == 0 {
// Pretend it doesn't exist at all.
return C.CXChildVisit_Continue
}
// For information about integer literals:
// https://en.cppreference.com/w/cpp/language/integer_literal
if value[0] == '"' {
// string constant
info.constants[name] = &ast.BasicLit{pos, token.STRING, value}
return C.CXChildVisit_Continue
}
if value[0] == '\'' {
// char constant
info.constants[name] = &ast.BasicLit{pos, token.CHAR, value}
return C.CXChildVisit_Continue
}
// assume it's a number (int or float)
value = strings.Replace(value, "'", "", -1) // remove ' chars
value = strings.TrimRight(value, "lu") // remove llu suffixes etc.
// find the first non-number
nonnum := byte(0)
for i := 0; i < len(value); i++ {
if value[i] < '0' || value[i] > '9' {
nonnum = value[i]
break
}
}
// determine number type based on the first non-number
switch nonnum {
case 0:
// no non-number found, must be an integer
info.constants[name] = &ast.BasicLit{pos, token.INT, value}
case 'x', 'X':
// hex integer constant
// TODO: may also be a floating point number per C++17.
info.constants[name] = &ast.BasicLit{pos, token.INT, value}
case '.', 'e':
// float constant
value = strings.TrimRight(value, "fFlL")
info.constants[name] = &ast.BasicLit{pos, token.FLOAT, value}
default:
// unknown type, ignore
}
}
return C.CXChildVisit_Continue
}

4
loader/libclang_stubs.c

@ -49,6 +49,10 @@ CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
return clang_getCursorLocation(c);
}
CXSourceRange tinygo_clang_getCursorExtent(CXCursor c) {
return clang_getCursorExtent(c);
}
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) {
return clang_Cursor_getTranslationUnit(c);
}

4
testdata/cgo/main.go

@ -18,6 +18,10 @@ func main() {
var y C.longlong = -(1 << 40)
println("longlong:", y)
println("global:", C.global)
println("defined ints:", C.CONST_INT, C.CONST_INT2)
println("defined floats:", C.CONST_FLOAT, C.CONST_FLOAT2)
println("defined string:", C.CONST_STRING)
println("defined char:", C.CONST_CHAR)
var ptr C.intPointer
var n C.int = 15
ptr = C.intPointer(&n)

7
testdata/cgo/main.h

@ -10,6 +10,13 @@ int doCallback(int a, int b, binop_t cb);
typedef int * intPointer;
void store(int value, int *ptr);
# define CONST_INT 5
# define CONST_INT2 5llu
# define CONST_FLOAT 5.8
# define CONST_FLOAT2 5.8f
# define CONST_CHAR 'c'
# define CONST_STRING "defined string"
// this signature should not be included by CGo
void unusedFunction2(int x, __builtin_va_list args);

4
testdata/cgo/out.txt

@ -4,6 +4,10 @@ myint: 3 5
myint size: 2
longlong: -1099511627776
global: 3
defined ints: 5 5
defined floats: +5.800000e+000 +5.800000e+000
defined string: defined string
defined char: 99
15: 15
25: 25
callback 1: 50

Loading…
Cancel
Save