diff options
author | Ayke van Laethem <[email protected]> | 2019-05-29 23:26:57 +0200 |
---|---|---|
committer | Ron Evans <[email protected]> | 2019-06-03 16:13:19 +0200 |
commit | 1d7cc2c2427c473b5ad07ab4e1ce144114e73988 (patch) | |
tree | 91227c51fe4aaa33b565e49106145b1605795e7f /cgo | |
parent | 23c8d158470e8df944a93b45fb204b06ee5685b8 (diff) | |
download | tinygo-1d7cc2c2427c473b5ad07ab4e1ce144114e73988.tar.gz tinygo-1d7cc2c2427c473b5ad07ab4e1ce144114e73988.zip |
cgo: add support for bitfields using generated getters and setters
Diffstat (limited to 'cgo')
-rw-r--r-- | cgo/cgo.go | 303 | ||||
-rw-r--r-- | cgo/libclang.go | 77 | ||||
-rw-r--r-- | cgo/libclang_stubs.c | 4 |
3 files changed, 376 insertions, 8 deletions
diff --git a/cgo/cgo.go b/cgo/cgo.go index d31fe57a4..3d02defc7 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)<<uint64(bitfield.endBit-bitfield.startBit))-1, 16), + }, + } + // Create a mask for the AND NOT operation. + mask := ((uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1) << uint64(bitfield.startBit) + // Zero the bits in the field that will soon be inserted. + field = &ast.BinaryExpr{ + X: field, + OpPos: bitfield.pos, + Op: token.AND_NOT, + Y: &ast.BasicLit{ + ValuePos: bitfield.pos, + Kind: token.INT, + Value: "0x" + strconv.FormatUint(mask, 16), + }, + } + } else { // bitfield.endBit == 0 + // We don't know exactly how many high bits should be zeroed. So we do + // something different: keep the low bits with a mask and OR the new + // value with it. + mask := (uint64(1) << uint64(bitfield.startBit)) - 1 + // Extract the lower bits. + field = &ast.BinaryExpr{ + X: field, + OpPos: bitfield.pos, + Op: token.AND, + Y: &ast.BasicLit{ + ValuePos: bitfield.pos, + Kind: token.INT, + Value: "0x" + strconv.FormatUint(mask, 16), + }, + } + } + + // Bitwise OR with the new value (after the new value has been shifted). + field = &ast.BinaryExpr{ + X: field, + OpPos: bitfield.pos, + Op: token.OR, + Y: &ast.BinaryExpr{ + X: valueToInsert, + OpPos: bitfield.pos, + Op: token.SHL, + Y: &ast.BasicLit{ + ValuePos: bitfield.pos, + Kind: token.INT, + Value: strconv.FormatInt(bitfield.startBit, 10), + }, + }, + } + + // Create the setter function. + setter := &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: "set_bitfield_" + bitfield.name, + }, + Type: &ast.FuncType{ + Func: bitfield.pos, + Params: &ast.FieldList{ + Opening: bitfield.pos, + List: []*ast.Field{ + &ast.Field{ + Names: []*ast.Ident{ + &ast.Ident{ + NamePos: bitfield.pos, + Name: "value", + Obj: nil, + }, + }, + Type: bitfield.field.Type, + }, + }, + Closing: bitfield.pos, + }, + }, + Body: &ast.BlockStmt{ + Lbrace: bitfield.pos, + List: []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []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, + }, + }, + }, + TokPos: bitfield.pos, + Tok: token.ASSIGN, + Rhs: []ast.Expr{ + field, + }, + }, + }, + Rbrace: bitfield.pos, + }, + } + p.generated.Decls = append(p.generated.Decls, setter) +} + // addEnumTypes adds C enums to the AST. For example, the following C code: // // enum option { diff --git a/cgo/libclang.go b/cgo/libclang.go index ce6df7667..b2104296b 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -51,6 +51,7 @@ CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c); CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c); long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c); CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c); +unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c); int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); @@ -527,10 +528,16 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { Opening: pos, Closing: pos, } + var bitfieldList []bitfieldInfo + inBitfield := false + bitfieldNum := 0 ref := storedRefs.Put(struct { - fieldList *ast.FieldList - pkg *cgoPackage - }{fieldList, p}) + 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) { @@ -540,9 +547,17 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { Struct: pos, Fields: fieldList, }, - pos: pos, + 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"), + }) + } 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 @@ -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 0ccfd4ab1..ba3e6af93 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 |