Skip to content

Commit

Permalink
translate-c: initial support for bitfields
Browse files Browse the repository at this point in the history
  • Loading branch information
thislight committed Aug 1, 2024
1 parent 624fa85 commit bc1a098
Show file tree
Hide file tree
Showing 13 changed files with 424 additions and 21 deletions.
18 changes: 18 additions & 0 deletions lib/compiler/aro_translate_c/ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
};
}

Expand Down Expand Up @@ -698,6 +702,14 @@ pub const Payload = struct {
},
};

pub const EmulateBitfieldStruct = struct {
base: Payload,
data: struct {
record: Node,
backings: Node,
},
};

pub const StringSlice = struct {
base: Payload,
data: struct {
Expand Down Expand Up @@ -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.record, payload.backings });
},
.vector => {
const payload = node.castTag(.vector).?.data;
return renderBuiltinCall(c, "@Vector", &.{ payload.lhs, payload.rhs });
Expand Down Expand Up @@ -2387,6 +2404,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,
Expand Down
165 changes: 165 additions & 0 deletions lib/std/zig/c_translation.zig
Original file line number Diff line number Diff line change
Expand Up @@ -670,3 +670,168 @@ 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 ABI starts a new storage unit after a unnamed zero-bit width bit field.
/// Some ABI ignores that, sets to false.
unnamed_void_boundary: bool,

fn fromTarget(target: std.Target) ?BitfieldEmulation {
if (target.cpu.arch.isX86() and target.os.tag == .linux) {
return .{
.reverse_bits = false,
.unnamed_void_boundary = true,
};
}
return null;
}
};

/// 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.
pub fn EmulateBitfieldStruct(comptime T: type, comptime l: anytype) type {
const cfg = BitfieldEmulation.fromTarget(builtin.target) orelse return opaque {};
const inf = infT: {
const typeinf = @typeInfo(T);
if (typeinf != .Struct or typeinf.Struct.layout != .@"packed") {
@compileError("unsupported type for bitfields, zig translate-c might have an error");
}
break :infT typeinf.Struct;
};

const linf = infL: {
const LType = @TypeOf(l);
const typeInf = @typeInfo(LType);
if (typeInf != .Struct or !typeInf.Struct.is_tuple) {
@compileError("the second arg must be a tuple of type");
}
break :infL typeInf.Struct;
};

comptime {
// worst case: every after field needs a padding field
var buf = std.BoundedArray(
std.builtin.Type.StructField,
linf.fields.len * 2,
).init(0) catch unreachable;
var units = std.BoundedArray(
comptime_int,
linf.fields.len,
).init(0) catch unreachable;
var lastType: ?type = null;
var leftBitWidth = 0;
var padFieldCount = 0;

for (linf.fields, inf.fields) |bf, f| {
const K = @field(l, bf.name);
const kInf = @typeInfo(K);
if (kInf != .Int) {
@compileError("expects integer type");
}
const fInf = @typeInfo(f.type).Int;

if (f.type == void) {
if (!cfg.unnamed_void_boundary) continue;
if (leftBitWidth > 0) {
const bits = leftBitWidth - f.alignment;
if (bits < 0) {
@compileError("impossible alignment since it is larger than the rest bits");
}
buf.append(.{
.type = @Type(.{ .Int = .{
.bits = bits,
.signedness = .unsigned,
} }),
.alignment = f.alignment,
.default_value = f.default_value,
.is_comptime = f.is_comptime,
.name = f.name,
}) catch unreachable;
units.append(0) catch unreachable;
lastType = null;
leftBitWidth = 0;
}
continue;
}

// needs padding?
const allocatedBits = fInf.bits + f.alignment;
if (leftBitWidth < allocatedBits) {
// sets padding
if (leftBitWidth > 0) {
const typ = @Type(.{ .Int = .{
.bits = leftBitWidth,
.signedness = .unsigned,
} });
buf.append(.{
.alignment = 0,
.type = typ,
.default_value = &std.mem.zeroes(typ),
.is_comptime = false,
.name = std.fmt.comptimePrint(" pad_{}", .{padFieldCount}),
}) catch unreachable;
padFieldCount += 1;
const i = units.get(units.len);
units.set(units.len, i + 1);
}
lastType = K;
leftBitWidth = kInf.Int.bits;
units.append(0) catch unreachable;
}
leftBitWidth -= fInf.bits;
buf.append(f) catch unreachable;
const i = units.get(units.len - 1);
units.set(units.len - 1, i + 1);
}

if (leftBitWidth > 0) {
const typ = @Type(.{ .Int = .{
.bits = leftBitWidth,
.signedness = .unsigned,
} });
buf.append(.{
.alignment = 0,
.type = typ,
.default_value = &std.mem.zeroes(typ),
.is_comptime = false,
.name = std.fmt.comptimePrint(" pad_{}", .{padFieldCount}),
}) catch unreachable;
padFieldCount += 1;
const i = units.get(units.len - 1);
units.set(units.len - 1, i + 1);
}

if (cfg.reverse_bits) {
var offset = 0;
for (units.constSlice()) |i| {
const nextOffset = offset + i;
std.mem.reverse(std.builtin.Type.StructField, buf.buffer[offset..nextOffset]);
offset = nextOffset;
}
}

const backingInteger = bint: {
var bits = 0;
for (buf.constSlice()) |field| {
bits += field.alignment + @bitSizeOf(field.type);
}
break :bint @Type(.{ .Int = .{
.signedness = .unsigned,
.bits = bits,
} });
};

return @Type(.{ .Struct = .{
.backing_integer = backingInteger,
.decls = inf.decls,
.fields = buf.buffer[0..buf.len],
.layout = inf.layout,
.is_tuple = inf.is_tuple,
} });
}
}
6 changes: 6 additions & 0 deletions src/clang.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
47 changes: 41 additions & 6 deletions src/translate_c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -923,22 +923,28 @@ 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();
const end_it = record_def.field_end();
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();
// 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;
Expand All @@ -961,7 +967,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 });
Expand All @@ -970,6 +977,15 @@ 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 field_otype;

const alignment = if (flexible_field != null and field_decl.getFieldIndex() == 0)
@as(c_uint, @intCast(record_alignment))
else
Expand All @@ -995,17 +1011,36 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD
});
}

const is_packed = bitfield_count > 0;

if (is_packed and bitfield_count != fields.items.len) {
try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl.getCanonicalDecl()), {});
try warn(c, scope, record_loc, "{s} demoted to opaque type - has bitfields mixed with regular fields", .{container_kind_name});
break :blk Tag.opaque_literal.init();
}

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 (is_packed) .@"packed" else .@"extern",
.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);

if (bitfield_count > 0) {
const backing_tuple = try Tag.tuple.create(c.arena, try c.arena.dupe(ast.Node, backings.items));
const emulate_call = try Tag.helpers_emulate_bitfield_struct.create(c.arena, .{
.record = node,
.backings = backing_tuple,
});
break :blk emulate_call;
}
break :blk node;
};

const payload = try c.arena.create(ast.Payload.SimpleVarDecl);
Expand Down
13 changes: 13 additions & 0 deletions src/zig_clang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
* 3. Prevent C++ from infecting the rest of the project.
*/
#include "zig_clang.h"
#include <clang/AST/ASTContext.h>
#include <clang/AST/Decl.h>

#if __GNUC__ >= 8
#pragma GCC diagnostic push
Expand Down Expand Up @@ -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<const clang::FieldDecl *>(self);
return casted->isUnnamedBitfield();
}

unsigned int ZigClangFieldDecl_getBitWidthValue(const struct ZigClangFieldDecl *self, const struct ZigClangASTContext *ast_cx){
auto casted = reinterpret_cast<const clang::FieldDecl *>(self);
auto casted_ast_cx = reinterpret_cast<const clang::ASTContext *>(ast_cx);
return casted->getBitWidthValue(*casted_ast_cx);
}

bool ZigClangFieldDecl_isAnonymousStructOrUnion(const ZigClangFieldDecl *field_decl) {
return reinterpret_cast<const clang::FieldDecl*>(field_decl)->isAnonymousStructOrUnion();
}
Expand Down
2 changes: 2 additions & 0 deletions src/zig_clang.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);
Expand Down
Loading

0 comments on commit bc1a098

Please sign in to comment.