From 81851b3bdb9ac5b29adc373ddb3f511de2004d2b Mon Sep 17 00:00:00 2001 From: Anders Elfgren Date: Sun, 10 Mar 2024 06:41:09 +0100 Subject: [PATCH 1/3] Improved support for nested structs and unions. --- build.zig | 2 + src/Transpiler.zig | 124 ++++++++++++++---- test_cases/c023_cpp_nested_structs.zig | 71 ++++++++++ test_cases/c023_cpp_nested_structs_glue.cpp | 6 + .../include/c023_cpp_nested_structs.cpp | 11 ++ test_cases/include/c023_cpp_nested_structs.h | 105 +++++++++++++++ test_cases/tests.zig | 17 +++ 7 files changed, 309 insertions(+), 27 deletions(-) create mode 100644 test_cases/c023_cpp_nested_structs.zig create mode 100644 test_cases/c023_cpp_nested_structs_glue.cpp create mode 100644 test_cases/include/c023_cpp_nested_structs.cpp create mode 100644 test_cases/include/c023_cpp_nested_structs.h diff --git a/build.zig b/build.zig index adbbaa3..8640ae6 100644 --- a/build.zig +++ b/build.zig @@ -77,6 +77,7 @@ 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/c023_cpp_nested_structs.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 }); @@ -84,6 +85,7 @@ pub fn build(b: *std.Build) void { 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/c023_cpp_nested_structs_glue.cpp" }, .flags = cflags }); test_cases.linkLibrary(lib); const cpp_mod = b.addModule("cpp", .{ .root_source_file = .{ .path = "src/cpp.zig" } }); diff --git a/src/Transpiler.zig b/src/Transpiler.zig index 2b78c8a..2f5fbac 100644 --- a/src/Transpiler.zig +++ b/src/Transpiler.zig @@ -209,11 +209,15 @@ const NamespaceScope = struct { } }; +// ClassInfo is now stored in class_info both with its name as the key, +// and with it's line and column. const ClassInfo = struct { is_polymorphic: bool, + name: []const u8, }; allocator: Allocator, +arena: std.heap.ArenaAllocator, buffer: std.ArrayList(u8), out: std.ArrayList(u8).Writer, @@ -240,6 +244,7 @@ header: []const u8 = "", pub fn init(allocator: Allocator) Self { return Self{ .allocator = allocator, + .arena = std.heap.ArenaAllocator.init(allocator), .buffer = std.ArrayList(u8).init(allocator), .out = undefined, // can't be initialized because Self will be moved .c_buffer = std.ArrayList(u8).init(allocator), @@ -253,6 +258,7 @@ pub fn init(allocator: Allocator) Self { } pub fn deinit(self: *Self) void { + self.arena.deinit(); self.buffer.deinit(); self.c_buffer.deinit(); self.namespace.deinit(); @@ -545,19 +551,18 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void { self.nodes_visited += 1; const tag = value.object.get("tagUsed").?.string; - - // nested unamed strucs or unions are treated as implicit fields - var is_field = false; + const is_union = mem.eql(u8, tag, "union"); var is_generated_name = false; var name: []const u8 = undefined; if (value.object.get("name")) |v| { name = v.string; } else if (self.scope.tag == .class) { - // unamed struct, class or union inside a class definition should be treated as a field - is_field = true; is_generated_name = true; - name = try fmt.allocPrint(self.allocator, "__field{d}", .{self.scope.fields}); + name = try fmt.allocPrint(self.allocator, "__{s}{d}", .{ + if (is_union) "Union" else "Struct", + self.scope.fields, + }); self.scope.fields += 1; } else { // referenced by someone else @@ -594,11 +599,10 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void { try self.writeDocs(inner); - if (is_field) { - try self.out.print("{s}: extern {s} {{\n", .{ name, tag }); - } else { - try self.out.print("pub const {s} = extern struct {{\n", .{name}); - } + try self.out.print("pub const {s} = extern {s} {{\n", .{ + name, + if (is_union) "union" else "struct", + }); const v_bases = value.object.get("bases"); @@ -620,7 +624,22 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void { try self.out.print(" vtable: *const anyopaque,\n\n", .{}); } - _ = try self.class_info.put(name, .{ .is_polymorphic = is_polymorphic }); + _ = try self.class_info.put(name, .{ + .is_polymorphic = is_polymorphic, + .name = name, + }); + + // Double-storing class info by the line and col so we can look up it's name using that. + // Need to store this 'globally' so using an arena + // For anonymous structs and similar constructs, the corresponding FieldDecl comes *after* + // the CXXRecordDecl. + if (location(value)) |loc| { + const line_col_key = try fmt.allocPrint(self.arena.allocator(), "{d}:{d}", .{ loc.line, loc.col }); + _ = try self.class_info.put(line_col_key, .{ + .is_polymorphic = is_polymorphic, + .name = try fmt.allocPrint(self.arena.allocator(), "{s}", .{name}), + }); + } if (v_bases != null) { if (v_bases.?.array.items.len > 1) { @@ -656,20 +675,34 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void { var bitfield_struct_bits_remaining: u32 = 0; for (inner.?.array.items) |*item| { + const kind = item.object.getPtr("kind").?.string; + + // FieldDecls that are implicit shouldn't be skipped. This is things like + // anonymous structs. + const inner_is_field = mem.eql(u8, kind, "FieldDecl"); + var is_implicit = false; if (item.object.getPtr("isImplicit")) |implicit| { if (implicit.bool) { - self.nodes_visited += nodeCount(item); - continue; + is_implicit = true; + if (!inner_is_field) { + self.nodes_visited += nodeCount(item); + continue; + } } } - const kind = item.object.getPtr("kind").?.string; if (mem.eql(u8, kind, "FullComment")) { // skip - } else if (mem.eql(u8, kind, "FieldDecl")) { + } else if (inner_is_field) { self.nodes_visited += 1; - const field_name = item.object.getPtr("name").?.string; + const field_name = if (is_implicit) blk: { + const type_name = item.object.getPtr("type").?.object.get("qualType").?.string; + const field_type = if (mem.indexOf(u8, type_name, "union at") != null) "union_field" else if (mem.indexOf(u8, type_name, "struct at") != null) "struct_field" else "field"; + const field_name_tmp = try fmt.allocPrint(self.arena.allocator(), "__{s}{d}", .{ field_type, self.scope.fields }); + self.scope.fields += 1; + break :blk field_name_tmp; + } else item.object.getPtr("name").?.string; if (item.object.getPtr("isInvalid")) |invalid| { if (invalid.bool) { @@ -682,8 +715,6 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void { const item_type = try self.transpileType(typeQualifier(item).?); defer self.allocator.free(item_type); - try self.writeDocs(item_inner); - var bitfield_field_bits: u32 = 0; if (item.object.getPtr("isBitfield")) |is_bitfield| { @@ -738,6 +769,8 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void { bitfield_type_bytes_curr = null; } + try self.writeDocs(item_inner); + const field_type = switch (bitfield_type_bytes_curr != null) { true => blk: { bitfield_struct_bits_remaining -= bitfield_field_bits; @@ -778,8 +811,11 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void { // nested enums try self.visitEnumDecl(item); } else if (mem.eql(u8, kind, "CXXRecordDecl")) { - // nested stucts, classes and unions + // nested stucts, classes and unions, mustn't be intermixed with fields. + const out = self.out; + self.out = functions.writer(); try self.visitCXXRecordDecl(item); + self.out = out; } else if (mem.eql(u8, kind, "VarDecl")) { const out = self.out; self.out = functions.writer(); @@ -836,11 +872,7 @@ fn visitCXXRecordDecl(self: *Self, value: *const json.Value) !void { try self.endNamespace(parent_namespace); - if (is_field) { - try self.out.print("}},\n\n", .{}); - } else { - try self.out.print("}};\n\n", .{}); - } + try self.out.print("}};\n\n", .{}); } fn startBitfield(self: *Self, bitfield_group: u32, bitfield_type_bits: u32) !void { @@ -857,7 +889,7 @@ fn finalizeBitfield(self: *Self, bits_remaining: u32) !void { try self.out.print(" /// Padding added by c2z\n", .{}); try self.out.print(" _dummy_padding: u{d},\n", .{bits_remaining}); } - try self.out.print(" }},\n", .{}); + try self.out.print(" }},\n\n", .{}); } fn visitVarDecl(self: *Self, value: *const json.Value) !void { @@ -2824,6 +2856,25 @@ inline fn typeQualifier(value: *const json.Value) ?[]const u8 { return null; } +inline fn location(value: *const json.Value) ?struct { line: i64, col: i64 } { + if (value.object.getPtr("loc")) |loc| { + if (loc.object.getPtr("spellingLoc")) |spelling_loc| { + const line = spelling_loc.object.get("line").?.integer; + const col = spelling_loc.object.get("col").?.integer; + return .{ .line = line, .col = col }; + } else if (loc.object.getPtr("expansionLoc")) |expansion_loc| { + const line = expansion_loc.object.get("line").?.integer; + const col = expansion_loc.object.get("col").?.integer; + return .{ .line = line, .col = col }; + } + + const line = loc.object.get("line").?.integer; + const col = loc.object.get("col").?.integer; + return .{ .line = line, .col = col }; + } + return null; +} + inline fn resolveEnumVariantName(base: []const u8, variant: []const u8) []const u8 { return if (mem.startsWith(u8, variant, base)) variant[base.len..] else variant; } @@ -2903,6 +2954,11 @@ fn transpileType(self: *Self, tname: []const u8) ![]u8 { ttname = ttname["class ".len..]; } + // remove union from C style definition + if (mem.startsWith(u8, ttname, "union ")) { + ttname = ttname["union ".len..]; + } + const ch = ttname[ttname.len - 1]; if (ch == '*' or ch == '&') { // note: avoid c-style pointers `[*c]` when dealing with references types @@ -2934,6 +2990,20 @@ fn transpileType(self: *Self, tname: []const u8) ![]u8 { const inner = try self.transpileType(raw_name); defer self.allocator.free(inner); return try fmt.allocPrint(self.allocator, "{s}{s}{s}", .{ ptr, constness, inner }); + } else if (mem.indexOf(u8, ttname, "struct at ") != null or + mem.indexOf(u8, ttname, "union at ") != null) // or + // mem.indexOf(u8, ttname, "enum at ") != null) + { + // "qualType": "RootStruct::(anonymous union at bitfieldtest.h:4:5)" + // "qualType": "RootStruct::(anonymous struct at bitfieldtest.h:25:5)" + // "qualType": "struct (unnamed struct at header.h:4:5)" + var separator_index = mem.lastIndexOf(u8, ttname, ":").?; + const tmpname = ttname[0 .. separator_index - 1]; + separator_index = mem.lastIndexOf(u8, tmpname, ":").?; + ttname = ttname[separator_index + 1 ..]; + ttname = ttname[0 .. ttname.len - 1]; + const class = self.class_info.get(ttname).?; + ttname = class.name; } else if (mem.endsWith(u8, ttname, " *const")) { // NOTE: This can probably be improved to handle more cases, or maybe combined with the // above case. @@ -2961,7 +3031,7 @@ fn transpileType(self: *Self, tname: []const u8) ![]u8 { return try fmt.allocPrint(self.allocator, "?*const fn({s}) callconv(.C) {s} ", .{ args.items, tret }); } else { - log.err("unknow type `{s}`, falling back to `*anyopaque`", .{ttname}); + log.err("unknown type `{s}`, falling back to `*anyopaque`", .{ttname}); ttname = "*anyopaque"; } } else if (ch == '>') { diff --git a/test_cases/c023_cpp_nested_structs.zig b/test_cases/c023_cpp_nested_structs.zig new file mode 100644 index 0000000..8819d2c --- /dev/null +++ b/test_cases/c023_cpp_nested_structs.zig @@ -0,0 +1,71 @@ +// auto generated by c2z +const std = @import("std"); +//const cpp = @import("cpp"); + +pub const RootStruct = extern struct { + value_begin: c_int, + nested_struct_1a: __Struct0, + nested_struct_1b: __Struct0, + value_mid: c_int, + nested_struct_2b: NestedStruct2a, + nested_struct_3b: NestedStruct3a, + nested_struct_3c: NestedStruct3a, + __struct_field2: __Struct1, + value_end: c_int, + + pub const __Struct0 = extern struct { + m1: f32, + }; + + pub const NestedStruct2a = extern struct { + m2: f32, + }; + + pub const NestedStruct3a = extern struct { + m3: f32, + }; + + /// Fully anonymous 4 + pub const __Struct1 = extern struct { + m44: f32, + }; +}; + +extern fn _1_test_sizeof_RootStruct_() c_int; +pub const test_sizeof_RootStruct = _1_test_sizeof_RootStruct_; + +pub const RootUnion = extern struct { + value_begin: c_int, + nested_union_1a: __Union0, + nested_union_1b: __Union0, + value_mid: c_int, + nested_union_2b: NestedUnion2a, + nested_union_3b: NestedUnion3a, + nested_union_3c: NestedUnion3a, + __union_field2: __Union1, + value_end: c_int, + + pub const __Union0 = extern union { + iii1: c_int, + fff1: f32, + }; + + pub const NestedUnion2a = extern union { + iii2: c_int, + fff2: f32, + }; + + pub const NestedUnion3a = extern union { + iii3: c_int, + fff3: f32, + }; + + /// Fully anonymous 4 + pub const __Union1 = extern union { + iii4: c_int, + fff4: f32, + }; +}; + +extern fn _1_test_sizeof_RootUnion_() c_int; +pub const test_sizeof_RootUnion = _1_test_sizeof_RootUnion_; diff --git a/test_cases/c023_cpp_nested_structs_glue.cpp b/test_cases/c023_cpp_nested_structs_glue.cpp new file mode 100644 index 0000000..1f94845 --- /dev/null +++ b/test_cases/c023_cpp_nested_structs_glue.cpp @@ -0,0 +1,6 @@ +// auto generated by c2z +#include +#include "c023_cpp_nested_structs.h" + +extern "C" int _1_test_sizeof_RootStruct_() { return ::test_sizeof_RootStruct(); } +extern "C" int _1_test_sizeof_RootUnion_() { return ::test_sizeof_RootUnion(); } diff --git a/test_cases/include/c023_cpp_nested_structs.cpp b/test_cases/include/c023_cpp_nested_structs.cpp new file mode 100644 index 0000000..fe358f6 --- /dev/null +++ b/test_cases/include/c023_cpp_nested_structs.cpp @@ -0,0 +1,11 @@ +#include "c023_cpp_nested_structs.h" + +int test_sizeof_RootStruct() +{ + return (int)sizeof(RootStruct); +} + +int test_sizeof_RootUnion() +{ + return (int)sizeof(RootUnion); +} diff --git a/test_cases/include/c023_cpp_nested_structs.h b/test_cases/include/c023_cpp_nested_structs.h new file mode 100644 index 0000000..374ae54 --- /dev/null +++ b/test_cases/include/c023_cpp_nested_structs.h @@ -0,0 +1,105 @@ +class RootStruct +{ + int value_begin; + struct + { + float m1; + } nested_struct_1a, nested_struct_1b; + + class NestedStruct2a + { + float m2; + }; + + int value_mid; + + NestedStruct2a nested_struct_2b; + + struct NestedStruct3a + { + float m3; + } nested_struct_3b, nested_struct_3c; + + // Fully anonymous 4 + struct + { + float m44; + }; + + int value_end; +}; + +int test_sizeof_RootStruct(); + +class RootUnion +{ + int value_begin; + union + { + int iii1; + float fff1; + } nested_union_1a, nested_union_1b; + + union NestedUnion2a + { + int iii2; + float fff2; + }; + + int value_mid; + + NestedUnion2a nested_union_2b; + + union NestedUnion3a + { + int iii3; + float fff3; + } nested_union_3b, nested_union_3c; + + // Fully anonymous 4 + union + { + int iii4; + float fff4; + }; + int value_end; +}; + +int test_sizeof_RootUnion(); + +// TODO +// class RootEnum +// { +// int value_begin; +// enum +// { +// AAAA, +// ZZZZ +// } nested_enum_1a, +// nested_enum_1b; + +// enum NestedEnum2a +// { +// AAAA, +// ZZZZ +// }; + +// int value_mid; + +// NestedEnum2a nested_enum_2b; + +// enum NestedEnum3a +// { +// AAAA, +// ZZZZ +// } nested_enum_3b, +// nested_enum_3c; + +// // Fully anonymous 4 +// enum +// { +// AAAA, +// ZZZZ +// }; +// int value_end; +// }; diff --git a/test_cases/tests.zig b/test_cases/tests.zig index 92433e7..341b609 100644 --- a/test_cases/tests.zig +++ b/test_cases/tests.zig @@ -113,3 +113,20 @@ test "cpp_string" { // //buffer = cpp.String.init(.{}); // this leaks memory // } } + +test "cpp_nested_structs" { + const fii = @import("c023_cpp_nested_structs.zig"); + + const zig_size_struct = @as(c_int, @sizeOf(fii.RootStruct)); + const cpp_size_struct = fii.test_sizeof_RootStruct(); + try expectEqual(zig_size_struct, cpp_size_struct); + + const zig_size_union = @as(c_int, @sizeOf(fii.RootUnion)); + const cpp_size_union = fii.test_sizeof_RootUnion(); + try expectEqual(zig_size_union, cpp_size_union); + + // TODO + // const zig_size_enum = @as(c_int, @sizeOf(fii.RootEnum)); + // const cpp_size_enum = fii.test_sizeof_RootEnum(); + // try expectEqual(zig_size_enum, cpp_size_enum); +} From 121b325155a4b4d8f3a2af377be9dac1dff19fb0 Mon Sep 17 00:00:00 2001 From: Anders Elfgren Date: Mon, 11 Mar 2024 09:42:20 +0100 Subject: [PATCH 2/3] Added missing brace. --- test_cases/tests.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/test_cases/tests.zig b/test_cases/tests.zig index 92c4c6c..cac4aae 100644 --- a/test_cases/tests.zig +++ b/test_cases/tests.zig @@ -112,6 +112,7 @@ test "cpp_string" { // buffer.deinit(); // this doesn't // //buffer = cpp.String.init(.{}); // this leaks memory // } +} test "cpp_bitfields" { const fii = @import("c024_cpp_bitfields.zig"); From e7ef478cba8b97999743187b2d410852fae96c66 Mon Sep 17 00:00:00 2001 From: Anders Elfgren Date: Mon, 18 Mar 2024 08:42:11 +0100 Subject: [PATCH 3/3] Added tests to build (lost in merge?) --- build.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.zig b/build.zig index fbcf738..ae6d509 100644 --- a/build.zig +++ b/build.zig @@ -77,7 +77,8 @@ 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/c023_cpp_nested_structs.cpp" }, .flags = cflags }); // glue + lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/include/c023_cpp_nested_structs.cpp" }, .flags = cflags }); + lib.addCSourceFile(.{ .file = .{ .path = "./test_cases/include/c024_cpp_bitfields.cpp" }, .flags = cflags }); //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 }); @@ -85,6 +86,7 @@ pub fn build(b: *std.Build) void { 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/c023_cpp_nested_structs_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" } });