//! Holds `struct_layout`'s profiles to a C compiler (NFR-3). //! //! For each profile, writes one C file asserting, with `_Static_assert`, what the //! engine computed: the size and in-struct alignment of every scalar kind, then the //! size, alignment, and every member's offset and size for each test struct below. //! `build.zig` compiles each file for every clang target the profile claims to //! reproduce, so a wrong table entry or a wrong layout rule fails `zig build test` //! with the struct and the claim in the message. Nothing beyond Zig is needed: clang //! is the one Zig ships. //! //! The test structs are C, read by the engine's own parser, so the compiler and the //! engine see the same text. //! //! Usage: struct-oracle [ ...] //! Every profile in the engine must be named exactly once, so a profile cannot be //! added without being checked. const std = @import("std"); const engine = @import("engine"); const struct_layout = engine.struct_layout; const CStructParser = engine.CStructParser; const Primitive = struct_layout.Primitive; const Case = struct { /// C that the engine does not read: declarations a test struct refers to. prelude: []const u8 = "", /// One struct definition, named, as the engine parses it. source: []const u8, }; /// Each one exists to pin a rule. Any `push` a case makes it also pops: clang warns /// about an unterminated one, and the generator resets the pack after every case /// with `#pragma pack()` so a plain `pack(N)` cannot leak into the next. const cases = [_]Case{ // The design document's example: one byte of interior padding. .{ .source = "struct spec { u32 x; u8 flags; u16 id; u64 ts; };" }, // Tail padding. .{ .source = "struct tail { uint64_t a; uint8_t b; };" }, // The data model: what `long`, pointers and `wchar_t` are. .{ .source = "struct model { char c; long l; void *p; wchar_t w; size_t n; char e; };" }, // Where 8-byte types align. .{ .source = "struct doubles { char c; double d; char e; long long ll; char f; };" }, // Alignment rising and falling through a struct. .{ .source = "struct mixed { char a; short b; char c; int d; char e; long long f; char g; };" }, // Every spelling the parser accepts maps to the kind the compiler gives it. .{ .source = \\struct spellings { \\ signed char sc; unsigned char uc; char c; _Bool b; bool b2; \\ short s; unsigned short us; short int si; int i; unsigned u; signed sg; \\ long l; unsigned long ul; long int li; long long ll; unsigned long long int ull; \\ float f; double d; \\ int8_t i8_; uint8_t u8_; int16_t i16_; uint16_t u16_; int32_t i32_; uint32_t u32_; \\ int64_t i64_; uint64_t u64_; \\ intptr_t ip; uintptr_t up; size_t sz; ssize_t ssz; ptrdiff_t pd; \\ wchar_t wc; char16_t c16; char32_t c32; \\ u8 s1; i8 s2; u16 s3; i16 s4; u32 s5; i32 s6; u64 s7; i64 s8; \\ f32 s9; f64 s10; usize s11; isize s12; ptr s13; \\ const volatile int cv; \\}; }, // Sixteen-byte integers, on the profiles that have them. .{ .source = \\struct wide { \\ char c; __int128 big; char d; unsigned __int128 ubig; \\ __int128_t t; __uint128_t ut; u128 x; i128 y; \\}; }, // Arrays, including C's octal and hex lengths. .{ .source = "struct arrays { char tag[3]; int grid[2][3]; short s[0x4]; char oct[010]; double d[1]; };" }, // A flexible array member: no size, but it aligns, and the struct rounds up. .{ .source = "struct flex { short len; char c; double data[]; };" }, .{ .source = "struct flex_tail { int n; char c; char data[]; };" }, // Pointers of every shape. .{ .prelude = "enum color { red, green };", .source = \\struct ptrs { \\ char c; const char *name, **more; void (*cb)(int); \\ void (*handlers[3])(void); char (*row)[16]; struct ptrs *next; \\ struct elsewhere *opaque; enum color k; char *const *const pp; \\}; }, // A typedef'd anonymous struct. .{ .source = "typedef struct { char c; double d; } typedef_s;" }, // `packed` on the struct. .{ .source = "struct __attribute__((packed)) packed_s { char c; int i; double d; short s; };" }, // ... with a member that asks for alignment anyway. .{ .source = "struct __attribute__((packed)) packed_aligned { char c; int i __attribute__((aligned(4))); short s; };" }, // `packed` on one member. .{ .source = "struct packed_member { char c; int i __attribute__((packed)); short s; };" }, // `alignas` and `aligned` on members. .{ .source = "struct aligned_member { char c; alignas(16) int i; _Alignas(8) char d; };" }, .{ .source = "struct attr_aligned { char c; int i __attribute__((aligned(8))); char d; };" }, // An `aligned` smaller than the natural alignment. .{ .source = "struct aligned_lower { char c; double d __attribute__((aligned(2))); };" }, // `aligned` on the struct. .{ .source = "struct __attribute__((aligned(32))) aligned_struct { int a; char b; };" }, .{ .source = "struct trailing_aligned { int a; char b; } __attribute__((aligned(16)));" }, // `#pragma pack`. .{ .source = "#pragma pack(push, 1)\nstruct pack1 { char c; int i; double d; };\n#pragma pack(pop)" }, .{ .source = "#pragma pack(2)\nstruct pack2 { char c; double d; short s; char e; };" }, .{ .source = "#pragma pack(4)\nstruct pack4 { char c; double d; long long ll; };" }, .{ .source = "#pragma pack(16)\nstruct pack16 { char c; double d; };" }, // How pack meets the attributes: the interactions most worth checking. .{ .source = "#pragma pack(push, 2)\nstruct pack_attr_aligned { char c; int i __attribute__((aligned(8))); };\n#pragma pack(pop)" }, .{ .source = "#pragma pack(push, 2)\nstruct pack_alignas { char c; alignas(8) int i; };\n#pragma pack(pop)" }, .{ .source = "#pragma pack(push, 4)\nstruct __attribute__((packed)) pack_packed { char c; double d; };\n#pragma pack(pop)" }, .{ .source = "#pragma pack(push, 1)\nstruct __attribute__((aligned(8))) pack_struct_aligned { char c; int i; };\n#pragma pack(pop)" }, .{ .source = "#pragma pack(push, 2)\nstruct pack_flex { char c; double data[]; };\n#pragma pack(pop)" }, }; pub fn main(init: std.process.Init) !void { const allocator = init.arena.allocator(); const io = init.io; const args = try init.minimal.args.toSlice(allocator); if (args.len < 3 or (args.len - 1) % 2 != 0) { std.log.err("usage: struct-oracle [...]", .{}); return error.BadArguments; } var seen = [_]bool{false} ** struct_layout.abis.len; var i: usize = 1; while (i < args.len) : (i += 2) { const index = for (struct_layout.abis, 0..) |abi, n| { if (std.mem.eql(u8, abi.id, args[i])) break n; } else { std.log.err("no profile named '{s}'", .{args[i]}); return error.UnknownAbi; }; if (seen[index]) { std.log.err("'{s}' named twice", .{args[i]}); return error.DuplicateAbi; } seen[index] = true; const text = try generate(allocator, &struct_layout.abis[index]); try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = args[i + 1], .data = text }); } for (struct_layout.abis, seen) |abi, checked| { if (!checked) { std.log.err("profile '{s}' has no compiler target in build.zig", .{abi.id}); return error.UncheckedAbi; } } } /// The C spelling of each kind, for the scalar probes. const spelling: std.EnumArray(Primitive, []const u8) = .init(.{ .char = "char", .bool = "_Bool", .short = "short", .int = "int", .long = "long", .long_long = "long long", .int128 = "__int128", .float = "float", .double = "double", .pointer = "void *", .wchar = "wchar_t", .enumeration = "enum probe_enum", }); const prelude = \\#include \\#include \\#include \\#include \\ \\/* What the engine's parser accepts that C does not define for itself. */ \\typedef __CHAR16_TYPE__ char16_t; \\typedef __CHAR32_TYPE__ char32_t; \\typedef __PTRDIFF_TYPE__ ssize_t; \\typedef uint8_t u8; \\typedef int8_t i8; \\typedef uint16_t u16; \\typedef int16_t i16; \\typedef uint32_t u32; \\typedef int32_t i32; \\typedef uint64_t u64; \\typedef int64_t i64; \\typedef float f32; \\typedef double f64; \\typedef size_t usize; \\typedef ptrdiff_t isize; \\typedef void *ptr; \\#ifdef __SIZEOF_INT128__ \\typedef unsigned __int128 u128; \\typedef __int128 i128; \\#endif \\ \\enum probe_enum { probe_enum_value }; \\ ; fn generate(allocator: std.mem.Allocator, abi: *const struct_layout.Abi) ![]u8 { var out: std.Io.Writer.Allocating = .init(allocator); const w = &out.writer; try w.print( \\/* Generated by engine/test/struct_oracle.zig. Every assertion is a claim the \\ * engine makes about the {s} profile ({s}); a failure here means \\ * the engine disagrees with the compiler. */ \\ , .{ abi.id, abi.label }); try w.writeAll(prelude); // Every scalar kind: its size, and its alignment inside a struct (which is not // `_Alignof`'s answer on i386, where a double aligns to 8 alone and 4 in a struct). try w.writeAll("/* Scalars */\n"); for (std.enums.values(Primitive)) |p| { const shape = abi.shapes.get(p) orelse { if (p != .int128) return error.ProbeMissing; try w.print( \\#ifdef __SIZEOF_INT128__ \\#error "{s}: the engine says there is no __int128, and this target has one" \\#endif \\ , .{abi.id}); continue; }; if (p == .int128) try w.print( \\#ifndef __SIZEOF_INT128__ \\#error "{s}: the engine says there is an __int128, and this target has none" \\#endif \\ , .{abi.id}); const c_type = spelling.get(p); try w.print( \\struct probe_{t} {{ char c; {s} v; }}; \\_Static_assert(sizeof({s}) == {d}, "{s}: the engine says {s} is {d} bytes"); \\_Static_assert(__builtin_offsetof(struct probe_{t}, v) == {d}, "{s}: the engine says {s} aligns to {d} in a struct"); \\ , .{ p, c_type, c_type, shape.size, abi.id, c_type, shape.size, p, shape.alignment, abi.id, c_type, shape.alignment, }); } for (cases) |case| try writeCase(allocator, w, abi, case); return out.toOwnedSlice(); } fn writeCase(allocator: std.mem.Allocator, w: *std.Io.Writer, abi: *const struct_layout.Abi, case: Case) !void { var diag: CStructParser.Diagnostic = .{}; var parsed = CStructParser.parse(allocator, case.source, &diag) catch |err| { const at = CStructParser.locate(case.source, diag.span.start); std.log.err("a test struct does not parse ({t}, line {d}, column {d}):\n{s}", .{ err, at.line, at.column, case.source, }); return err; }; defer parsed.deinit(); const def = parsed.definition; const name = def.name orelse return error.UnnamedCase; var ref_buf: [64]u8 = undefined; const ref = switch (def.name_kind) { .tag => try std.fmt.bufPrint(&ref_buf, "struct {s}", .{name}), .typedef => name, }; var layout = struct_layout.compute(allocator, def, abi, null) catch |err| switch (err) { // Left out entirely, since the compiler would refuse the struct. Whether the // type really is absent is the scalar probes' assertion. error.TypeUnavailable => { try w.print("\n/* {s}: uses a type {s} does not have */\n", .{ name, abi.id }); return; }, else => { std.log.err("{s} does not lay out on {s}: {t}", .{ name, abi.id, err }); return err; }, }; defer layout.deinit(allocator); try w.print("\n/* {s} */\n{s}\n{s}\n#pragma pack()\n", .{ name, case.prelude, case.source }); try w.print( \\_Static_assert(sizeof({s}) == {d}, "{s}: the engine says {s} is {d} bytes"); \\_Static_assert(_Alignof({s}) == {d}, "{s}: the engine says {s} aligns to {d}"); \\ , .{ ref, layout.size, abi.id, ref, layout.size, ref, layout.alignment, abi.id, ref, layout.alignment }); for (def.members, layout.placements) |member, placement| { try w.print( \\_Static_assert(__builtin_offsetof({s}, {s}) == {d}, "{s}: the engine says {s}.{s} is at {d}"); \\ , .{ ref, member.name, placement.offset, abi.id, ref, member.name, placement.offset }); if (!member.flexible) try w.print( \\_Static_assert(sizeof((({s} *)0)->{s}) == {d}, "{s}: the engine says {s}.{s} is {d} bytes"); \\ , .{ ref, member.name, placement.size, abi.id, ref, member.name, placement.size }); } }