diff --git a/cgo/cgo.go b/cgo/cgo.go index d31fe57a..3d02defc 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -68,8 +68,20 @@ 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.Expr + pos token.Pos + bitfields []bitfieldInfo +} + +// bitfieldInfo contains information about a single bitfield in a struct. It +// keeps information about the start, end, and the special (renamed) base field +// of this bitfield. +type bitfieldInfo struct { + field *ast.Field + name string pos token.Pos + startBit int64 + endBit int64 // may be 0 meaning "until the end of the field" } // enumInfo contains information about an enum in the C. @@ -581,10 +593,299 @@ func (p *cgoPackage) addElaboratedTypes() { } obj.Decl = typeSpec gen.Specs = append(gen.Specs, typeSpec) + // If this struct has bitfields, create getters for them. + for _, bitfield := range typ.bitfields { + p.createBitfieldGetter(bitfield, typeName) + p.createBitfieldSetter(bitfield, typeName) + } } p.generated.Decls = append(p.generated.Decls, gen) } +// createBitfieldGetter creates a bitfield getter function like the following: +// +// func (s *C.struct_foo) bitfield_b() byte { +// return (s.__bitfield_1 >> 5) & 0x1 +// } +func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string) { + // The value to return from the getter. + // Not complete: this is just an expression to get the complete field. + var result ast.Expr = &ast.SelectorExpr{ + X: &ast.Ident{ + NamePos: bitfield.pos, + Name: "s", + Obj: nil, + }, + Sel: &ast.Ident{ + NamePos: bitfield.pos, + Name: bitfield.field.Names[0].Name, + }, + } + if bitfield.startBit != 0 { + // Shift to the right by .startBit so that fields that come before are + // shifted off. + result = &ast.BinaryExpr{ + X: result, + OpPos: bitfield.pos, + Op: token.SHR, + Y: &ast.BasicLit{ + ValuePos: bitfield.pos, + Kind: token.INT, + Value: strconv.FormatInt(bitfield.startBit, 10), + }, + } + } + if bitfield.endBit != 0 { + // Mask off the high bits so that fields that come after this field are + // masked off. + and := (uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1 + result = &ast.BinaryExpr{ + X: result, + OpPos: bitfield.pos, + Op: token.AND, + Y: &ast.BasicLit{ + ValuePos: bitfield.pos, + Kind: token.INT, + Value: "0x" + strconv.FormatUint(and, 16), + }, + } + } + + // Create the getter function. + getter := &ast.FuncDecl{ + Recv: &ast.FieldList{ + Opening: bitfield.pos, + List: []*ast.Field{ + &ast.Field{ + Names: []*ast.Ident{ + &ast.Ident{ + NamePos: bitfield.pos, + Name: "s", + Obj: &ast.Object{ + Kind: ast.Var, + Name: "s", + Decl: nil, + }, + }, + }, + Type: &ast.StarExpr{ + Star: bitfield.pos, + X: &ast.Ident{ + NamePos: bitfield.pos, + Name: typeName, + Obj: nil, + }, + }, + }, + }, + Closing: bitfield.pos, + }, + Name: &ast.Ident{ + NamePos: bitfield.pos, + Name: "bitfield_" + bitfield.name, + }, + Type: &ast.FuncType{ + Func: bitfield.pos, + Params: &ast.FieldList{ + Opening: bitfield.pos, + Closing: bitfield.pos, + }, + Results: &ast.FieldList{ + List: []*ast.Field{ + &ast.Field{ + Type: bitfield.field.Type, + }, + }, + }, + }, + Body: &ast.BlockStmt{ + Lbrace: bitfield.pos, + List: []ast.Stmt{ + &ast.ReturnStmt{ + Return: bitfield.pos, + Results: []ast.Expr{ + result, + }, + }, + }, + Rbrace: bitfield.pos, + }, + } + p.generated.Decls = append(p.generated.Decls, getter) +} + +// createBitfieldSetter creates a bitfield setter function like the following: +// +// func (s *C.struct_foo) set_bitfield_b(value byte) { +// s.__bitfield_1 = s.__bitfield_1 ^ 0x60 | ((value & 1) << 5) +// } +// +// Or the following: +// +// func (s *C.struct_foo) set_bitfield_c(value byte) { +// s.__bitfield_1 = s.__bitfield_1 & 0x3f | (value << 6) +// } +func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string) { + // The full field with all bitfields. + var field ast.Expr = &ast.SelectorExpr{ + X: &ast.Ident{ + NamePos: bitfield.pos, + Name: "s", + Obj: nil, + }, + Sel: &ast.Ident{ + NamePos: bitfield.pos, + Name: bitfield.field.Names[0].Name, + }, + } + // The value to insert into the field. + var valueToInsert ast.Expr = &ast.Ident{ + NamePos: bitfield.pos, + Name: "value", + } + + if bitfield.endBit != 0 { + // Make sure the value is in range with a mask. + valueToInsert = &ast.BinaryExpr{ + X: valueToInsert, + OpPos: bitfield.pos, + Op: token.AND, + Y: &ast.BasicLit{ + ValuePos: bitfield.pos, + Kind: token.INT, + Value: "0x" + strconv.FormatUint((uint64(1)< 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 @@ -632,22 +647,70 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { //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 { - fieldList *ast.FieldList - pkg *cgoPackage + fieldList *ast.FieldList + pkg *cgoPackage + inBitfield *bool + bitfieldNum *int + bitfieldList *[]bitfieldInfo }) fieldList := passed.fieldList p := passed.pkg + inBitfield := passed.inBitfield + bitfieldNum := passed.bitfieldNum + bitfieldList := passed.bitfieldList if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl { panic("expected field inside cursor") } name := getString(C.tinygo_clang_getCursorSpelling(c)) + if name == "" { + // Assume this is a bitfield of 0 bits. + // Warning: this is not necessarily true! + return C.CXChildVisit_Continue + } typ := C.tinygo_clang_getCursorType(c) + pos := p.getCursorPosition(c) field := &ast.Field{ Type: p.makeASTType(typ, p.getCursorPosition(c)), } + offsetof := int64(C.clang_Type_getOffsetOf(C.tinygo_clang_getCursorType(parent), C.CString(name))) + alignOf := int64(C.clang_Type_getAlignOf(typ) * 8) + bitfieldOffset := offsetof % alignOf + if bitfieldOffset != 0 { + if C.tinygo_clang_Cursor_isBitField(c) != 1 { + panic("expected a bitfield") + } + if !*inBitfield { + *bitfieldNum++ + } + bitfieldName := "__bitfield_" + strconv.Itoa(*bitfieldNum) + prevField := fieldList.List[len(fieldList.List)-1] + if !*inBitfield { + // The previous element also was a bitfield, but wasn't noticed + // then. Add it now. + *inBitfield = true + *bitfieldList = append(*bitfieldList, bitfieldInfo{ + field: prevField, + name: prevField.Names[0].Name, + startBit: 0, + pos: prevField.Names[0].NamePos, + }) + prevField.Names[0].Name = bitfieldName + prevField.Names[0].Obj.Name = bitfieldName + } + prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1] + prevBitfield.endBit = bitfieldOffset + *bitfieldList = append(*bitfieldList, bitfieldInfo{ + field: prevField, + name: name, + startBit: bitfieldOffset, + pos: pos, + }) + return C.CXChildVisit_Continue + } + *inBitfield = false field.Names = []*ast.Ident{ &ast.Ident{ - NamePos: p.getCursorPosition(c), + NamePos: pos, Name: name, Obj: &ast.Object{ Kind: ast.Var, diff --git a/cgo/libclang_stubs.c b/cgo/libclang_stubs.c index 0ccfd4ab..ba3e6af9 100644 --- a/cgo/libclang_stubs.c +++ b/cgo/libclang_stubs.c @@ -64,3 +64,7 @@ long long tinygo_clang_getEnumConstantDeclValue(CXCursor c) { CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) { return clang_getEnumDeclIntegerType(c); } + +unsigned tinygo_clang_Cursor_isBitField(CXCursor c) { + return clang_Cursor_isBitField(c); +} \ No newline at end of file diff --git a/testdata/cgo/main.c b/testdata/cgo/main.c index 9d1e3c6a..97b49155 100644 --- a/testdata/cgo/main.c +++ b/testdata/cgo/main.c @@ -18,6 +18,7 @@ short globalArray[3] = {5, 6, 7}; joined_t globalUnion; int globalUnionSize = sizeof(globalUnion); option_t globalOption = optionG; +bitfield_t globalBitfield = {244, 15, 1, 2, 47, 5}; int fortytwo() { return 42; diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index 7837a779..1668b51a 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -64,6 +64,11 @@ func main() { println("union global data:", C.globalUnion.data[0], C.globalUnion.data[1], C.globalUnion.data[2]) println("union field:", printUnion(C.globalUnion).f) var _ C.union_joined = C.globalUnion + printBitfield(&C.globalBitfield) + C.globalBitfield.set_bitfield_a(7) + C.globalBitfield.set_bitfield_b(0) + C.globalBitfield.set_bitfield_c(0xff) + printBitfield(&C.globalBitfield) // elaborated type p := C.struct_point{x: 3, y: 5} @@ -108,3 +113,11 @@ func printUnion(union C.joined_t) C.joined_t { func mul(a, b C.int) C.int { return a * b } + +func printBitfield(bitfield *C.bitfield_t) { + println("bitfield a:", bitfield.bitfield_a()) + println("bitfield b:", bitfield.bitfield_b()) + println("bitfield c:", bitfield.bitfield_c()) + println("bitfield d:", bitfield.d) + println("bitfield e:", bitfield.e) +} diff --git a/testdata/cgo/main.h b/testdata/cgo/main.h index 3e97f1ad..23a6f73d 100644 --- a/testdata/cgo/main.h +++ b/testdata/cgo/main.h @@ -65,6 +65,17 @@ typedef enum { option3A = 21, } option3_t; +typedef struct { + unsigned char start; + unsigned char a : 5; + unsigned char b : 1; + unsigned char c : 2; + unsigned char :0; // new field + unsigned char d : 6; + unsigned char e : 3; + // Note that C++ allows bitfields bigger than the underlying type. +} bitfield_t; + // test globals and datatypes extern int global; extern int unusedGlobal; @@ -85,6 +96,7 @@ extern short globalArray[3]; extern joined_t globalUnion; extern int globalUnionSize; extern option_t globalOption; +extern bitfield_t globalBitfield; // test duplicate definitions int add(int a, int b); diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index e06d79ac..73cb2b99 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -31,6 +31,16 @@ union local data: 5 8 1 union s: true union f: +6.280000e+000 union field: +6.280000e+000 +bitfield a: 15 +bitfield b: 1 +bitfield c: 2 +bitfield d: 47 +bitfield e: 5 +bitfield a: 7 +bitfield b: 0 +bitfield c: 3 +bitfield d: 47 +bitfield e: 5 struct: 3 5 n in chain: 3 n in chain: 6