Skip to content

Commit

Permalink
Merge pull request #27 from Srekel/srekel-bitfields-deluxe
Browse files Browse the repository at this point in the history
Added better support for bitfields.
  • Loading branch information
lassade authored Mar 14, 2024
2 parents 8f35b5a + 26c5b37 commit ba3ab48
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 33 deletions.
2 changes: 2 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,15 @@ pub fn build(b: *std.Build) void {
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/include/c005_inheritance.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/include/c013_cpp_vector.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/include/c022_cpp_string.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/include/c024_cpp_bitfields.cpp" }, .flags = cflags });
// glue
//lib.addCSourceFile("./test_cases/c001_c_structs_glue.cpp", cflags);
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/c005_inheritance_glue.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/c009_enum_flags_glue.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/c011_index_this_glue.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/c013_cpp_vector_glue.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/c022_cpp_string_glue.cpp" }, .flags = cflags });
lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/c024_cpp_bitfields_glue.cpp" }, .flags = cflags });
test_cases.linkLibrary(lib);

const cpp_mod = b.addModule("cpp", .{ .root_source_file = .{ .path = "src/cpp.zig" } });
Expand Down
166 changes: 133 additions & 33 deletions src/Transpiler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,58 @@ const PrimitivesTypeLUT = std.ComptimeStringMap([]const u8, .{
.{ "std::string", "cpp.String" },
});

const TypeToByteSizeLUT = std.ComptimeStringMap(u32, .{
.{ "bool", @sizeOf(bool) },
.{ "c_int", @sizeOf(c_int) },
.{ "c_long", @sizeOf(c_long) },
.{ "c_longdouble", @sizeOf(c_longdouble) },
.{ "c_longlong", @sizeOf(c_longlong) },
.{ "c_short", @sizeOf(c_short) },
.{ "c_uint", @sizeOf(c_uint) },
.{ "c_ulong", @sizeOf(c_ulong) },
.{ "c_ulonglong", @sizeOf(c_ulonglong) },
.{ "c_ushort", @sizeOf(c_ushort) },
.{ "f32", @sizeOf(f32) },
.{ "f64", @sizeOf(f64) },
.{ "i128", @sizeOf(i128) },
.{ "i16", @sizeOf(i16) },
.{ "i32", @sizeOf(i32) },
.{ "i64", @sizeOf(i64) },
.{ "i8", @sizeOf(i8) },
.{ "isize", @sizeOf(isize) },
.{ "u128", @sizeOf(u128) },
.{ "u16", @sizeOf(u16) },
.{ "u32", @sizeOf(u32) },
.{ "u64", @sizeOf(u64) },
.{ "u8", @sizeOf(u8) },
.{ "usize", @sizeOf(usize) },
});

const TypeToSignedLUT = std.ComptimeStringMap(bool, .{
.{ "bool", false },
.{ "c_int", true },
.{ "c_long", true },
.{ "c_longdouble", true },
.{ "c_longlong", true },
.{ "c_short", true },
.{ "c_uint", false },
.{ "c_ulong", false },
.{ "c_ulonglong", false },
.{ "c_ushort", false },
.{ "i128", true },
.{ "i16", true },
.{ "i32", true },
.{ "i64", true },
.{ "i8", true },
.{ "isize", true },
.{ "u128", false },
.{ "u16", false },
.{ "u32", false },
.{ "u64", false },
.{ "u8", false },
.{ "usize", false },
});

const ScopeTag = enum {
root,
class,
Expand Down Expand Up @@ -598,9 +650,10 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void {
}
try self.namespace.full_path.appendSlice(name);

var is_in_bitfield = false;
var bitfield_type_bytes_curr: ?u32 = null;
var bitfield_signed_curr = false;
var bitfield_group: u32 = 0;
var bitfield_struct_size_remaining: u32 = 0;
var bitfield_struct_bits_remaining: u32 = 0;

for (inner.?.array.items) |*item| {
if (item.object.getPtr("isImplicit")) |implicit| {
Expand All @@ -625,39 +678,72 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void {
}
}

const item_inner = item.object.getPtr("inner");
const item_type = try self.transpileType(typeQualifier(item).?);
defer self.allocator.free(item_type);
const bitfield_signed = if (TypeToSignedLUT.has(item_type)) TypeToSignedLUT.get(item_type).? else false;

try self.writeDocs(item_inner);

var bitfield_field_bits: u32 = 0;

if (item.object.getPtr("isBitfield")) |is_bitfield| {
if (!is_in_bitfield and is_bitfield.bool) {
is_in_bitfield = true;
bitfield_group += 1;
if (!is_bitfield.bool) {
// Not sure when this would be true.
std.debug.assert(false);
}

// TODO: Calculate this.
bitfield_struct_size_remaining = 32;
const inner_value_index = 0; // Not sure if this is always 0.
const inner_value_elem = item_inner.?.array.items[inner_value_index];
const bitfield_field_bits_str = inner_value_elem.object.getPtr("value").?.string;
bitfield_field_bits = try std.fmt.parseInt(u32, bitfield_field_bits_str, 10);

const bitfield_type_bytes = if (TypeToByteSizeLUT.has(item_type)) TypeToByteSizeLUT.get(item_type).? else 4;
const bitfield_type_bits = bitfield_type_bytes * 8;

// TODO: Need to handle 0-length (unnamed) bitfields (used for re-aligning next field)
const bitfield_type_size_prev = if (bitfield_type_bytes_curr == null) 0 else bitfield_type_bytes_curr.?;
const bitfield_type_size_changed = bitfield_type_size_prev != bitfield_type_bytes;
const bitfield_sign_changed = false; // Actually fine to mix I think? bitfield_signed_curr != bitfield_signed;
if (bitfield_type_size_changed or bitfield_sign_changed) {
// A new bitfield
// - or -
// Underlying type's size changed, need to start a new bitfield

// NOTE: C's behavior of padding when the type and signedness has changed seems tricky and perhaps
// not even consistent across platforms/compilers so leaving a warning when it's noticed.

if (bitfield_type_bytes_curr != null) {
if (bitfield_struct_bits_remaining > 0) {
try self.out.print(" /// C2Z WARNING: This perhaps shouldn't be padded in this way! \n", .{});
}

try self.finalizeBitfield(bitfield_struct_bits_remaining);
}

try self.out.print(" field_{d}: packed struct(u{d}) {{\n", .{ bitfield_group, bitfield_struct_size_remaining });
} else if (is_in_bitfield and is_bitfield.bool) {
// pass
} else if (is_in_bitfield) {
is_in_bitfield = false;
try self.finalizeBitfield(bitfield_struct_size_remaining);
bitfield_type_bytes_curr = bitfield_type_bytes;
bitfield_signed_curr = bitfield_signed;

bitfield_group += 1;
try self.startBitfield(bitfield_group, bitfield_type_bits);
bitfield_struct_bits_remaining = bitfield_type_bits;
} else if (bitfield_struct_bits_remaining < bitfield_field_bits) {
// Existing bitfield but new field doesn't fit
try self.finalizeBitfield(bitfield_struct_bits_remaining);

bitfield_group += 1;
try self.startBitfield(bitfield_group, bitfield_type_bits);
bitfield_struct_bits_remaining = bitfield_type_bits;
}
} else if (is_in_bitfield) {
is_in_bitfield = false;
try self.finalizeBitfield(bitfield_struct_size_remaining);
} else if (bitfield_type_bytes_curr != null) {
try self.finalizeBitfield(bitfield_struct_bits_remaining);
bitfield_type_bytes_curr = null;
}

const item_inner = item.object.getPtr("inner");
try self.writeDocs(item_inner);

const field_type = switch (is_in_bitfield) {
const field_type = switch (bitfield_type_bytes_curr != null) {
true => blk: {
const inner_value_index = 0; // Not sure if this is always 0.
const inner_value_elem = item_inner.?.array.items[inner_value_index];
const bitfield_size_str = inner_value_elem.object.getPtr("value").?.string;
const bitfield_size = try std.fmt.parseInt(u32, bitfield_size_str, 10);
// TODO: Handle this.
std.debug.assert(bitfield_struct_size_remaining >= bitfield_size);
bitfield_struct_size_remaining -= bitfield_size;
break :blk try fmt.allocPrint(self.allocator, "u{d}", .{bitfield_size});
bitfield_struct_bits_remaining -= bitfield_field_bits;
break :blk try self.addBitfieldField(bitfield_signed, bitfield_field_bits);
},
false => try self.transpileType(typeQualifier(item).?),
};
Expand All @@ -666,7 +752,7 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void {
try self.out.print(" {s}: {s}", .{ field_name, field_type });

// field default value
if (item_inner != null and !is_in_bitfield) {
if (item_inner != null and bitfield_type_bytes_curr == null) {
var value_exp = std.ArrayList(u8).init(self.allocator);
defer value_exp.deinit();

Expand All @@ -682,7 +768,11 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void {
}
}

try self.out.print(",\n", .{});
if (bitfield_type_bytes_curr != null) {
try self.out.print(", // {d} bits\n", .{bitfield_type_bytes_curr.? * 8 - bitfield_struct_bits_remaining});
} else {
try self.out.print(",\n", .{});
}
} else if (mem.eql(u8, kind, "CXXMethodDecl")) {
if (!self.public) continue;

Expand Down Expand Up @@ -740,9 +830,9 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void {
}
}

if (is_in_bitfield) {
is_in_bitfield = false;
try self.finalizeBitfield(bitfield_struct_size_remaining);
if (bitfield_type_bytes_curr != null) {
bitfield_type_bytes_curr = null;
try self.finalizeBitfield(bitfield_struct_bits_remaining);
}

// declarations must be after fields
Expand All @@ -759,6 +849,16 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void {
}
}

fn startBitfield(self: *Self, bitfield_group: u32, bitfield_type_bits: u32) !void {
try self.out.print(" bitfield_{d}: packed struct(u{d}) {{\n", .{ bitfield_group, bitfield_type_bits });
try self.out.print(" // NOTE: Bitfield generation not guaranteed to work on all platforms, use with caution. \n\n", .{});
}

fn addBitfieldField(self: *Self, is_signed: bool, bitfield_field_bits: u32) ![]u8 {
const signed_str = if (is_signed) "i" else "u";
return try fmt.allocPrint(self.allocator, "{s}{d}", .{ signed_str, bitfield_field_bits });
}

fn finalizeBitfield(self: *Self, bits_remaining: u32) !void {
if (bits_remaining > 0) {
try self.out.print(" /// Padding added by c2z\n", .{});
Expand Down
45 changes: 45 additions & 0 deletions test_cases/c024_cpp_bitfields.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// auto generated by c2z
const std = @import("std");
//const cpp = @import("cpp");

pub const Bitfields = extern struct {
bitfield_1: packed struct(u64) {
bitfield1: u10, // 10 bits
bitfield2: u10, // 20 bits
/// C2Z WARNING: This perhaps shouldn't be padded in this way!
/// Padding added by c2z
_dummy_padding: u44,
},
bitfield_2: packed struct(u32) {
bitfield3: u5, // 5 bits
bitfield4: i5, // 10 bits
/// C2Z WARNING: This perhaps shouldn't be padded in this way!
/// Padding added by c2z
_dummy_padding: u22,
},
bitfield_3: packed struct(u8) {
bitfield5: u2, // 2 bits
bitfield6: u2, // 4 bits
bitfield7: u2, // 6 bits
/// C2Z WARNING: This perhaps shouldn't be padded in this way!
/// Padding added by c2z
_dummy_padding: u2,
},
bitfield_4: packed struct(u32) {
bitfield8: i31, // 31 bits
/// C2Z WARNING: This perhaps shouldn't be padded in this way!
/// Padding added by c2z
_dummy_padding: u1,
},
bitfield_5: packed struct(u64) {
bitfield9: i30, // 30 bits
/// TODO: Add test of 0-length bitfield here
/// long long : 0;
bitfield11: i30, // 60 bits
/// Padding added by c2z
_dummy_padding: u4,
},
};

extern fn _1_size_of_Bitfields_() c_int;
pub const size_of_Bitfields = _1_size_of_Bitfields_;
5 changes: 5 additions & 0 deletions test_cases/c024_cpp_bitfields_glue.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// auto generated by c2z
#include <new>
#include "c024_cpp_bitfields.h"

extern "C" int _1_size_of_Bitfields_() { return ::size_of_Bitfields(); }
5 changes: 5 additions & 0 deletions test_cases/include/c024_cpp_bitfields.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include "c024_cpp_bitfields.h"

int size_of_Bitfields(){
return sizeof(Bitfields);
}
17 changes: 17 additions & 0 deletions test_cases/include/c024_cpp_bitfields.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
typedef struct Bitfields
{
unsigned long long bitfield1 : 10;
unsigned long long bitfield2 : 10;
unsigned long bitfield3 : 5;
signed long bitfield4 : 5;
bool bitfield5 : 2;
char bitfield6 : 2;
unsigned char bitfield7 : 2;
int bitfield8 : 31;
long long bitfield9 : 30;
// TODO: Add test of 0-length bitfield here
// long long : 0;
long long bitfield11 : 30;
} Bitfields;

int size_of_Bitfields();
12 changes: 12 additions & 0 deletions test_cases/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,15 @@ test "cpp_string" {
// //buffer = cpp.String.init(.{}); // this leaks memory
// }
}

test "cpp_bitfields" {
const fii = @import("c024_cpp_bitfields.zig");

if (@import("builtin").os.tag == .windows) {
// TODO: Improved support for bitfields.
const zig_size_bitfields = @as(c_int, @sizeOf(fii.Bitfields));
const cpp_size_bitfields = fii.size_of_Bitfields();
try expectEqual(zig_size_bitfields, cpp_size_bitfields);
}
}

0 comments on commit ba3ab48

Please sign in to comment.