From 99587fe0731e6a80b2485eaca5657e6bd43cb9be Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 28 Apr 2019 14:10:03 +0200 Subject: [PATCH] cgo: add support for #define constants These are converted to Go constants where possible. --- loader/cgo.go | 50 ++++++++++++++++++++++++- loader/libclang.go | 83 ++++++++++++++++++++++++++++++++++++++++- loader/libclang_stubs.c | 4 ++ testdata/cgo/main.go | 4 ++ testdata/cgo/main.h | 7 ++++ testdata/cgo/out.txt | 4 ++ 6 files changed, 150 insertions(+), 2 deletions(-) diff --git a/loader/cgo.go b/loader/cgo.go index 342a2af1..bcd26b9f 100644 --- a/loader/cgo.go +++ b/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{ diff --git a/loader/libclang.go b/loader/libclang.go index 469b0ebb..3d643cf0 100644 --- a/loader/libclang.go +++ b/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 } diff --git a/loader/libclang_stubs.c b/loader/libclang_stubs.c index 3524644a..c282540a 100644 --- a/loader/libclang_stubs.c +++ b/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); } diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index 3e444f4b..7a434855 100644 --- a/testdata/cgo/main.go +++ b/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) diff --git a/testdata/cgo/main.h b/testdata/cgo/main.h index 0659ab0c..9b325efd 100644 --- a/testdata/cgo/main.h +++ b/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); diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index ee34f0bd..70c093b1 100644 --- a/testdata/cgo/out.txt +++ b/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