aboutsummaryrefslogtreecommitdiffhomepage
path: root/cgo
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2019-05-29 23:26:57 +0200
committerRon Evans <[email protected]>2019-06-03 16:13:19 +0200
commit1d7cc2c2427c473b5ad07ab4e1ce144114e73988 (patch)
tree91227c51fe4aaa33b565e49106145b1605795e7f /cgo
parent23c8d158470e8df944a93b45fb204b06ee5685b8 (diff)
downloadtinygo-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.go303
-rw-r--r--cgo/libclang.go77
-rw-r--r--cgo/libclang_stubs.c4
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