From e8c1b5ab6e6b5789deb1cf1bf0f16e920710d1c2 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 29 Nov 2018 17:30:46 +0100 Subject: [PATCH] cgo: add support for C typedefs --- loader/cgo.go | 126 ++++++++++++++++++++++++++++++++++++------- loader/libclang.go | 11 ++++ testdata/cgo/main.go | 5 +- testdata/cgo/out.txt | 1 + 4 files changed, 122 insertions(+), 21 deletions(-) diff --git a/loader/cgo.go b/loader/cgo.go index dc7d0824..c798a84c 100644 --- a/loader/cgo.go +++ b/loader/cgo.go @@ -6,6 +6,7 @@ package loader import ( "go/ast" "go/token" + "sort" "strconv" ) @@ -14,7 +15,7 @@ type fileInfo struct { *ast.File filename string functions []*functionInfo - types []string + typedefs []*typedefInfo importCPos token.Pos } @@ -32,11 +33,25 @@ type paramInfo struct { typeName string } -// aliasInfo encapsulates aliases between C and Go, like C.int32_t -> int32. See -// addTypeAliases. -type aliasInfo struct { - typeName string - goTypeName string +// typedefInfo contains information about a single typedef in C. +type typedefInfo struct { + newName string // newly defined type name + oldName string // old type name, may be something like "unsigned long long" + size int // size in bytes +} + +// cgoAliases list type aliases between Go and C, for types that are equivalent +// in both languages. See addTypeAliases. +var cgoAliases = map[string]string{ + "C.int8_t": "int8", + "C.int16_t": "int16", + "C.int32_t": "int32", + "C.int64_t": "int64", + "C.uint8_t": "uint8", + "C.uint16_t": "uint16", + "C.uint32_t": "uint32", + "C.uint64_t": "uint64", + "C.uintptr_t": "uintptr", } // processCgo extracts the `import "C"` statement from the AST, parses the @@ -93,6 +108,9 @@ func (p *Package) processCgo(filename string, f *ast.File) error { // Forward C types to Go types (like C.uint32_t -> uint32). info.addTypeAliases() + // Add type declarations for C types, declared using typeef in C. + info.addTypedefs() + // Patch the AST to use the declared types and functions. ast.Inspect(f, info.walker) @@ -168,38 +186,91 @@ func (info *fileInfo) addFuncDecls() { // // ... // ) func (info *fileInfo) addTypeAliases() { - aliases := []aliasInfo{ - aliasInfo{"C.int8_t", "int8"}, - aliasInfo{"C.int16_t", "int16"}, - aliasInfo{"C.int32_t", "int32"}, - aliasInfo{"C.int64_t", "int64"}, - aliasInfo{"C.uint8_t", "uint8"}, - aliasInfo{"C.uint16_t", "uint16"}, - aliasInfo{"C.uint32_t", "uint32"}, - aliasInfo{"C.uint64_t", "uint64"}, - aliasInfo{"C.uintptr_t", "uintptr"}, + aliasKeys := make([]string, 0, len(cgoAliases)) + for key := range cgoAliases { + aliasKeys = append(aliasKeys, key) } + sort.Strings(aliasKeys) gen := &ast.GenDecl{ TokPos: info.importCPos, Tok: token.TYPE, Lparen: info.importCPos, Rparen: info.importCPos, } - for _, alias := range aliases { + for _, typeName := range aliasKeys { + goTypeName := cgoAliases[typeName] obj := &ast.Object{ Kind: ast.Typ, - Name: alias.typeName, + Name: typeName, } typeSpec := &ast.TypeSpec{ Name: &ast.Ident{ NamePos: info.importCPos, - Name: alias.typeName, + Name: typeName, Obj: obj, }, Assign: info.importCPos, Type: &ast.Ident{ NamePos: info.importCPos, - Name: alias.goTypeName, + Name: goTypeName, + }, + } + obj.Decl = typeSpec + gen.Specs = append(gen.Specs, typeSpec) + } + info.Decls = append(info.Decls, gen) +} + +func (info *fileInfo) addTypedefs() { + gen := &ast.GenDecl{ + TokPos: info.importCPos, + Tok: token.TYPE, + } + for _, typedef := range info.typedefs { + newType := "C." + typedef.newName + oldType := "C." + typedef.oldName + if _, ok := cgoAliases[newType]; ok { + // This is a type that also exists in Go (defined in stdint.h). + continue + } + obj := &ast.Object{ + Kind: ast.Typ, + Name: newType, + } + switch oldType { + // TODO: plain char (may be signed or unsigned) + case "C.signed char", "C.short", "C.int", "C.long", "C.long long": + switch typedef.size { + case 1: + oldType = "int8" + case 2: + oldType = "int16" + case 4: + oldType = "int32" + case 8: + oldType = "int64" + } + case "C.unsigned char", "C.unsigned short", "C.unsigned int", "C.unsigned long", "C.unsigned long long": + switch typedef.size { + case 1: + oldType = "uint8" + case 2: + oldType = "uint16" + case 4: + oldType = "uint32" + case 8: + oldType = "uint64" + } + } + typeSpec := &ast.TypeSpec{ + Name: &ast.Ident{ + NamePos: info.importCPos, + Name: newType, + Obj: obj, + }, + Type: &ast.Ident{ + NamePos: info.importCPos, + Name: oldType, }, } obj.Decl = typeSpec @@ -229,6 +300,21 @@ func (info *fileInfo) walker(node ast.Node) bool { Name: "C." + 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: "C." + typ.Sel.Name, + } + } } return true } diff --git a/loader/libclang.go b/loader/libclang.go index ca1d8142..9f3b444d 100644 --- a/loader/libclang.go +++ b/loader/libclang.go @@ -93,6 +93,17 @@ func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.in resultType := C.clang_getCursorResultType(c) resultTypeName := getString(C.clang_getTypeSpelling(resultType)) fn.result = resultTypeName + case C.CXCursor_TypedefDecl: + typedefType := C.clang_getCursorType(c) + name := getString(C.clang_getTypedefName(typedefType)) + underlyingType := C.clang_getTypedefDeclUnderlyingType(c) + underlyingTypeName := getString(C.clang_getTypeSpelling(underlyingType)) + typeSize := C.clang_Type_getSizeOf(underlyingType) + info.typedefs = append(info.typedefs, &typedefInfo{ + newName: name, + oldName: underlyingTypeName, + size: int(typeSize), + }) } return C.CXChildVisit_Continue } diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index f2c5b533..cd927940 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -4,10 +4,13 @@ package main #include int32_t fortytwo(void); int32_t mul(int32_t a, int32_t b); +typedef int32_t myint; */ import "C" func main() { println("fortytwo:", C.fortytwo()) - println("mul:", C.mul(int32(3), 5)) + println("mul:", C.mul(C.int32_t(3), 5)) + var x C.myint = 3 + println("myint:", x, C.myint(5)) } diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index e89fc913..ce0b312a 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -1,2 +1,3 @@ fortytwo: 42 mul: 15 +myint: 3 5