From 0ce4d907797d1bdcd8b99606d2e0139e06eaf31a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 2 Jun 2019 16:22:27 +0200 Subject: [PATCH] cgo: add support for anonymous structs --- cgo/cgo.go | 3 +- cgo/libclang.go | 186 ++++++++++++++++++++++++------------------- testdata/cgo/main.go | 6 +- testdata/cgo/main.h | 13 ++- 4 files changed, 125 insertions(+), 83 deletions(-) diff --git a/cgo/cgo.go b/cgo/cgo.go index 3d02defc..856149c2 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -36,6 +36,7 @@ type cgoPackage struct { typedefs map[string]*typedefInfo elaboratedTypes map[string]*elaboratedTypeInfo enums map[string]enumInfo + anonStructNum int } // constantInfo stores some information about a CGo constant found by libclang @@ -68,7 +69,7 @@ type typedefInfo struct { // elaboratedTypeInfo contains some information about an elaborated type // (struct, union) found in the C AST. type elaboratedTypeInfo struct { - typeExpr ast.Expr + typeExpr *ast.StructType pos token.Pos bitfields []bitfieldInfo } diff --git a/cgo/libclang.go b/cgo/libclang.go index b2104296..5bc3a1b1 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -513,92 +513,48 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { case C.CXType_Record: cursor := C.tinygo_clang_getTypeDeclaration(typ) name := getString(C.tinygo_clang_getCursorSpelling(cursor)) - var cgoName string - switch C.tinygo_clang_getCursorKind(cursor) { - case C.CXCursor_StructDecl: - cgoName = "struct_" + name - case C.CXCursor_UnionDecl: - cgoName = "union_" + name - default: - panic("unknown record declaration") - } - if _, ok := p.elaboratedTypes[cgoName]; !ok { - p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion) - fieldList := &ast.FieldList{ - Opening: pos, - Closing: pos, - } - var bitfieldList []bitfieldInfo - inBitfield := false - bitfieldNum := 0 - ref := storedRefs.Put(struct { - fieldList *ast.FieldList - pkg *cgoPackage - inBitfield *bool - bitfieldNum *int - bitfieldList *[]bitfieldInfo - }{fieldList, p, &inBitfield, &bitfieldNum, &bitfieldList}) - defer storedRefs.Remove(ref) - C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref)) - switch C.tinygo_clang_getCursorKind(cursor) { - case C.CXCursor_StructDecl: + if name == "" { + // Anonymous record, probably inside a typedef. + typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos) + if bitfieldList != nil { + // This struct has bitfields, so we have to declare it as a + // named type (for bitfield getters/setters to work). + p.anonStructNum++ + cgoName := "struct_" + strconv.Itoa(p.anonStructNum) p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{ - typeExpr: &ast.StructType{ - Struct: pos, - Fields: fieldList, - }, + typeExpr: typeExpr, pos: pos, bitfields: bitfieldList, } - case C.CXCursor_UnionDecl: - if bitfieldList != nil { - // This is valid C... but please don't do this. - p.errors = append(p.errors, scanner.Error{ - Pos: p.fset.PositionFor(pos, true), - Msg: fmt.Sprintf("bitfield in a union is not supported"), - }) + return &ast.Ident{ + NamePos: pos, + Name: "C." + cgoName, } - if len(fieldList.List) > 1 { - // Insert a special field at the front (of zero width) as a - // marker that this is struct is actually a union. This is done - // by giving the field a name that cannot be expressed directly - // in Go. - // Other parts of the compiler look at the first element in a - // struct (of size > 2) to know whether this is a union. - // Note that we don't have to insert it for single-element - // unions as they're basically equivalent to a struct. - unionMarker := &ast.Field{ - Type: &ast.StructType{ - Struct: pos, - }, - } - unionMarker.Names = []*ast.Ident{ - &ast.Ident{ - NamePos: pos, - Name: "C union", - Obj: &ast.Object{ - Kind: ast.Var, - Name: "C union", - Decl: unionMarker, - }, - }, - } - fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...) - } - p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{ - typeExpr: &ast.StructType{ - Struct: pos, - Fields: fieldList, - }, - pos: pos, - } - default: - panic("unreachable") } - } - return &ast.Ident{ - NamePos: pos, - Name: "C." + cgoName, + return typeExpr + } else { + var cgoName string + switch C.tinygo_clang_getCursorKind(cursor) { + case C.CXCursor_StructDecl: + cgoName = "struct_" + name + case C.CXCursor_UnionDecl: + cgoName = "union_" + name + default: + panic("unknown record declaration") + } + if _, ok := p.elaboratedTypes[cgoName]; !ok { + p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion) + typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos) + p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{ + typeExpr: typeExpr, + pos: pos, + bitfields: bitfieldList, + } + } + return &ast.Ident{ + NamePos: pos, + Name: "C." + cgoName, + } } case C.CXType_Enum: cursor := C.tinygo_clang_getTypeDeclaration(typ) @@ -644,6 +600,76 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { } } +// makeASTRecordType parses a C record (struct or union) and translates it into +// a Go struct type. Unions are implemented by setting the first field to a +// zero-lengt "C union" field, which cannot be written in Go directly. +func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) (*ast.StructType, []bitfieldInfo) { + fieldList := &ast.FieldList{ + Opening: pos, + Closing: pos, + } + var bitfieldList []bitfieldInfo + inBitfield := false + bitfieldNum := 0 + ref := storedRefs.Put(struct { + fieldList *ast.FieldList + pkg *cgoPackage + inBitfield *bool + bitfieldNum *int + bitfieldList *[]bitfieldInfo + }{fieldList, p, &inBitfield, &bitfieldNum, &bitfieldList}) + defer storedRefs.Remove(ref) + C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref)) + switch C.tinygo_clang_getCursorKind(cursor) { + case C.CXCursor_StructDecl: + return &ast.StructType{ + Struct: pos, + Fields: fieldList, + }, bitfieldList + case C.CXCursor_UnionDecl: + if bitfieldList != nil { + // This is valid C... but please don't do this. + p.errors = append(p.errors, scanner.Error{ + Pos: p.fset.PositionFor(pos, true), + Msg: fmt.Sprintf("bitfield in a union is not supported"), + }) + } + if len(fieldList.List) > 1 { + // Insert a special field at the front (of zero width) as a + // marker that this is struct is actually a union. This is done + // by giving the field a name that cannot be expressed directly + // in Go. + // Other parts of the compiler look at the first element in a + // struct (of size > 2) to know whether this is a union. + // Note that we don't have to insert it for single-element + // unions as they're basically equivalent to a struct. + unionMarker := &ast.Field{ + Type: &ast.StructType{ + Struct: pos, + }, + } + unionMarker.Names = []*ast.Ident{ + &ast.Ident{ + NamePos: pos, + Name: "C union", + Obj: &ast.Object{ + Kind: ast.Var, + Name: "C union", + Decl: unionMarker, + }, + }, + } + fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...) + } + return &ast.StructType{ + Struct: pos, + Fields: fieldList, + }, bitfieldList + default: + panic("unknown record declaration") + } +} + //export tinygo_clang_struct_visitor func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct { diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index 1668b51a..2e5222c1 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -71,9 +71,13 @@ func main() { printBitfield(&C.globalBitfield) // elaborated type - p := C.struct_point{x: 3, y: 5} + p := C.struct_point2d{x: 3, y: 5} println("struct:", p.x, p.y) + // multiple anonymous structs (inside a typedef) + var _ C.point2d_t = C.point2d_t{x: 3, y: 5} + var _ C.point3d_t = C.point3d_t{x: 3, y: 5, z: 7} + // recursive types, test using a linked list list := &C.list_t{n: 3, next: &C.struct_list_t{n: 6, next: &C.list_t{n: 7, next: nil}}} for list != nil { diff --git a/testdata/cgo/main.h b/testdata/cgo/main.h index 23a6f73d..7f414a84 100644 --- a/testdata/cgo/main.h +++ b/testdata/cgo/main.h @@ -27,11 +27,22 @@ typedef struct collection { unsigned char c; } collection_t; -struct point { +struct point2d { int x; int y; }; +typedef struct { + int x; + int y; +} point2d_t; + +typedef struct { + int x; + int y; + int z; +} point3d_t; + // linked list typedef struct list_t { int n;