diff --git a/lib/compiler/aro_translate_c/ast.zig b/lib/compiler/aro_translate_c/ast.zig index 853fcb748cb5..69ad12315ca1 100644 --- a/lib/compiler/aro_translate_c/ast.zig +++ b/lib/compiler/aro_translate_c/ast.zig @@ -227,6 +227,9 @@ pub const Node = extern union { /// [1]type{val} ** count array_filler, + /// @import("std").zig.c_translation.EmulateBitfieldStruct(S) + helpers_emulate_bitfield_struct, + pub const last_no_payload_tag = Tag.@"break"; pub const no_payload_count = @intFromEnum(last_no_payload_tag) + 1; @@ -376,6 +379,7 @@ pub const Node = extern union { .shuffle => Payload.Shuffle, .builtin_extern => Payload.Extern, .macro_arithmetic => Payload.MacroArithmetic, + .helpers_emulate_bitfield_struct => Payload.EmulateBitfieldStruct, }; } @@ -698,6 +702,14 @@ pub const Payload = struct { }, }; + pub const EmulateBitfieldStruct = struct { + base: Payload, + data: struct { + definition: Node, + cfg: Node, + }, + }; + pub const StringSlice = struct { base: Payload, data: struct { @@ -917,6 +929,11 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "shuffleVectorIndex" }); return renderCall(c, import_node, &.{ payload.lhs, payload.rhs }); }, + .helpers_emulate_bitfield_struct => { + const payload = node.castTag(.helpers_emulate_bitfield_struct).?.data; + const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "EmulateBitfieldStruct" }); + return renderCall(c, import_node, &.{ payload.definition, payload.cfg }); + }, .vector => { const payload = node.castTag(.vector).?.data; return renderBuiltinCall(c, "@Vector", &.{ payload.lhs, payload.rhs }); @@ -2042,25 +2059,34 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { } _ = try c.addToken(.r_brace, "}"); - if (payload.len < 3) { - return c.addNode(.{ - .tag = .struct_init_dot_two_comma, + switch (payload.len) { + 0 => return c.addNode(.{ + .tag = .struct_init_dot_two, // the inits[0], inits[1] are both 0 .main_token = l_brace, .data = .{ .lhs = inits[0], .rhs = inits[1], }, - }); - } else { - const span = try c.listToSpan(inits); - return c.addNode(.{ - .tag = .struct_init_dot_comma, + }), + 1, 2 => return c.addNode(.{ + .tag = .struct_init_dot_two_comma, .main_token = l_brace, .data = .{ - .lhs = span.start, - .rhs = span.end, + .lhs = inits[0], + .rhs = inits[1], }, - }); + }), + else => { + const span = try c.listToSpan(inits); + return c.addNode(.{ + .tag = .struct_init_dot_comma, + .main_token = l_brace, + .data = .{ + .lhs = span.start, + .rhs = span.end, + }, + }); + }, } }, .container_init => { @@ -2387,6 +2413,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { .helpers_promoteIntLiteral, .helpers_shuffle_vector_index, .helpers_flexible_array_type, + .helpers_emulate_bitfield_struct, .std_mem_zeroinit, .integer_literal, .float_literal, diff --git a/lib/std/zig/c_translation.zig b/lib/std/zig/c_translation.zig index 39f7fa9a113f..a4c9fb35927c 100644 --- a/lib/std/zig/c_translation.zig +++ b/lib/std/zig/c_translation.zig @@ -670,3 +670,224 @@ test "Extended C ABI casting" { try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); // comptime_int -> c_longlong } } + +const BitfieldEmulation = struct { + /// By default the bits are allocated from LSB to MSB + /// (follows Zig's packed struct and most ABI). + /// Sets to true to allocate from MSB to LSB. + reverse_bits: bool, + /// Most of ABIs starts a new storage unit after a unnamed zero-bit width bit field. + /// Some ABIs ignores that, sets to false. + unnamed_void_boundary: bool, + /// Some ABIs allow a bitfield straddles on storage units. + /// Some ABIs, like MSVC, don't straddle, sets to false. + straddle: bool, + /// Also called 'steal padding'. + /// This option allows to steal the space from the previous padding for the next field. + collapse_padding: bool, + + fn fromTarget(target: std.Target) ?BitfieldEmulation { + return switch (target.cpu.arch) { + .x86_64, .x86 => .{ + .reverse_bits = false, + .unnamed_void_boundary = true, + .straddle = false, + .collapse_padding = switch (target.os.tag) { + .windows => false, + else => true, + }, + }, + .aarch64 => .{ + .reverse_bits = false, + .unnamed_void_boundary = true, + .straddle = false, + .collapse_padding = true, + }, + else => null, + }; + } + + fn merge(base: BitfieldEmulation, apply: anytype) BitfieldEmulation { + var copy = base; + for (std.meta.fieldNames(@This())) |name| { + if (@hasField(@TypeOf(apply), name)) { + @field(copy, name) = @field(apply, name); + } + } + return copy; + } +}; + +pub const Bitfield = struct { + /// The field name. + name: [:0]const u8, + /// The actual type of the field. For a bitfield, + /// It's the bit-sized unsigned int. + /// + /// Like in C `unsigned field0: 1` the type is `u1`. + type: type, + /// The backing integer for this field. + /// + /// Like in C `unsigned field0: 1`, the backing integer is `c_uint`. + backing_integer: ?type = null, + /// If the field is a pointer, it will be treated as `usize` and we avoid accessing the type. + /// + /// This helps to avoid the dependency loop problem. + is_pointer: bool = false, +}; + +fn makePaddingField(comptime bitsize: comptime_int, fieldNameCount: comptime_int) std.builtin.Type.StructField { + return makePaddingFieldWithName(bitsize, std.fmt.comptimePrint(" pad_{},+{}b", .{ fieldNameCount, bitsize })); +} + +fn makePaddingFieldWithName(comptime bitsize: comptime_int, fieldName: [:0]const u8) std.builtin.Type.StructField { + const T = @Type(.{ .int = .{ + .signedness = .unsigned, + .bits = bitsize, + } }); + return .{ + .alignment = 0, + .type = T, + .default_value = &std.mem.zeroes(T), + .name = fieldName, + .is_comptime = false, + }; +} + +fn isPaddingField(field: ?*const std.builtin.Type.StructField) bool { + return if (field) |f| std.mem.startsWith(u8, f.name, " pad_") else false; +} + +/// Translate a packed struct type to adapt the C bitfields on the target platform. +/// +/// If the target platform is unsupported, an opaque type will be returned. +/// +/// `fields` is the struct definition. +/// `modCfg` is the configuration accepted by `BitfieldEmulation.merge`. +/// +/// Be advised that, the bitfields have different representation range in different ABI. +/// This function assumes all bitfields are unsigned. +pub fn EmulateBitfieldStruct(comptime fields: []const Bitfield, comptime modCfg: anytype) type { + const cfg = if (BitfieldEmulation.fromTarget(builtin.target)) |cfg| + cfg.merge(modCfg) + else { + return opaque {}; + }; + + // TODO: implement reverse_bits + if (cfg.reverse_bits) @compileError("TODO: reverse_bit is not implemented"); + + comptime var finals: std.BoundedArray(std.builtin.Type.StructField, fields.len * 2) = .{}; + comptime var lastBackingInt: ?type = null; + comptime var leftBitWidth = 0; + comptime var padFieldCount = 0; + comptime var lastField: ?*std.builtin.Type.StructField = null; + // The used space in bits + comptime var offset = 0; + + for (fields, 0..fields.len) |field, _| { + if (comptime !field.is_pointer and @typeInfo(field.type) == .@"struct" and @typeInfo(field.type).@"struct".layout == .@"extern") { + return opaque {}; + } + if (field.backing_integer) |BackingInt| { + const requiredBits = @typeInfo(field.type).int.bits; + if (leftBitWidth < requiredBits) { + if (!cfg.straddle and (leftBitWidth > 0)) { + // add padding to use a new unit for the next field + finals.appendAssumeCapacity(makePaddingField(leftBitWidth, padFieldCount)); + lastField = &finals.slice()[finals.len - 1]; + padFieldCount += 1; + leftBitWidth = 0; + } + + if (offset % @alignOf(BackingInt) != 0) { + const padding = (@divTrunc(offset, @alignOf(BackingInt)) + 1) * @alignOf(BackingInt) - offset; + offset += padding; + + finals.appendAssumeCapacity(makePaddingField(padding * 8, padFieldCount)); + lastField = &finals.slice()[finals.len - 1]; + padFieldCount += 1; + } else if (isPaddingField(lastField) and cfg.collapse_padding) { + // Maybe we need to steal padding + const lfield = lastField.?; + const mlp = @divTrunc(@bitSizeOf(lfield.type), @alignOf(BackingInt) * 8); + if (mlp >= 1) { + const stolePadding = @alignOf(BackingInt) * mlp; + const nsize = @bitSizeOf(lfield.type) - (stolePadding * 8); + fields.set(fields.len - 1, makePaddingFieldWithName( + nsize, + std.fmt.comptimePrint("{s},-{}b", .{ lfield.name, stolePadding * 8 }), + )); + offset -= stolePadding; + } + } + + lastBackingInt = BackingInt; + leftBitWidth += @bitSizeOf(BackingInt); + } + + leftBitWidth -= @bitSizeOf(field.type); + finals.appendAssumeCapacity(.{ + .alignment = 0, + .default_value = &std.mem.zeroes(field.type), + .is_comptime = false, + .name = field.name, + .type = field.type, + }); + lastField = &finals.slice()[finals.len - 1]; + } else { + const LayoutAs = if (field.is_pointer) usize else field.type; + + if (leftBitWidth > 0) { + finals.appendAssumeCapacity(makePaddingField(leftBitWidth, padFieldCount)); + lastField = &finals.slice()[finals.len - 1]; + padFieldCount += 1; + offset += leftBitWidth; + } + leftBitWidth = 0; + lastBackingInt = null; + + if (offset % @alignOf(LayoutAs) != 0) { + const padding = (@divTrunc(offset, @alignOf(LayoutAs)) + 1) * @alignOf(LayoutAs) - offset; + offset += padding; + + finals.appendAssumeCapacity(makePaddingField(padding * 8, padFieldCount)); + lastField = &finals.slice()[finals.len - 1]; + padFieldCount += 1; + } else if (isPaddingField(lastField) and cfg.collapse_padding) { + // Maybe we need to steal padding + const lfield = lastField.?; + const mlp = @divTrunc(@bitSizeOf(LayoutAs), @alignOf(LayoutAs) * 8); + if (mlp >= 1) { + const stolePadding = @alignOf(LayoutAs) * mlp; + const nsize = @bitSizeOf(lfield.type) - (stolePadding * 8); + finals.set(finals.len - 1, makePaddingFieldWithName( + nsize, + std.fmt.comptimePrint("{s},-{}b", .{ lfield.name, stolePadding * 8 }), + )); + offset -= stolePadding; + } + } + + finals.appendAssumeCapacity(.{ + .alignment = 0, + .default_value = if (field.is_pointer) &@as(usize, 0) else &std.mem.zeroes(field.type), + .is_comptime = false, + .name = field.name, + .type = field.type, + }); + lastField = &finals.slice()[finals.len - 1]; + offset += @bitSizeOf(LayoutAs); + } + } + + return @Type(.{ + .@"struct" = .{ + .layout = .@"packed", + .decls = &.{}, + .fields = finals.constSlice(), + .is_tuple = false, + .backing_integer = null, + }, + }); +} diff --git a/src/clang.zig b/src/clang.zig index 04c142b3e338..74520c006393 100644 --- a/src/clang.zig +++ b/src/clang.zig @@ -487,6 +487,12 @@ pub const FieldDecl = opaque { pub const isBitField = ZigClangFieldDecl_isBitField; extern fn ZigClangFieldDecl_isBitField(*const FieldDecl) bool; + pub const isUnnamedBitField = ZigClangFieldDecl_isUnnamedBitField; + extern fn ZigClangFieldDecl_isUnnamedBitField(*const FieldDecl) bool; + + pub const getBitWidthValue = ZigClangFieldDecl_getBitWidthValue; + extern fn ZigClangFieldDecl_getBitWidthValue(*const FieldDecl, *const ASTContext) c_uint; + pub const getType = ZigClangFieldDecl_getType; extern fn ZigClangFieldDecl_getType(*const FieldDecl) QualType; diff --git a/src/translate_c.zig b/src/translate_c.zig index ef81a5dfdacf..db2d5ed8d096 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -923,6 +923,9 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD var functions = std.ArrayList(Node).init(c.gpa); defer functions.deinit(); + var backings = std.ArrayList(ast.Node).init(c.gpa); + defer backings.deinit(); + const flexible_field = flexibleArrayField(c, record_def); var unnamed_field_count: u32 = 0; var it = record_def.field_begin(); @@ -930,15 +933,15 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD const layout = record_def.getASTRecordLayout(c.clang_context); const record_alignment = layout.getAlignment(); + var bitfield_count: usize = 0; + while (it.neq(end_it)) : (it = it.next()) { const field_decl = it.deref(); const field_loc = field_decl.getLocation(); const field_qt = field_decl.getType(); if (field_decl.isBitField()) { - try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl.getCanonicalDecl()), {}); - try warn(c, scope, field_loc, "{s} demoted to opaque type - has bitfield", .{container_kind_name}); - break :blk Tag.opaque_literal.init(); + bitfield_count += 1; } var is_anon = false; @@ -961,7 +964,8 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD try functions.append(flexible_array_fn); continue; } - const field_type = transQualType(c, scope, field_qt, field_loc) catch |err| switch (err) { + + const field_otype = transQualType(c, scope, field_qt, field_loc) catch |err| switch (err) { error.UnsupportedType => { try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl.getCanonicalDecl()), {}); try warn(c, scope, record_loc, "{s} demoted to opaque type - unable to translate type of field {s}", .{ container_kind_name, field_name }); @@ -970,6 +974,18 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD else => |e| return e, }; + const field_type = if (field_decl.isBitField()) bitfield_type: { + try backings.append(field_otype); + const bitwidth = field_decl.getBitWidthValue(c.clang_context); + if (field_decl.isUnnamedBitField()) { + break :bitfield_type Tag.void_type.init(); + } + break :bitfield_type try Tag.type.create(c.arena, try std.fmt.allocPrint(c.arena, "u{}", .{bitwidth})); + } else regular_field: { + try backings.append(Tag.null_literal.init()); + break :regular_field field_otype; + }; + const alignment = if (flexible_field != null and field_decl.getFieldIndex() == 0) @as(c_uint, @intCast(record_alignment)) else @@ -994,18 +1010,77 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD .default_value = default_value, }); } + const has_bitfield = bitfield_count > 0; const record_payload = try c.arena.create(ast.Payload.Record); record_payload.* = .{ .base = .{ .tag = ([2]Tag{ .@"struct", .@"union" })[@intFromBool(is_union)] }, .data = .{ - .layout = .@"extern", + .layout = if (has_bitfield) .none else .@"extern", + // We will leave to the `EmulateBitfieldStruct` to handle the layout .fields = try c.arena.dupe(ast.Payload.Record.Field, fields.items), .functions = try c.arena.dupe(Node, functions.items), .variables = &.{}, }, }; - break :blk Node.initPayload(&record_payload.base); + + const node = Node.initPayload(&record_payload.base); + + // Use `EmulateBitfieldStruct` to transform the struct if any bitfield is found + if (has_bitfield) { + try warn( + c, + scope, + record_loc, + "{s} has bitfields - limited bitfield support - nested extern struct {{}} or struct {{}} results in an opaque type", + .{name}, + ); + const Initializer = ast.Payload.ContainerInitDot.Initializer; + + var nfields = std.ArrayList(ast.Node).init(c.gpa); + defer nfields.deinit(); + for (fields.items, 0..fields.items.len) |fl, flidx| { + var fieldDef: std.BoundedArray(Initializer, 5) = .{}; + const quoted_name = try std.fmt.allocPrint(c.arena, "\"{s}\"", .{fl.name}); + fieldDef.appendAssumeCapacity(.{ + .name = "name", + .value = try Tag.string_literal.create(c.arena, quoted_name), + }); + fieldDef.appendAssumeCapacity(.{ + .name = "type", + .value = fl.type, + }); + const backingTypeNode = backings.items[flidx]; + fieldDef.appendAssumeCapacity(.{ + .name = "backing_integer", + .value = backingTypeNode, + }); + const isPointer = if (fl.type.castTag(.c_pointer)) |_| + true + else if (fl.type.castTag(.optional_type)) |castedNode| + (if (castedNode.data.castTag(.single_pointer)) |_| true else false) + else + false; + if (isPointer) { + fieldDef.appendAssumeCapacity(.{ + .name = "is_pointer", + .value = Tag.true_literal.init(), + }); + } + try nfields.append(try Tag.container_init_dot.create(c.arena, try c.arena.dupe(Initializer, fieldDef.constSlice()))); + } + + const defs = try Tag.tuple.create(c.arena, try c.arena.dupe(Node, nfields.items)); + + var cfglist: std.BoundedArray(Initializer, 3) = .{}; + const cfg = try Tag.container_init_dot.create(c.arena, try c.arena.dupe(Initializer, cfglist.constSlice())); + const emulate_call = try Tag.helpers_emulate_bitfield_struct.create(c.arena, .{ + .definition = try Tag.address_of.create(c.arena, defs), + .cfg = cfg, + }); + break :blk emulate_call; + } + break :blk node; }; const payload = try c.arena.create(ast.Payload.SimpleVarDecl); diff --git a/src/zig_clang.cpp b/src/zig_clang.cpp index 635bad13747f..9a2a754421c2 100644 --- a/src/zig_clang.cpp +++ b/src/zig_clang.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include #if __GNUC__ >= 8 #pragma GCC diagnostic pop @@ -3954,6 +3956,17 @@ bool ZigClangFieldDecl_isBitField(const struct ZigClangFieldDecl *self) { return casted->isBitField(); } +bool ZigClangFieldDecl_isUnnamedBitField(const struct ZigClangFieldDecl *self) { + auto casted = reinterpret_cast(self); + return casted->isUnnamedBitfield(); +} + +unsigned int ZigClangFieldDecl_getBitWidthValue(const struct ZigClangFieldDecl *self, const struct ZigClangASTContext *ast_cx){ + auto casted = reinterpret_cast(self); + auto casted_ast_cx = reinterpret_cast(ast_cx); + return casted->getBitWidthValue(*casted_ast_cx); +} + bool ZigClangFieldDecl_isAnonymousStructOrUnion(const ZigClangFieldDecl *field_decl) { return reinterpret_cast(field_decl)->isAnonymousStructOrUnion(); } diff --git a/src/zig_clang.h b/src/zig_clang.h index 6d7ebc64f541..4ad53b2dc364 100644 --- a/src/zig_clang.h +++ b/src/zig_clang.h @@ -1707,6 +1707,8 @@ ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangMacroDefinitionRecord_getSour ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangMacroDefinitionRecord_getSourceRange_getEnd(const struct ZigClangMacroDefinitionRecord *); ZIG_EXTERN_C bool ZigClangFieldDecl_isBitField(const struct ZigClangFieldDecl *); +ZIG_EXTERN_C bool ZigClangFieldDecl_isUnnamedBitField(const struct ZigClangFieldDecl *); +ZIG_EXTERN_C unsigned int ZigClangFieldDecl_getBitWidthValue(const struct ZigClangFieldDecl *, const struct ZigClangASTContext *); ZIG_EXTERN_C bool ZigClangFieldDecl_isAnonymousStructOrUnion(const ZigClangFieldDecl *); ZIG_EXTERN_C struct ZigClangQualType ZigClangFieldDecl_getType(const struct ZigClangFieldDecl *); ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangFieldDecl_getLocation(const struct ZigClangFieldDecl *); diff --git a/test/c_abi/cfuncs.c b/test/c_abi/cfuncs.c index 028f6e06d27e..7608914c650d 100644 --- a/test/c_abi/cfuncs.c +++ b/test/c_abi/cfuncs.c @@ -5567,3 +5567,118 @@ struct byval_tail_callsite_attr_Rect { double c_byval_tail_callsite_attr(struct byval_tail_callsite_attr_Rect in) { return in.size.width; } + +struct bitfield_simple { + uint8_t a: 1; + uint8_t b: 1; + uint8_t c: 1; + uint8_t d: 1; + uint8_t e: 1; + uint8_t f: 1; + uint8_t g: 1; + uint8_t h: 1; +}; + +signed c_struct_bitfield_simple_access( + struct bitfield_simple const * s, unsigned offset) { + switch (offset) { + case 0: + return s->a; + case 1: + return s->b; + case 2: + return s->c; + case 3: + return s->d; + case 4: + return s->e; + case 5: + return s->f; + case 6: + return s->g; + case 7: + return s->h; + default: + return -2; + } +} + +struct bitfield_pad { + unsigned a: 1; + unsigned b: 1; + unsigned c: 1; +}; + +signed c_struct_bitfield_pad_access( + struct bitfield_pad const * s, unsigned offset) { + switch (offset) { + case 0: + return s->a; + case 1: + return s->b; + case 2: + return s->c; + default: + return -2; + } +} + +struct tricky_bits +{ + unsigned int first : 9; + unsigned int second : 7; + unsigned int may_straddle : 30; + unsigned int last : 18; +} ; + +signed c_struct_bitfield_tricky_bits_access(struct tricky_bits const * s, unsigned offset) { + switch (offset) { + case 0: + return s->first; + case 1: + return s->second; + case 2: + return s->may_straddle; + case 3: + return s->last; + default: + return -2; + } +} + +struct mixing_bits { + unsigned first; + char b0_0: 1; + char b0_1: 1; + char b0_2: 1; + unsigned second; + unsigned int b1_0: 3; + unsigned int b1_1: 4; + unsigned int b1_2: 1; + short third; +}; + +int64_t c_struct_bitfield_mixing_bits_access(struct mixing_bits const *s, unsigned offset) { + switch (offset) { + case 0: + return s->first; + case 1: + return s->b0_0; + case 2: + return s->b0_1; + case 3: + return s->b0_2; + case 4: + return s->second; + case 5: + return s->b1_0; + case 6: + return s->b1_1; + case 7: + return s->b1_2; + case 8: + return s->third; + default: + return -2; + } +} diff --git a/test/c_abi/main.zig b/test/c_abi/main.zig index 86b9584a6ee7..afb2341b0eb3 100644 --- a/test/c_abi/main.zig +++ b/test/c_abi/main.zig @@ -951,6 +951,135 @@ test "Struct with array as padding." { try expect(x.b == 155); } +const StructBitfieldSimple = std.zig.c_translation.EmulateBitfieldStruct(&.{ + .{ .name = "a", .type = u1, .backing_integer = u8 }, + .{ .name = "b", .type = u1, .backing_integer = u8 }, + .{ .name = "c", .type = u1, .backing_integer = u8 }, + .{ .name = "d", .type = u1, .backing_integer = u8 }, + .{ .name = "e", .type = u1, .backing_integer = u8 }, + .{ .name = "f", .type = u1, .backing_integer = u8 }, + .{ .name = "g", .type = u1, .backing_integer = u8 }, + .{ .name = "h", .type = u1, .backing_integer = u8 }, +}, .{}); + +extern fn c_struct_bitfield_simple_access(*const StructBitfieldSimple, offset: c_uint) c_int; + +test "translate-c: struct with only bitfields" { + if (@typeInfo(StructBitfieldSimple) == .@"opaque") { + return error.SkipZigTest; + } + const word = StructBitfieldSimple{ .a = 1 }; + // @compileLog(@typeInfo(StructBitfieldSimple).Struct.fields); + try expectEqual(@as(c_int, 1), c_struct_bitfield_simple_access(&word, 0)); + try expectEqual(@as(c_int, 0), c_struct_bitfield_simple_access(&word, 7)); +} + +const StructBitfieldPad = std.zig.c_translation.EmulateBitfieldStruct(&.{ + .{ .name = "a", .type = u1, .backing_integer = u32 }, + .{ .name = "b", .type = u1, .backing_integer = u32 }, + .{ .name = "c", .type = u1, .backing_integer = u32 }, +}, .{}); + +extern fn c_struct_bitfield_pad_access(*const StructBitfieldPad, offset: c_uint) c_int; + +test "translate-c: struct with unanmed bitfield syntax" { + if (@typeInfo(StructBitfieldPad) == .@"opaque") { + return error.SkipZigTest; + } + const word = StructBitfieldPad{ .a = 1 }; + try expectEqual(@as(c_int, 1), c_struct_bitfield_pad_access(&word, 0)); + try expectEqual(@as(c_int, 0), c_struct_bitfield_pad_access(&word, 1)); +} + +const StructTrickyBits = std.zig.c_translation.EmulateBitfieldStruct(&.{ + .{ .name = "first", .type = u9, .backing_integer = u32 }, + .{ .name = "second", .type = u7, .backing_integer = u32 }, + .{ .name = "may_straddle", .type = u30, .backing_integer = u32 }, + .{ .name = "last", .type = u18, .backing_integer = u32 }, +}, .{}); + +extern fn c_struct_bitfield_tricky_bits_access(*const StructTrickyBits, offset: c_uint) c_int; + +test "translate-c: struct with may straddle bitfield" { + if (@typeInfo(StructTrickyBits) == .@"opaque") { + return error.SkipZigTest; + } + const word = StructTrickyBits{ .may_straddle = std.math.maxInt(u30) }; + // @compileLog(@typeInfo(StructTrickyBits).Struct.fields); + try expectEqual( + @as(c_int, std.math.maxInt(u30)), + c_struct_bitfield_tricky_bits_access(&word, 2), + ); +} + +const StructMixingBits = std.zig.c_translation.EmulateBitfieldStruct(&.{ + .{ .name = "first", .type = c_uint, .backing_integer = null }, + .{ .name = "b0_0", .type = u1, .backing_integer = c_char }, + .{ .name = "b0_1", .type = u1, .backing_integer = c_char }, + .{ .name = "b0_2", .type = u1, .backing_integer = c_char }, + .{ .name = "second", .type = c_uint, .backing_integer = null }, + .{ .name = "b1_0", .type = u3, .backing_integer = c_uint }, + .{ .name = "b1_1", .type = u4, .backing_integer = c_uint }, + .{ .name = "b1_3", .type = u1, .backing_integer = c_uint }, + .{ .name = "third", .type = c_short, .backing_integer = null }, +}, .{}); + +extern fn c_struct_bitfield_mixing_bits_access(*const StructMixingBits, offset: c_uint) i64; + +test "translate-c: struct mixing bitfields with regulars." { + if (@typeInfo(StructMixingBits) == .@"opaque") { + return error.SkipZigTest; + } + + { + const word = StructMixingBits{ .b0_2 = 1 }; + try expectEqual( + @as(u64, 1), + @abs(c_struct_bitfield_mixing_bits_access(&word, 3)), + ); + } + { + const word = StructMixingBits{ .second = std.math.maxInt(c_uint) }; + try expectEqual( + @as(i64, std.math.maxInt(c_uint)), + c_struct_bitfield_mixing_bits_access(&word, 4), + ); + } + { + const word = StructMixingBits{ .second = std.math.maxInt(c_uint) }; + inline for (0..4) |i| { + try expectEqual(@as(i64, 0), c_struct_bitfield_mixing_bits_access(&word, i)); + } + inline for (5..9) |i| { + try expectEqual(@as(i64, 0), c_struct_bitfield_mixing_bits_access(&word, i)); + } + } + { + const word = StructMixingBits{ .third = std.math.maxInt(c_short) }; + try expectEqual(@as(i64, std.math.maxInt(c_short)), c_struct_bitfield_mixing_bits_access(&word, 8)); + inline for (0..8) |i| { + try expectEqual(@as(i64, 0), c_struct_bitfield_mixing_bits_access(&word, i)); + } + } +} + +const NestedStructWithoutBitfields = extern struct { + f0: c_uint = std.mem.zeroes(c_uint), + f1: c_uint = std.mem.zeroes(c_uint), +}; + +const MixedStructWithBitfields = std.zig.c_translation.EmulateBitfieldStruct(&.{ + .{ .name = "b0", .type = u1, .backing_integer = c_uint }, + .{ .name = "b1", .type = u1, .backing_integer = c_uint }, + .{ .name = "b2", .type = u1, .backing_integer = c_uint }, + .{ .name = "f0", .type = NestedStructWithoutBitfields, .backing_integer = null }, +}, .{}); + +test "translate-c: struct mixing bitfields with structs" { + // It seems zig could not describe such case + try expectEqual(@as(std.builtin.TypeId, .@"opaque"), std.meta.activeTag(@typeInfo(MixedStructWithBitfields))); +} + const FloatArrayStruct = extern struct { origin: extern struct { x: f64, diff --git a/test/cases/translate_c/pointer_to_struct_demoted_opaque_due_to_bit_fields.c b/test/cases/translate_c/pointer_to_struct_demoted_opaque_due_to_bit_fields.c deleted file mode 100644 index 320368749783..000000000000 --- a/test/cases/translate_c/pointer_to_struct_demoted_opaque_due_to_bit_fields.c +++ /dev/null @@ -1,15 +0,0 @@ -struct Foo { - unsigned int: 1; -}; -struct Bar { - struct Foo *foo; -}; - -// translate-c -// c_frontend=clang -// -// pub const struct_Foo = opaque {}; -// -// pub const struct_Bar = extern struct { -// foo: ?*struct_Foo = @import("std").mem.zeroes(?*struct_Foo), -// }; diff --git a/test/cases/translate_c/struct_bitfield_unnamed_field.c b/test/cases/translate_c/struct_bitfield_unnamed_field.c new file mode 100644 index 000000000000..c5903d4628f9 --- /dev/null +++ b/test/cases/translate_c/struct_bitfield_unnamed_field.c @@ -0,0 +1,27 @@ +typedef struct { + char first: 1; + char second: 2; + char : 0; + char fourth: 1; +} eightbits; + +// translate-c +// c_frontend=clang +// +// pub const eightbits = @import("std").zig.c_translation.EmulateBitfieldStruct(&(.{ .{ +// .name = "first", +// .type = u1, +// .backing_integer = u8, +// }, .{ +// .name = "second", +// .type = u2, +// .backing_integer = u8, +// }, .{ +// .name = "unnamed_0", +// .type = void, +// .backing_integer = u8, +// }, .{ +// .name = "fourth", +// .type = u1, +// .backing_integer = u8, +// } }), .{}); \ No newline at end of file diff --git a/test/cases/translate_c/struct_bitfields.c b/test/cases/translate_c/struct_bitfields.c new file mode 100644 index 000000000000..6b94117fec42 --- /dev/null +++ b/test/cases/translate_c/struct_bitfields.c @@ -0,0 +1,27 @@ +typedef struct { + char first: 1; + char second: 2; + char third: 4; + char fourth: 1; +} eightbits; + +// translate-c +// c_frontend=clang +// +// pub const eightbits = @import("std").zig.c_translation.EmulateBitfieldStruct(&(.{ .{ +// .name = "first", +// .type = u1, +// .backing_integer = u8, +// }, .{ +// .name = "second", +// .type = u2, +// .backing_integer = u8, +// }, .{ +// .name = "third", +// .type = u4, +// .backing_integer = u8, +// }, .{ +// .name = "fourth", +// .type = u1, +// .backing_integer = u8, +// } }), .{}); \ No newline at end of file diff --git a/test/cases/translate_c/struct_bitfields_mixing_pointers.c b/test/cases/translate_c/struct_bitfields_mixing_pointers.c new file mode 100644 index 000000000000..817576c8a920 --- /dev/null +++ b/test/cases/translate_c/struct_bitfields_mixing_pointers.c @@ -0,0 +1,20 @@ +typedef struct word word; + +struct word { + signed int (*ptr)(word *v); + unsigned first: 1; +}; + +// translate-c +// c_frontend=clang +// +// pub const struct_word = @import("std").zig.c_translation.EmulateBitfieldStruct(&(.{ .{ +// .name = "ptr", +// .type = ?*const fn (?*word) callconv(.C) c_int, +// .backing_integer = null, +// .is_pointer = true, +// }, .{ +// .name = "first", +// .type = u1, +// .backing_integer = c_uint, +// } }), .{}); \ No newline at end of file diff --git a/test/cases/translate_c/struct_bitfields_mixing_regulars.c b/test/cases/translate_c/struct_bitfields_mixing_regulars.c new file mode 100644 index 000000000000..7b3ce5166e34 --- /dev/null +++ b/test/cases/translate_c/struct_bitfields_mixing_regulars.c @@ -0,0 +1,27 @@ +typedef struct { + signed int v0; + unsigned b0: 1; + unsigned b1: 1; + unsigned b2: 1; +} word; + +// translate-c +// c_frontend=clang +// +// pub const word = @import("std").zig.c_translation.EmulateBitfieldStruct(&(.{ .{ +// .name = "v0", +// .type = c_int, +// .backing_integer = null, +// }, .{ +// .name = "b0", +// .type = u1, +// .backing_integer = c_uint, +// }, .{ +// .name = "b1", +// .type = u1, +// .backing_integer = c_uint, +// }, .{ +// .name = "b2", +// .type = u1, +// .backing_integer = c_uint, +// } }), .{}); \ No newline at end of file diff --git a/test/translate_c.zig b/test/translate_c.zig index 431289f9e369..dc43ae53331b 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -3477,35 +3477,39 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - cases.add("Demote function that initializes opaque struct", - \\struct my_struct { - \\ unsigned a: 15; - \\ unsigned: 2; - \\ unsigned b: 15; - \\}; - \\void initialize(void) { - \\ struct my_struct S = {.a = 1, .b = 2}; - \\} - , &[_][]const u8{ - \\warning: local variable has opaque type - , - \\warning: unable to translate function, demoted to extern - \\pub extern fn initialize() void; - }); + if (false) { // FIXME: bitfield is now supported, find another way to emit opaque + cases.add("Demote function that initializes opaque struct", + \\struct my_struct { + \\ unsigned a: 15; + \\ unsigned: 2; + \\ unsigned b: 15; + \\ unsigned d; + \\}; + \\void initialize(void) { + \\ struct my_struct S = {.a = 1, .b = 2}; + \\} + , &[_][]const u8{ + \\warning: local variable has opaque type + , + \\warning: unable to translate function, demoted to extern + \\pub extern fn initialize() void; + }); - cases.add("Demote function that dereferences opaque type", - \\struct my_struct { - \\ unsigned a: 1; - \\}; - \\void deref(struct my_struct *s) { - \\ *s; - \\} - , &[_][]const u8{ - \\warning: cannot dereference opaque type - , - \\warning: unable to translate function, demoted to extern - \\pub extern fn deref(arg_s: ?*struct_my_struct) void; - }); + cases.add("Demote function that dereferences opaque type", + \\struct my_struct { + \\ unsigned a: 1; + \\ unsigned b; + \\}; + \\void deref(struct my_struct *s) { + \\ *s; + \\} + , &[_][]const u8{ + \\warning: cannot dereference opaque type + , + \\warning: unable to translate function, demoted to extern + \\pub extern fn deref(arg_s: ?*struct_my_struct) void; + }); + } cases.add("Demote function that dereference types that contain opaque type", \\struct inner { diff --git a/tools/bitfields-layout-reporter.c b/tools/bitfields-layout-reporter.c new file mode 100644 index 000000000000..67b0d4aa7ce9 --- /dev/null +++ b/tools/bitfields-layout-reporter.c @@ -0,0 +1,81 @@ +#include +#include +#include + +const char *bit_rep[16] = { + [ 0] = "0000", [ 1] = "0001", [ 2] = "0010", [ 3] = "0011", + [ 4] = "0100", [ 5] = "0101", [ 6] = "0110", [ 7] = "0111", + [ 8] = "1000", [ 9] = "1001", [10] = "1010", [11] = "1011", + [12] = "1100", [13] = "1101", [14] = "1110", [15] = "1111", +}; + +void print_byte(uint8_t byte) +{ + printf("%s%s", bit_rep[byte >> 4], bit_rep[byte & 0x0F]); +} + + +typedef struct { + char a: 1; + char b: 1; + char c: 1; + char d: 1; + char e: 1; + char f: 1; + char g: 1; + char h: 1; +} bitword; + +typedef struct { + char a: 1; + char :0; + char c: 1; +} bitword_unnamed; + +// Copied from https://learn.microsoft.com/en-us/cpp/c-language/c-bit-fields?view=msvc-170 +struct tricky_bits +{ + unsigned int first : 9; + unsigned int second : 7; + unsigned int may_straddle : 30; + unsigned int last : 18; +} ; + + +struct mixing_bits { + unsigned first; + char b0_0: 1; + char b0_1: 1; + char b0_2: 1; + unsigned second; + unsigned int b1_0: 3; + unsigned int b1_1: 4; + unsigned int b1_2: 1; + short third; +}; + +int main() { + bitword w0 = {.a = -1}; + uint8_t backing0 = *(uint8_t *)(&w0); + bool reverse = (backing0 & 0b10000000) > 0; // The bits allocates from MSB to LSB + + bitword_unnamed w1 = {.c = -1, .a = -1}; + uint16_t backing1 = *(uint16_t *)(&w1); + bool unnamed_void_boundary = (backing0 & (reverse ? 0b100000001000000 : 0b0000000100000001)) > 0; + + bool not_straddle = sizeof(struct tricky_bits) == 12; + bool straddle = sizeof(struct tricky_bits) == 8; + + bool collpse_padding = sizeof(struct mixing_bits) == 20; + bool not_collpse_padding = sizeof(struct mixing_bits) == 16; + + printf(".{\n .reverse_bits=%s,\n", reverse ? "true" : "false"); + printf(" .unnamed_void_boundary=%s,\n", unnamed_void_boundary ? "true" : "false"); + printf(" .straddle = %s,\n", straddle ? "true" : (not_straddle ? "false" : "@compilerError(\"straddle test failed\")")); + printf(" .collapse_padding = %s,\n", + collpse_padding ? "true" : ( + not_collpse_padding ? "false" : "@compilerError(\"collapse padding test failed\")" + ) + ); + printf("}\n"); +} diff --git a/tools/update-bitfields-layouts.zig b/tools/update-bitfields-layouts.zig new file mode 100644 index 000000000000..48f7448f5886 --- /dev/null +++ b/tools/update-bitfields-layouts.zig @@ -0,0 +1,4 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +pub fn main() !void {}