Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added better support for bitfields. #27

Merged
merged 7 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}

Loading