Skip to content

Commit 12ef597

Browse files
committed
translate-c: initial support for bitfields
1 parent 624fa85 commit 12ef597

File tree

13 files changed

+424
-21
lines changed

13 files changed

+424
-21
lines changed

lib/compiler/aro_translate_c/ast.zig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ pub const Node = extern union {
227227
/// [1]type{val} ** count
228228
array_filler,
229229

230+
/// @import("std").zig.c_translation.EmulateBitfieldStruct(S)
231+
helpers_emulate_bitfield_struct,
232+
230233
pub const last_no_payload_tag = Tag.@"break";
231234
pub const no_payload_count = @intFromEnum(last_no_payload_tag) + 1;
232235

@@ -376,6 +379,7 @@ pub const Node = extern union {
376379
.shuffle => Payload.Shuffle,
377380
.builtin_extern => Payload.Extern,
378381
.macro_arithmetic => Payload.MacroArithmetic,
382+
.helpers_emulate_bitfield_struct => Payload.EmulateBitfieldStruct,
379383
};
380384
}
381385

@@ -698,6 +702,14 @@ pub const Payload = struct {
698702
},
699703
};
700704

705+
pub const EmulateBitfieldStruct = struct {
706+
base: Payload,
707+
data: struct {
708+
record: Node,
709+
backings: Node,
710+
},
711+
};
712+
701713
pub const StringSlice = struct {
702714
base: Payload,
703715
data: struct {
@@ -917,6 +929,11 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
917929
const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "shuffleVectorIndex" });
918930
return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
919931
},
932+
.helpers_emulate_bitfield_struct => {
933+
const payload = node.castTag(.helpers_emulate_bitfield_struct).?.data;
934+
const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "EmulateBitfieldStruct" });
935+
return renderCall(c, import_node, &.{ payload.record, payload.backings });
936+
},
920937
.vector => {
921938
const payload = node.castTag(.vector).?.data;
922939
return renderBuiltinCall(c, "@Vector", &.{ payload.lhs, payload.rhs });
@@ -2387,6 +2404,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {
23872404
.helpers_promoteIntLiteral,
23882405
.helpers_shuffle_vector_index,
23892406
.helpers_flexible_array_type,
2407+
.helpers_emulate_bitfield_struct,
23902408
.std_mem_zeroinit,
23912409
.integer_literal,
23922410
.float_literal,

lib/std/zig/c_translation.zig

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,3 +670,168 @@ test "Extended C ABI casting" {
670670
try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); // comptime_int -> c_longlong
671671
}
672672
}
673+
674+
const BitfieldEmulation = struct {
675+
/// By default the bits are allocated from LSB to MSB
676+
/// (follows Zig's packed struct and most ABI).
677+
/// Sets to true to allocate from MSB to LSB.
678+
reverse_bits: bool,
679+
/// Most ABI starts a new storage unit after a unnamed zero-bit width bit field.
680+
/// Some ABI ignores that, sets to false.
681+
unnamed_void_boundary: bool,
682+
683+
fn fromTarget(target: std.Target) ?BitfieldEmulation {
684+
if (target.cpu.arch.isX86() and target.os.tag == .linux) {
685+
return .{
686+
.reverse_bits = false,
687+
.unnamed_void_boundary = true,
688+
};
689+
}
690+
return null;
691+
}
692+
};
693+
694+
/// Translate a packed struct type to adapt the C bitfields on the target platform.
695+
///
696+
/// If the target platform is unsupported, an opaque type will be returned.
697+
pub fn EmulateBitfieldStruct(comptime T: type, comptime l: anytype) type {
698+
const cfg = BitfieldEmulation.fromTarget(builtin.target) orelse return opaque {};
699+
const inf = infT: {
700+
const typeinf = @typeInfo(T);
701+
if (typeinf != .Struct or typeinf.Struct.layout != .@"packed") {
702+
@compileError("unsupported type for bitfields, zig translate-c might have an error");
703+
}
704+
break :infT typeinf.Struct;
705+
};
706+
707+
const linf = infL: {
708+
const LType = @TypeOf(l);
709+
const typeInf = @typeInfo(LType);
710+
if (typeInf != .Struct or !typeInf.Struct.is_tuple) {
711+
@compileError("the second arg must be a tuple of type");
712+
}
713+
break :infL typeInf.Struct;
714+
};
715+
716+
comptime {
717+
// worst case: every after field needs a padding field
718+
var buf = std.BoundedArray(
719+
std.builtin.Type.StructField,
720+
linf.fields.len * 2,
721+
).init(0) catch unreachable;
722+
var units = std.BoundedArray(
723+
comptime_int,
724+
linf.fields.len,
725+
).init(0) catch unreachable;
726+
var lastType: ?type = null;
727+
var leftBitWidth = 0;
728+
var padFieldCount = 0;
729+
730+
for (linf.fields, inf.fields) |bf, f| {
731+
const K = @field(l, bf.name);
732+
const kInf = @typeInfo(K);
733+
if (kInf != .Int) {
734+
@compileError("expects integer type");
735+
}
736+
const fInf = @typeInfo(f.type).Int;
737+
738+
if (f.type == void) {
739+
if (!cfg.unnamed_void_boundary) continue;
740+
if (leftBitWidth > 0) {
741+
const bits = leftBitWidth - f.alignment;
742+
if (bits < 0) {
743+
@compileError("impossible alignment since it is larger than the rest bits");
744+
}
745+
buf.append(.{
746+
.type = @Type(.{ .Int = .{
747+
.bits = bits,
748+
.signedness = .unsigned,
749+
} }),
750+
.alignment = f.alignment,
751+
.default_value = f.default_value,
752+
.is_comptime = f.is_comptime,
753+
.name = f.name,
754+
}) catch unreachable;
755+
units.append(0) catch unreachable;
756+
lastType = null;
757+
leftBitWidth = 0;
758+
}
759+
continue;
760+
}
761+
762+
// needs padding?
763+
const allocatedBits = fInf.bits + f.alignment;
764+
if (leftBitWidth < allocatedBits) {
765+
// sets padding
766+
if (leftBitWidth > 0) {
767+
const typ = @Type(.{ .Int = .{
768+
.bits = leftBitWidth,
769+
.signedness = .unsigned,
770+
} });
771+
buf.append(.{
772+
.alignment = 0,
773+
.type = typ,
774+
.default_value = &std.mem.zeroes(typ),
775+
.is_comptime = false,
776+
.name = std.fmt.comptimePrint(" pad_{}", .{padFieldCount}),
777+
}) catch unreachable;
778+
padFieldCount += 1;
779+
const i = units.get(units.len);
780+
units.set(units.len, i + 1);
781+
}
782+
lastType = K;
783+
leftBitWidth = kInf.Int.bits;
784+
units.append(0) catch unreachable;
785+
}
786+
leftBitWidth -= fInf.bits;
787+
buf.append(f) catch unreachable;
788+
const i = units.get(units.len - 1);
789+
units.set(units.len - 1, i + 1);
790+
}
791+
792+
if (leftBitWidth > 0) {
793+
const typ = @Type(.{ .Int = .{
794+
.bits = leftBitWidth,
795+
.signedness = .unsigned,
796+
} });
797+
buf.append(.{
798+
.alignment = 0,
799+
.type = typ,
800+
.default_value = &std.mem.zeroes(typ),
801+
.is_comptime = false,
802+
.name = std.fmt.comptimePrint(" pad_{}", .{padFieldCount}),
803+
}) catch unreachable;
804+
padFieldCount += 1;
805+
const i = units.get(units.len - 1);
806+
units.set(units.len - 1, i + 1);
807+
}
808+
809+
if (cfg.reverse_bits) {
810+
var offset = 0;
811+
for (units.constSlice()) |i| {
812+
const nextOffset = offset + i;
813+
std.mem.reverse(std.builtin.Type.StructField, buf.buffer[offset..nextOffset]);
814+
offset = nextOffset;
815+
}
816+
}
817+
818+
const backingInteger = bint: {
819+
var bits = 0;
820+
for (buf.constSlice()) |field| {
821+
bits += field.alignment + @bitSizeOf(field.type);
822+
}
823+
break :bint @Type(.{ .Int = .{
824+
.signedness = .unsigned,
825+
.bits = bits,
826+
} });
827+
};
828+
829+
return @Type(.{ .Struct = .{
830+
.backing_integer = backingInteger,
831+
.decls = inf.decls,
832+
.fields = buf.buffer[0..buf.len],
833+
.layout = inf.layout,
834+
.is_tuple = inf.is_tuple,
835+
} });
836+
}
837+
}

src/clang.zig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,12 @@ pub const FieldDecl = opaque {
487487
pub const isBitField = ZigClangFieldDecl_isBitField;
488488
extern fn ZigClangFieldDecl_isBitField(*const FieldDecl) bool;
489489

490+
pub const isUnnamedBitField = ZigClangFieldDecl_isUnnamedBitField;
491+
extern fn ZigClangFieldDecl_isUnnamedBitField(*const FieldDecl) bool;
492+
493+
pub const getBitWidthValue = ZigClangFieldDecl_getBitWidthValue;
494+
extern fn ZigClangFieldDecl_getBitWidthValue(*const FieldDecl, *const ASTContext) c_uint;
495+
490496
pub const getType = ZigClangFieldDecl_getType;
491497
extern fn ZigClangFieldDecl_getType(*const FieldDecl) QualType;
492498

src/translate_c.zig

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -923,22 +923,28 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD
923923
var functions = std.ArrayList(Node).init(c.gpa);
924924
defer functions.deinit();
925925

926+
var backings = std.ArrayList(ast.Node).init(c.gpa);
927+
defer backings.deinit();
928+
926929
const flexible_field = flexibleArrayField(c, record_def);
927930
var unnamed_field_count: u32 = 0;
928931
var it = record_def.field_begin();
929932
const end_it = record_def.field_end();
930933
const layout = record_def.getASTRecordLayout(c.clang_context);
931934
const record_alignment = layout.getAlignment();
932935

936+
var bitfield_count: usize = 0;
937+
933938
while (it.neq(end_it)) : (it = it.next()) {
934939
const field_decl = it.deref();
935940
const field_loc = field_decl.getLocation();
936941
const field_qt = field_decl.getType();
937942

938943
if (field_decl.isBitField()) {
939-
try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl.getCanonicalDecl()), {});
940-
try warn(c, scope, field_loc, "{s} demoted to opaque type - has bitfield", .{container_kind_name});
941-
break :blk Tag.opaque_literal.init();
944+
// try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl.getCanonicalDecl()), {});
945+
// try warn(c, scope, field_loc, "{s} demoted to opaque type - has bitfield", .{container_kind_name});
946+
// break :blk Tag.opaque_literal.init();
947+
bitfield_count += 1;
942948
}
943949

944950
var is_anon = false;
@@ -961,7 +967,8 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD
961967
try functions.append(flexible_array_fn);
962968
continue;
963969
}
964-
const field_type = transQualType(c, scope, field_qt, field_loc) catch |err| switch (err) {
970+
971+
const field_otype = transQualType(c, scope, field_qt, field_loc) catch |err| switch (err) {
965972
error.UnsupportedType => {
966973
try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl.getCanonicalDecl()), {});
967974
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 +977,15 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD
970977
else => |e| return e,
971978
};
972979

980+
const field_type = if (field_decl.isBitField()) bitfield_type: {
981+
try backings.append(field_otype);
982+
const bitwidth = field_decl.getBitWidthValue(c.clang_context);
983+
if (field_decl.isUnnamedBitField()) {
984+
break :bitfield_type Tag.void_type.init();
985+
}
986+
break :bitfield_type try Tag.type.create(c.arena, try std.fmt.allocPrint(c.arena, "u{}", .{bitwidth}));
987+
} else field_otype;
988+
973989
const alignment = if (flexible_field != null and field_decl.getFieldIndex() == 0)
974990
@as(c_uint, @intCast(record_alignment))
975991
else
@@ -995,17 +1011,36 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD
9951011
});
9961012
}
9971013

1014+
const is_packed = bitfield_count > 0;
1015+
1016+
if (is_packed and bitfield_count != fields.items.len) {
1017+
try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl.getCanonicalDecl()), {});
1018+
try warn(c, scope, record_loc, "{s} demoted to opaque type - has bitfields mixed with regular fields", .{container_kind_name});
1019+
break :blk Tag.opaque_literal.init();
1020+
}
1021+
9981022
const record_payload = try c.arena.create(ast.Payload.Record);
9991023
record_payload.* = .{
10001024
.base = .{ .tag = ([2]Tag{ .@"struct", .@"union" })[@intFromBool(is_union)] },
10011025
.data = .{
1002-
.layout = .@"extern",
1026+
.layout = if (is_packed) .@"packed" else .@"extern",
10031027
.fields = try c.arena.dupe(ast.Payload.Record.Field, fields.items),
10041028
.functions = try c.arena.dupe(Node, functions.items),
10051029
.variables = &.{},
10061030
},
10071031
};
1008-
break :blk Node.initPayload(&record_payload.base);
1032+
1033+
const node = Node.initPayload(&record_payload.base);
1034+
1035+
if (bitfield_count > 0) {
1036+
const backing_tuple = try Tag.tuple.create(c.arena, try c.arena.dupe(ast.Node, backings.items));
1037+
const emulate_call = try Tag.helpers_emulate_bitfield_struct.create(c.arena, .{
1038+
.record = node,
1039+
.backings = backing_tuple,
1040+
});
1041+
break :blk emulate_call;
1042+
}
1043+
break :blk node;
10091044
};
10101045

10111046
const payload = try c.arena.create(ast.Payload.SimpleVarDecl);

src/zig_clang.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
* 3. Prevent C++ from infecting the rest of the project.
1414
*/
1515
#include "zig_clang.h"
16+
#include <clang/AST/ASTContext.h>
17+
#include <clang/AST/Decl.h>
1618

1719
#if __GNUC__ >= 8
1820
#pragma GCC diagnostic push
@@ -3954,6 +3956,17 @@ bool ZigClangFieldDecl_isBitField(const struct ZigClangFieldDecl *self) {
39543956
return casted->isBitField();
39553957
}
39563958

3959+
bool ZigClangFieldDecl_isUnnamedBitField(const struct ZigClangFieldDecl *self) {
3960+
auto casted = reinterpret_cast<const clang::FieldDecl *>(self);
3961+
return casted->isUnnamedBitfield();
3962+
}
3963+
3964+
unsigned int ZigClangFieldDecl_getBitWidthValue(const struct ZigClangFieldDecl *self, const struct ZigClangASTContext *ast_cx){
3965+
auto casted = reinterpret_cast<const clang::FieldDecl *>(self);
3966+
auto casted_ast_cx = reinterpret_cast<const clang::ASTContext *>(ast_cx);
3967+
return casted->getBitWidthValue(*casted_ast_cx);
3968+
}
3969+
39573970
bool ZigClangFieldDecl_isAnonymousStructOrUnion(const ZigClangFieldDecl *field_decl) {
39583971
return reinterpret_cast<const clang::FieldDecl*>(field_decl)->isAnonymousStructOrUnion();
39593972
}

src/zig_clang.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,6 +1707,8 @@ ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangMacroDefinitionRecord_getSour
17071707
ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangMacroDefinitionRecord_getSourceRange_getEnd(const struct ZigClangMacroDefinitionRecord *);
17081708

17091709
ZIG_EXTERN_C bool ZigClangFieldDecl_isBitField(const struct ZigClangFieldDecl *);
1710+
ZIG_EXTERN_C bool ZigClangFieldDecl_isUnnamedBitField(const struct ZigClangFieldDecl *);
1711+
ZIG_EXTERN_C unsigned int ZigClangFieldDecl_getBitWidthValue(const struct ZigClangFieldDecl *, const struct ZigClangASTContext *);
17101712
ZIG_EXTERN_C bool ZigClangFieldDecl_isAnonymousStructOrUnion(const ZigClangFieldDecl *);
17111713
ZIG_EXTERN_C struct ZigClangQualType ZigClangFieldDecl_getType(const struct ZigClangFieldDecl *);
17121714
ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangFieldDecl_getLocation(const struct ZigClangFieldDecl *);

0 commit comments

Comments
 (0)