diff options
author | Ayke van Laethem <[email protected]> | 2019-11-07 15:34:07 +0100 |
---|---|---|
committer | Ron Evans <[email protected]> | 2019-11-07 21:39:29 +0100 |
commit | 6108ee6859ff6fa2154ee588709e1e0b68f6b2b5 (patch) | |
tree | 11e574189d9b9344adfe9dd49d449e21d8469bf2 /cgo | |
parent | 76c9f13e133e09e5633917aecabfe44137f5a4ba (diff) | |
download | tinygo-6108ee6859ff6fa2154ee588709e1e0b68f6b2b5.tar.gz tinygo-6108ee6859ff6fa2154ee588709e1e0b68f6b2b5.zip |
cgo: refactor union support
Instead of putting the magic in the AST, generate regular accessor
methods. This avoids a number of special cases in the compiler, and
avoids missing any of them.
The resulting union accesses are somewhat clunkier to use, but the
compiler implementation has far less coupling between the CGo
implementation and the IR generator.
Diffstat (limited to 'cgo')
-rw-r--r-- | cgo/cgo.go | 206 | ||||
-rw-r--r-- | cgo/libclang.go | 123 | ||||
-rw-r--r-- | cgo/testdata/types.go | 37 | ||||
-rw-r--r-- | cgo/testdata/types.out.go | 31 |
4 files changed, 317 insertions, 80 deletions
diff --git a/cgo/cgo.go b/cgo/cgo.go index e11b15ce3..f20ea0d58 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -12,6 +12,7 @@ package cgo // source file parsing. import ( + "fmt" "go/ast" "go/token" "sort" @@ -69,9 +70,11 @@ type typedefInfo struct { // elaboratedTypeInfo contains some information about an elaborated type // (struct, union) found in the C AST. type elaboratedTypeInfo struct { - typeExpr *ast.StructType - pos token.Pos - bitfields []bitfieldInfo + typeExpr *ast.StructType + pos token.Pos + bitfields []bitfieldInfo + unionSize int64 // union size in bytes, nonzero when union getters/setters should be created + unionAlign int64 // union alignment in bytes } // bitfieldInfo contains information about a single bitfield in a struct. It @@ -608,13 +611,31 @@ func (p *cgoPackage) addElaboratedTypes() { Kind: ast.Typ, Name: typeName, } + typeExpr := typ.typeExpr + if typ.unionSize != 0 { + // Create getters/setters. + for _, field := range typ.typeExpr.Fields.List { + if len(field.Names) != 1 { + p.addError(typ.pos, fmt.Sprintf("union must have field with a single name, it has %d names", len(field.Names))) + continue + } + p.createUnionAccessor(field, typeName) + } + // Convert to a single-field struct type. + typeExpr = p.makeUnionField(typ) + if typeExpr == nil { + // There was an error, that was already added to the list of + // errors. + continue + } + } typeSpec := &ast.TypeSpec{ Name: &ast.Ident{ NamePos: typ.pos, Name: typeName, Obj: obj, }, - Type: typ.typeExpr, + Type: typeExpr, } obj.Decl = typeSpec gen.Specs = append(gen.Specs, typeSpec) @@ -627,6 +648,183 @@ func (p *cgoPackage) addElaboratedTypes() { } } +// makeUnionField creates a new struct from an existing *elaboratedTypeInfo, +// that has just a single field that must be accessed through special accessors. +// It returns nil when there is an error. In case of an error, that error has +// already been added to the list of errors using p.addError. +func (p *cgoPackage) makeUnionField(typ *elaboratedTypeInfo) *ast.StructType { + unionFieldTypeName, ok := map[int64]string{ + 1: "uint8", + 2: "uint16", + 4: "uint32", + 8: "uint64", + }[typ.unionAlign] + if !ok { + p.addError(typ.typeExpr.Struct, fmt.Sprintf("expected union alignment to be one of 1, 2, 4, or 8, but got %d", typ.unionAlign)) + return nil + } + var unionFieldType ast.Expr = &ast.Ident{ + NamePos: token.NoPos, + Name: unionFieldTypeName, + } + if typ.unionSize != typ.unionAlign { + // A plain struct{uintX} isn't enough, we have to make a + // struct{[N]uintX} to make the union big enough. + if typ.unionSize/typ.unionAlign*typ.unionAlign != typ.unionSize { + p.addError(typ.typeExpr.Struct, fmt.Sprintf("union alignment (%d) must be a multiple of union alignment (%d)", typ.unionSize, typ.unionAlign)) + return nil + } + unionFieldType = &ast.ArrayType{ + Len: &ast.BasicLit{ + Kind: token.INT, + Value: strconv.FormatInt(typ.unionSize/typ.unionAlign, 10), + }, + Elt: unionFieldType, + } + } + return &ast.StructType{ + Struct: typ.typeExpr.Struct, + Fields: &ast.FieldList{ + Opening: typ.typeExpr.Fields.Opening, + List: []*ast.Field{&ast.Field{ + Names: []*ast.Ident{ + &ast.Ident{ + NamePos: typ.typeExpr.Fields.Opening, + Name: "$union", + }, + }, + Type: unionFieldType, + }}, + Closing: typ.typeExpr.Fields.Closing, + }, + } +} + +// createUnionAccessor creates a function that returns a typed pointer to a +// union field for each field in a union. For example: +// +// func (union *C.union_1) unionfield_d() *float64 { +// return (*float64)(unsafe.Pointer(&union.$union)) +// } +// +// Where C.union_1 is defined as: +// +// type C.union_1 struct{ +// $union uint64 +// } +// +// The returned pointer can be used to get or set the field, or get the pointer +// to a subfield. +func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) { + if len(field.Names) != 1 { + panic("number of names in union field must be exactly 1") + } + fieldName := field.Names[0] + pos := fieldName.NamePos + + // The method receiver. + receiver := &ast.SelectorExpr{ + X: &ast.Ident{ + NamePos: pos, + Name: "union", + Obj: nil, + }, + Sel: &ast.Ident{ + NamePos: pos, + Name: "$union", + }, + } + + // Get the address of the $union field. + receiverPtr := &ast.UnaryExpr{ + Op: token.AND, + X: receiver, + } + + // Cast to unsafe.Pointer. + sourcePointer := &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{Name: "unsafe"}, + Sel: &ast.Ident{Name: "Pointer"}, + }, + Args: []ast.Expr{receiverPtr}, + } + + // Cast to the target pointer type. + targetPointer := &ast.CallExpr{ + Lparen: pos, + Fun: &ast.ParenExpr{ + Lparen: pos, + X: &ast.StarExpr{ + X: field.Type, + }, + Rparen: pos, + }, + Args: []ast.Expr{sourcePointer}, + Rparen: pos, + } + + // Create the accessor function. + accessor := &ast.FuncDecl{ + Recv: &ast.FieldList{ + Opening: pos, + List: []*ast.Field{ + &ast.Field{ + Names: []*ast.Ident{ + &ast.Ident{ + NamePos: pos, + Name: "union", + }, + }, + Type: &ast.StarExpr{ + Star: pos, + X: &ast.Ident{ + NamePos: pos, + Name: typeName, + Obj: nil, + }, + }, + }, + }, + Closing: pos, + }, + Name: &ast.Ident{ + NamePos: pos, + Name: "unionfield_" + fieldName.Name, + }, + Type: &ast.FuncType{ + Func: pos, + Params: &ast.FieldList{ + Opening: pos, + Closing: pos, + }, + Results: &ast.FieldList{ + List: []*ast.Field{ + &ast.Field{ + Type: &ast.StarExpr{ + Star: pos, + X: field.Type, + }, + }, + }, + }, + }, + Body: &ast.BlockStmt{ + Lbrace: pos, + List: []ast.Stmt{ + &ast.ReturnStmt{ + Return: pos, + Results: []ast.Expr{ + targetPointer, + }, + }, + }, + Rbrace: pos, + }, + } + p.generated.Decls = append(p.generated.Decls, accessor) +} + // createBitfieldGetter creates a bitfield getter function like the following: // // func (s *C.struct_foo) bitfield_b() byte { diff --git a/cgo/libclang.go b/cgo/libclang.go index 514e5326f..d495384e7 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -506,44 +506,37 @@ 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 cgoRecordPrefix string + switch C.tinygo_clang_getCursorKind(cursor) { + case C.CXCursor_StructDecl: + cgoRecordPrefix = "struct_" + case C.CXCursor_UnionDecl: + cgoRecordPrefix = "union_" + default: + // makeASTRecordType will create an appropriate error. + cgoRecordPrefix = "record_" + } 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). + typeInfo := p.makeASTRecordType(cursor, pos) + if typeInfo.bitfields != nil || typeInfo.unionSize != 0 { + // This record is a union or is a struct with bitfields, so we + // have to declare it as a named type (for getters/setters to + // work). p.anonStructNum++ - cgoName := "struct_" + strconv.Itoa(p.anonStructNum) - p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{ - typeExpr: typeExpr, - pos: pos, - bitfields: bitfieldList, - } + cgoName := cgoRecordPrefix + strconv.Itoa(p.anonStructNum) + p.elaboratedTypes[cgoName] = typeInfo return &ast.Ident{ NamePos: pos, Name: "C." + cgoName, } } - return typeExpr + return typeInfo.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: - // makeASTRecordType will create an appropriate error. - cgoName = "record_" + name - } + cgoName := cgoRecordPrefix + name 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, - } + p.elaboratedTypes[cgoName] = p.makeASTRecordType(cursor, pos) } return &ast.Ident{ NamePos: pos, @@ -591,9 +584,8 @@ 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) { +// a Go struct type. +func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elaboratedTypeInfo { fieldList := &ast.FieldList{ Opening: pos, Closing: pos, @@ -613,53 +605,50 @@ func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) (*ast renameFieldKeywords(fieldList) switch C.tinygo_clang_getCursorKind(cursor) { case C.CXCursor_StructDecl: - return &ast.StructType{ - Struct: pos, - Fields: fieldList, - }, bitfieldList + return &elaboratedTypeInfo{ + typeExpr: &ast.StructType{ + Struct: pos, + Fields: fieldList, + }, + pos: pos, + bitfields: bitfieldList, + } case C.CXCursor_UnionDecl: + typeInfo := &elaboratedTypeInfo{ + typeExpr: &ast.StructType{ + Struct: pos, + Fields: fieldList, + }, + pos: pos, + bitfields: bitfieldList, + } + if len(fieldList.List) <= 1 { + // Useless union, treat it as a regular struct. + return typeInfo + } if bitfieldList != nil { // This is valid C... but please don't do this. p.addError(pos, "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...) + typ := C.tinygo_clang_getCursorType(cursor) + alignInBytes := int64(C.clang_Type_getAlignOf(typ)) + sizeInBytes := int64(C.clang_Type_getSizeOf(typ)) + if sizeInBytes == 0 { + p.addError(pos, "zero-length union is not supported") } - return &ast.StructType{ - Struct: pos, - Fields: fieldList, - }, bitfieldList + typeInfo.unionSize = sizeInBytes + typeInfo.unionAlign = alignInBytes + return typeInfo default: cursorKind := C.tinygo_clang_getCursorKind(cursor) cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind)) p.addError(pos, fmt.Sprintf("expected StructDecl or UnionDecl, not %s", cursorKindSpelling)) - return &ast.StructType{ - Struct: pos, - }, nil + return &elaboratedTypeInfo{ + typeExpr: &ast.StructType{ + Struct: pos, + }, + pos: pos, + } } } diff --git a/cgo/testdata/types.go b/cgo/testdata/types.go index 08d8bb0e6..0c8668cbe 100644 --- a/cgo/testdata/types.go +++ b/cgo/testdata/types.go @@ -27,6 +27,25 @@ struct type2 { int _type; }; +// Unions. +typedef union { + // Union should be treated as a struct. + int i; +} union1_t; +typedef union { + // Union must contain a single field and have special getters/setters. + int i; + double d; + short s; +} union3_t; +typedef union union2d { + int i; + double d[2]; +} union2d_t; +typedef union { + unsigned char arr[10]; +} unionarrary_t; + // Enums. These define constant numbers. All these constants must be given the // correct number. typedef enum option { @@ -85,6 +104,12 @@ var ( _ C.struct_type1 _ C.struct_type2 + // Unions. + _ C.union1_t + _ C.union3_t + _ C.union2d_t + _ C.unionarrary_t + // Enums (anonymous and named). _ C.option_t _ C.enum_option @@ -98,7 +123,7 @@ var ( ) // Test bitfield accesses. -func foo() { +func accessBitfields() { var x C.bitfield_t x.start = 3 x.set_bitfield_a(4) @@ -108,3 +133,13 @@ func foo() { x.e = 5 var _ C.uchar = x.bitfield_a() } + +// Test union accesses. +func accessUnion() { + var union1 C.union1_t + union1.i = 5 + + var union2d C.union2d_t + var _ *C.int = union2d.unionfield_i() + var _ *[2]float64 = union2d.unionfield_d() +} diff --git a/cgo/testdata/types.out.go b/cgo/testdata/types.out.go index 0f632b88f..d0fcc5a22 100644 --- a/cgo/testdata/types.out.go +++ b/cgo/testdata/types.out.go @@ -34,7 +34,7 @@ type C.uint uint32 type C.ulong uint32 type C.ulonglong uint64 type C.ushort uint16 -type C.bitfield_t = C.struct_1 +type C.bitfield_t = C.struct_2 type C.myIntArray = [10]C.int type C.myint = C.int type C.option2_t = C.uint @@ -49,21 +49,25 @@ type C.types_t = struct { d float64 ptr *C.int } +type C.union1_t = struct{ i C.int } +type C.union2d_t = C.union_union2d +type C.union3_t = C.union_1 +type C.unionarrary_t = struct{ arr [10]C.uchar } -func (s *C.struct_1) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f } -func (s *C.struct_1) set_bitfield_a(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 } -func (s *C.struct_1) bitfield_b() C.uchar { +func (s *C.struct_2) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f } +func (s *C.struct_2) set_bitfield_a(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 } +func (s *C.struct_2) bitfield_b() C.uchar { return s.__bitfield_1 >> 5 & 0x1 } -func (s *C.struct_1) set_bitfield_b(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 } -func (s *C.struct_1) bitfield_c() C.uchar { +func (s *C.struct_2) set_bitfield_b(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 } +func (s *C.struct_2) bitfield_c() C.uchar { return s.__bitfield_1 >> 6 } -func (s *C.struct_1) set_bitfield_c(value C.uchar, +func (s *C.struct_2) set_bitfield_c(value C.uchar, ) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 } -type C.struct_1 struct { +type C.struct_2 struct { start C.uchar __bitfield_1 C.uchar @@ -81,5 +85,16 @@ type C.struct_type1 struct { ___type C.int } type C.struct_type2 struct{ _type C.int } + +func (union *C.union_1) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) } +func (union *C.union_1) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) } +func (union *C.union_1) unionfield_s() *C.short { return (*C.short)(unsafe.Pointer(&union.$union)) } + +type C.union_1 struct{ $union uint64 } + +func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) } +func (union *C.union_union2d) unionfield_d() *[2]float64 { return (*[2]float64)(unsafe.Pointer(&union.$union)) } + +type C.union_union2d struct{ $union [2]uint64 } type C.enum_option C.int type C.enum_unused C.uint |