//! Struct layout: where each member of a C-compatible struct lands in memory under a //! given ABI, and how much of the struct is padding (FR-3). //! //! Nothing here knows any language's syntax. A front end (today only //! `CStructParser.zig`) turns text into a `Definition`, and `compute` lays that out //! under an `Abi`. A Rust `#[repr(C)]` or a Zig `extern struct` front end would //! produce the same `Definition` and need nothing below to change. //! //! An ABI is data, not code: a table giving each scalar kind its size and alignment, //! or none where the type does not exist. Adding a profile is adding a table entry //! (NFR-4), and every profile names the C compiler targets it claims to reproduce, //! which is what the clang check in `engine/test/struct_oracle.zig` holds it to. //! //! No I/O. `compute` allocates the per-member results and nothing else. const std = @import("std"); const Allocator = std.mem.Allocator; /// What laying out a definition can fail with. pub const Error = error{ /// The definition has no members. C forbids an empty struct, and GNU C's /// zero-size answer is a different language's. EmptyStruct, /// More members than `max_members`. TooManyMembers, /// Two members share a name. DuplicateMember, /// A scalar the ABI does not have: `__int128` on a 32-bit target. TypeUnavailable, /// An array dimension of zero. A flexible array member is spelled `[]`. InvalidArrayLength, /// A flexible array member anywhere but last. MisplacedFlexibleArray, /// An alignment or pack value that is not a power of two. InvalidAlignment, /// Larger than the ABI can address, or than the arithmetic can hold. StructTooLarge, OutOfMemory, }; /// Ceiling on members, so untrusted input cannot ask for unbounded work (NFR-8). /// NFR-1 asks that 256 be handled without perceptible delay; this leaves room above /// that for generated code without making the duplicate check expensive. pub const max_members: usize = 1024; /// The scalar kinds whose size or alignment a profile has to state. /// /// These are C's kinds rather than shapes: `bool` and `char` are both one byte on /// every profile here, but they are different questions to ask of an ABI, and a /// profile where they differ would need no new member. Spellings are the front end's /// business; `uint32_t`, `u32` and `unsigned int` all arrive as `.int`. pub const Primitive = enum { /// One byte: `char`, `int8_t`, `u8`. char, /// `_Bool`. bool, /// Two bytes: `short`, `int16_t`, `char16_t`. short, /// Four bytes: `int`, `int32_t`, `char32_t`. int, /// `long`: eight bytes on LP64, four on LLP64 and ILP32. The reason a 64-bit /// Windows profile exists at all. long, /// Eight-byte integers: `long long`, `int64_t`. Aligned to four on i386. long_long, /// `__int128`. Absent on the 32-bit profiles. int128, float, /// Aligned to four on i386. double, /// Any data or function pointer, and the types defined to be pointer-sized here /// (`size_t`, `ptrdiff_t`, `intptr_t`). Every profile in the table has /// `size_t` the width of a pointer, so they share a row. pointer, /// `wchar_t`: two bytes on Windows, four elsewhere. wchar, /// An `enum`. Int-sized on every profile here; a bare-metal ARM profile with /// short enums is the case that would make this row differ. enumeration, }; /// Size and alignment of one scalar, in bytes. pub const Shape = struct { size: u8, alignment: u8, }; /// A target's layout rules. pub const Abi = struct { /// Stable and short: what `--abi` takes and what the clang check names. id: []const u8, /// What a person reads. label: []const u8, /// Every scalar kind, with null where the type does not exist. `EnumArray.init` /// takes a struct with a field per member, so a profile cannot forget one. shapes: std.EnumArray(Primitive, ?Shape), /// Which compiler family's rules the attributes follow. No default: a profile /// has to say. rules: Rules, /// How explicit alignment and packing combine, which is where compilers differ. /// Clang reproduces both, choosing by target, and the clang check is what /// established the one difference that matters so far. pub const Rules = enum { /// GCC's, and clang's everywhere but Windows: `#pragma pack` caps every /// member's alignment, including one raised by `aligned` or `alignas`. gnu, /// MSVC's, and clang's on `*-windows-msvc`: `#pragma pack` caps a member's /// natural alignment, and an explicit `aligned` or `alignas` is applied after /// it and wins. Under `pack(2)`, `char c; alignas(8) int i;` puts `i` at 8, /// where the GNU rules put it at 2. microsoft, }; /// The largest object the ABI can describe: `PTRDIFF_MAX`, half the address /// space. A C compiler refuses anything bigger, so a layout claiming otherwise /// would be one no program could have. fn maxObjectSize(self: *const Abi) u64 { const pointer_bits: u6 = @intCast(@as(u16, self.shapes.get(.pointer).?.size) * 8 - 1); return (@as(u64, 1) << pointer_bits) - 1; } }; fn s(size: u8, alignment: u8) ?Shape { return .{ .size = size, .alignment = alignment }; } /// The profiles, default first. Each is checked against every clang target listed /// for it in `build.zig`. pub const abis = [_]Abi{ .{ .id = "sysv64", .label = "64-bit Linux and macOS (x86-64 SysV, AArch64)", .rules = .gnu, .shapes = .init(.{ .char = s(1, 1), .bool = s(1, 1), .short = s(2, 2), .int = s(4, 4), .long = s(8, 8), .long_long = s(8, 8), .int128 = s(16, 16), .float = s(4, 4), .double = s(8, 8), .pointer = s(8, 8), .wchar = s(4, 4), .enumeration = s(4, 4), }), }, .{ .id = "win64", .label = "64-bit Windows (x64, ARM64)", .rules = .microsoft, .shapes = .init(.{ .char = s(1, 1), .bool = s(1, 1), .short = s(2, 2), .int = s(4, 4), .long = s(4, 4), .long_long = s(8, 8), .int128 = s(16, 16), .float = s(4, 4), .double = s(8, 8), .pointer = s(8, 8), .wchar = s(2, 2), .enumeration = s(4, 4), }), }, .{ .id = "i386", .label = "32-bit x86 Linux (i386 SysV)", .rules = .gnu, .shapes = .init(.{ .char = s(1, 1), .bool = s(1, 1), .short = s(2, 2), .int = s(4, 4), .long = s(4, 4), .long_long = s(8, 4), .int128 = null, .float = s(4, 4), .double = s(8, 4), .pointer = s(4, 4), .wchar = s(4, 4), .enumeration = s(4, 4), }), }, .{ .id = "arm32", .label = "32-bit ARM Linux (AAPCS)", .rules = .gnu, .shapes = .init(.{ .char = s(1, 1), .bool = s(1, 1), .short = s(2, 2), .int = s(4, 4), .long = s(4, 4), .long_long = s(8, 8), .int128 = null, .float = s(4, 4), .double = s(8, 8), .pointer = s(4, 4), .wchar = s(4, 4), .enumeration = s(4, 4), }), }, }; /// The profile used when none is named (FR-3.6). pub const default_abi: *const Abi = &abis[0]; /// The profile with this id, or null. pub fn findAbi(id: []const u8) ?*const Abi { for (&abis) |*abi| { if (std.mem.eql(u8, abi.id, id)) return abi; } return null; } /// Where a member was written, as a byte range in the front end's source. Lets a /// layout error point at the member it is about. pub const Span = struct { start: usize, len: usize, }; /// One member, as the front end understood it. pub const Member = struct { name: []const u8, /// The type as the user wrote it, for display only: `uint32_t`, `struct node *`. /// Array dimensions are not part of it; they are `dims`. type_name: []const u8, primitive: Primitive, /// Array dimensions, outermost first. Empty for a scalar. dims: []const u64 = &.{}, /// `T name[]` as the last member: no size of its own, but it still aligns. flexible: bool = false, /// `alignas(N)` or `aligned(N)`: raises the member's alignment, never lowers it. aligned: ?u32 = null, /// `__attribute__((packed))` on the member: alignment 1. @"packed": bool = false, span: ?Span = null, }; /// A struct to lay out. Borrows everything: a front end that allocates owns the /// memory and says how it is released. pub const Definition = struct { /// The struct's tag or typedef name, if it had one. name: ?[]const u8 = null, /// Whether `name` is a tag (`struct hdr`) or a typedef (`hdr`), which decides how /// the struct is referred to. name_kind: NameKind = .tag, members: []const Member, /// `__attribute__((packed))` on the struct: every member aligned to 1, unless the /// member asks for more itself. @"packed": bool = false, /// `#pragma pack(N)` in effect: no member aligned to more than N. pack: ?u32 = null, /// `aligned(N)` on the struct: the struct's alignment is at least N. aligned: ?u32 = null, pub const NameKind = enum { tag, typedef }; }; /// Where one member landed. Parallel to `Definition.members`. pub const Placement = struct { offset: u64, size: u64, /// The alignment the member was placed at, after every attribute and pack. alignment: u32, /// Bytes inserted between the previous member's end and this one's start. padding_before: u64, }; /// Which member a layout error is about, when it is about one. pub const Diagnostic = struct { member: ?usize = null, }; /// A laid-out struct. pub const Layout = struct { abi: *const Abi, /// Borrowed from the definition this came from. members: []const Member, /// Owned, one per member. placements: []Placement, size: u64, alignment: u32, /// Padding after the last member, rounding the size up to the alignment. tail_padding: u64, /// The sum of the members' own sizes: the struct with no padding at all /// (FR-3.10). packed_size: u64, pub fn deinit(self: *Layout, allocator: Allocator) void { allocator.free(self.placements); self.* = undefined; } /// Every padding byte, between members and at the tail. pub fn padding(self: Layout) u64 { return self.size - self.packed_size; } /// The struct as consecutive byte ranges, in address order: every member, and /// every run of padding between and after them. One list for both a table and a /// memory map, so they cannot disagree about what is where. Derived from /// `placements` on demand rather than stored as a byte array, because the answer /// for a struct holding `char buf[1 << 20]` is three segments, not a million /// entries. pub fn segments(self: *const Layout) SegmentIterator { return .{ .layout = self }; } }; /// A run of bytes in a layout. pub const Segment = struct { offset: u64, len: u64, kind: Kind, pub const Kind = union(enum) { /// Bytes of the member at this index. member: usize, /// Padding between members, before the member at this index. padding: usize, /// Padding after the last member. tail, }; }; /// Yields the segments of a layout. Every member is yielded, a flexible array member /// included with a length of zero, because a table lists it; padding is yielded only /// where there is some. pub const SegmentIterator = struct { layout: *const Layout, index: usize = 0, /// Whether the padding before `placements[index]` has been yielded. padding_done: bool = false, tail_done: bool = false, pub fn next(self: *SegmentIterator) ?Segment { const placements = self.layout.placements; while (self.index < placements.len) { const placement = placements[self.index]; if (!self.padding_done) { self.padding_done = true; if (placement.padding_before > 0) return .{ .offset = placement.offset - placement.padding_before, .len = placement.padding_before, .kind = .{ .padding = self.index }, }; } const index = self.index; self.index += 1; self.padding_done = false; return .{ .offset = placement.offset, .len = placement.size, .kind = .{ .member = index }, }; } if (!self.tail_done) { self.tail_done = true; const tail = self.layout.tail_padding; if (tail > 0) return .{ .offset = self.layout.size - tail, .len = tail, .kind = .tail }; } return null; } }; /// Lay out `definition` under `abi`. /// /// Members are placed in order, each at the next offset that is a multiple of its /// alignment, and the struct's size is rounded up to its own alignment, which is the /// largest of its members'. A member's alignment starts at its scalar's and is /// lowered to 1 by `packed` (on the member or the struct). Then `aligned` raises it /// and `#pragma pack` caps it, in the order `abi.rules` says: under the GNU rules /// the cap comes last, so it caps an explicit alignment too; under the Microsoft /// rules the explicit alignment comes last and wins. /// /// On failure, `diag.member` names the member the error is about, if any. pub fn compute( allocator: Allocator, definition: Definition, abi: *const Abi, diag: ?*Diagnostic, ) Error!Layout { const members = definition.members; if (members.len == 0) return error.EmptyStruct; if (members.len > max_members) return error.TooManyMembers; if (definition.pack) |n| try checkAlignment(n); if (definition.aligned) |n| try checkAlignment(n); for (members, 0..) |member, i| { errdefer if (diag) |d| { d.member = i; }; if (member.aligned) |n| try checkAlignment(n); for (member.dims) |dim| { if (dim == 0) return error.InvalidArrayLength; } if (member.flexible and i != members.len - 1) return error.MisplacedFlexibleArray; for (members[0..i]) |earlier| { if (std.mem.eql(u8, earlier.name, member.name)) return error.DuplicateMember; } } const placements = try allocator.alloc(Placement, members.len); errdefer allocator.free(placements); const limit = abi.maxObjectSize(); var offset: u64 = 0; var struct_alignment: u32 = 1; var packed_size: u64 = 0; for (members, placements, 0..) |member, *placement, i| { errdefer if (diag) |d| { d.member = i; }; const scalar = abi.shapes.get(member.primitive) orelse return error.TypeUnavailable; var count: u64 = 1; for (member.dims) |dim| count = try mul(count, dim); const size: u64 = if (member.flexible) 0 else try mul(scalar.size, count); var alignment: u32 = scalar.alignment; if (member.@"packed" or definition.@"packed") alignment = 1; switch (abi.rules) { .gnu => { if (member.aligned) |n| alignment = @max(alignment, n); if (definition.pack) |n| alignment = @min(alignment, n); }, .microsoft => { if (definition.pack) |n| alignment = @min(alignment, n); if (member.aligned) |n| alignment = @max(alignment, n); }, } const start = try alignUp(offset, alignment); placement.* = .{ .offset = start, .size = size, .alignment = alignment, .padding_before = start - offset, }; offset = try add(start, size); if (offset > limit) return error.StructTooLarge; packed_size = try add(packed_size, size); struct_alignment = @max(struct_alignment, alignment); } if (definition.aligned) |n| struct_alignment = @max(struct_alignment, n); const size = try alignUp(offset, struct_alignment); if (size > limit) return error.StructTooLarge; return .{ .abi = abi, .members = members, .placements = placements, .size = size, .alignment = struct_alignment, .tail_padding = size - offset, .packed_size = packed_size, }; } fn checkAlignment(n: u32) Error!void { // `isPowerOfTwo` asserts a nonzero argument, and zero is a value a definition // can carry. if (n == 0 or !std.math.isPowerOfTwo(n)) return error.InvalidAlignment; } fn mul(a: u64, b: u64) Error!u64 { return std.math.mul(u64, a, b) catch error.StructTooLarge; } fn add(a: u64, b: u64) Error!u64 { return std.math.add(u64, a, b) catch error.StructTooLarge; } /// `offset` rounded up to a multiple of `alignment`, which is a power of two. fn alignUp(offset: u64, alignment: u32) Error!u64 { const rem = offset % alignment; if (rem == 0) return offset; return add(offset, alignment - rem); } // -- Tests -- const testing = std.testing; fn m(name: []const u8, primitive: Primitive) Member { return .{ .name = name, .type_name = @tagName(primitive), .primitive = primitive }; } fn expectPlacement(layout: Layout, index: usize, offset: u64, size: u64, alignment: u32, padding_before: u64) !void { const placement = layout.placements[index]; try testing.expectEqual(offset, placement.offset); try testing.expectEqual(size, placement.size); try testing.expectEqual(alignment, placement.alignment); try testing.expectEqual(padding_before, placement.padding_before); } test "the design document's example: one byte of padding in sixteen" { // struct { u32 x; u8 flags; u16 id; u64 ts; } const members = [_]Member{ m("x", .int), m("flags", .char), m("id", .short), m("ts", .long_long) }; var layout = try compute(testing.allocator, .{ .members = &members }, default_abi, null); defer layout.deinit(testing.allocator); try expectPlacement(layout, 0, 0, 4, 4, 0); try expectPlacement(layout, 1, 4, 1, 1, 0); try expectPlacement(layout, 2, 6, 2, 2, 1); try expectPlacement(layout, 3, 8, 8, 8, 0); try testing.expectEqual(@as(u64, 16), layout.size); try testing.expectEqual(@as(u32, 8), layout.alignment); try testing.expectEqual(@as(u64, 0), layout.tail_padding); try testing.expectEqual(@as(u64, 15), layout.packed_size); try testing.expectEqual(@as(u64, 1), layout.padding()); } test "tail padding rounds the size up to the struct's alignment" { // struct { u64 a; u8 b; } is 16 bytes, seven of them after b. const members = [_]Member{ m("a", .long_long), m("b", .char) }; var layout = try compute(testing.allocator, .{ .members = &members }, default_abi, null); defer layout.deinit(testing.allocator); try testing.expectEqual(@as(u64, 16), layout.size); try testing.expectEqual(@as(u64, 7), layout.tail_padding); try testing.expectEqual(@as(u64, 7), layout.padding()); } test "what makes each profile its own" { const Case = struct { abi: []const u8, size: u64, alignment: u32 }; // struct { char c; double d; } shows where 8-byte types align. const doubles = [_]Member{ m("c", .char), m("d", .double) }; for ([_]Case{ .{ .abi = "sysv64", .size = 16, .alignment = 8 }, .{ .abi = "win64", .size = 16, .alignment = 8 }, .{ .abi = "i386", .size = 12, .alignment = 4 }, .{ .abi = "arm32", .size = 16, .alignment = 8 }, }) |case| { var layout = try compute(testing.allocator, .{ .members = &doubles }, findAbi(case.abi).?, null); defer layout.deinit(testing.allocator); try testing.expectEqual(case.size, layout.size); try testing.expectEqual(case.alignment, layout.alignment); } // struct { long l; void *p; wchar_t w; } shows the data model. const model = [_]Member{ m("l", .long), m("p", .pointer), m("w", .wchar) }; for ([_]Case{ .{ .abi = "sysv64", .size = 24, .alignment = 8 }, .{ .abi = "win64", .size = 24, .alignment = 8 }, .{ .abi = "i386", .size = 12, .alignment = 4 }, .{ .abi = "arm32", .size = 12, .alignment = 4 }, }) |case| { var layout = try compute(testing.allocator, .{ .members = &model }, findAbi(case.abi).?, null); defer layout.deinit(testing.allocator); try testing.expectEqual(case.size, layout.size); try testing.expectEqual(case.alignment, layout.alignment); } // On Windows `long` is four bytes, so the pointer after it is padded. var win = try compute(testing.allocator, .{ .members = &model }, findAbi("win64").?, null); defer win.deinit(testing.allocator); try expectPlacement(win, 1, 8, 8, 8, 4); try expectPlacement(win, 2, 16, 2, 2, 0); } test "__int128 is sixteen-aligned on 64-bit and absent on 32-bit" { const members = [_]Member{ m("c", .char), m("big", .int128) }; var layout = try compute(testing.allocator, .{ .members = &members }, default_abi, null); defer layout.deinit(testing.allocator); try expectPlacement(layout, 1, 16, 16, 16, 15); try testing.expectEqual(@as(u64, 32), layout.size); var diag: Diagnostic = .{}; try testing.expectError(error.TypeUnavailable, compute(testing.allocator, .{ .members = &members }, findAbi("i386").?, &diag)); try testing.expectEqual(@as(?usize, 1), diag.member); } test "arrays multiply the element size and keep the element alignment" { // struct { char tag[3]; int grid[2][3]; } : the grid starts at 4 and is 24 bytes. const members = [_]Member{ .{ .name = "tag", .type_name = "char", .primitive = .char, .dims = &.{3} }, .{ .name = "grid", .type_name = "int", .primitive = .int, .dims = &.{ 2, 3 } }, }; var layout = try compute(testing.allocator, .{ .members = &members }, default_abi, null); defer layout.deinit(testing.allocator); try expectPlacement(layout, 0, 0, 3, 1, 0); try expectPlacement(layout, 1, 4, 24, 4, 1); try testing.expectEqual(@as(u64, 28), layout.size); } test "a flexible array member has no size but still aligns" { // struct { short len; double data[]; } : data starts at 8, sizeof is 8. const members = [_]Member{ m("len", .short), .{ .name = "data", .type_name = "double", .primitive = .double, .flexible = true }, }; var layout = try compute(testing.allocator, .{ .members = &members }, default_abi, null); defer layout.deinit(testing.allocator); try expectPlacement(layout, 1, 8, 0, 8, 6); try testing.expectEqual(@as(u64, 8), layout.size); try testing.expectEqual(@as(u64, 2), layout.packed_size); } test "packed removes padding, aligned adds it back where asked" { const members = [_]Member{ m("c", .char), m("i", .int), m("d", .double) }; var packed_layout = try compute(testing.allocator, .{ .members = &members, .@"packed" = true }, default_abi, null); defer packed_layout.deinit(testing.allocator); try testing.expectEqual(@as(u64, 13), packed_layout.size); try testing.expectEqual(@as(u32, 1), packed_layout.alignment); try testing.expectEqual(@as(u64, 0), packed_layout.padding()); // A member's own `aligned` survives the struct being packed. const mixed = [_]Member{ m("c", .char), .{ .name = "i", .type_name = "int", .primitive = .int, .aligned = 4 } }; var aligned_layout = try compute(testing.allocator, .{ .members = &mixed, .@"packed" = true }, default_abi, null); defer aligned_layout.deinit(testing.allocator); try expectPlacement(aligned_layout, 1, 4, 4, 4, 3); try testing.expectEqual(@as(u32, 4), aligned_layout.alignment); // Packing one member leaves the rest natural. const one = [_]Member{ m("c", .char), .{ .name = "i", .type_name = "int", .primitive = .int, .@"packed" = true }, m("s", .short) }; var one_layout = try compute(testing.allocator, .{ .members = &one }, default_abi, null); defer one_layout.deinit(testing.allocator); try expectPlacement(one_layout, 1, 1, 4, 1, 0); try expectPlacement(one_layout, 2, 6, 2, 2, 1); try testing.expectEqual(@as(u64, 8), one_layout.size); } test "aligned on a member raises it; on the struct it raises the struct" { const members = [_]Member{ m("c", .char), .{ .name = "i", .type_name = "int", .primitive = .int, .aligned = 16 } }; var layout = try compute(testing.allocator, .{ .members = &members }, default_abi, null); defer layout.deinit(testing.allocator); try expectPlacement(layout, 1, 16, 4, 16, 15); try testing.expectEqual(@as(u64, 32), layout.size); // An `aligned` smaller than the natural alignment does nothing. const lower = [_]Member{ m("c", .char), .{ .name = "d", .type_name = "double", .primitive = .double, .aligned = 2 } }; var lower_layout = try compute(testing.allocator, .{ .members = &lower }, default_abi, null); defer lower_layout.deinit(testing.allocator); try expectPlacement(lower_layout, 1, 8, 8, 8, 7); const plain = [_]Member{ m("a", .int), m("b", .char) }; var struct_layout = try compute(testing.allocator, .{ .members = &plain, .aligned = 32 }, default_abi, null); defer struct_layout.deinit(testing.allocator); try testing.expectEqual(@as(u32, 32), struct_layout.alignment); try testing.expectEqual(@as(u64, 32), struct_layout.size); try testing.expectEqual(@as(u64, 27), struct_layout.tail_padding); } test "pragma pack caps every member's alignment" { const members = [_]Member{ m("c", .char), m("d", .double), m("s", .short) }; var layout = try compute(testing.allocator, .{ .members = &members, .pack = 2 }, default_abi, null); defer layout.deinit(testing.allocator); try expectPlacement(layout, 1, 2, 8, 2, 1); try expectPlacement(layout, 2, 10, 2, 2, 0); try testing.expectEqual(@as(u64, 12), layout.size); try testing.expectEqual(@as(u32, 2), layout.alignment); // A pack larger than anything in the struct changes nothing. var wide = try compute(testing.allocator, .{ .members = &members, .pack = 16 }, default_abi, null); defer wide.deinit(testing.allocator); try testing.expectEqual(@as(u64, 24), wide.size); } test "the rules decide whether pragma pack caps an explicit alignment" { // struct { char c; alignas(8) int i; } under pack(2). Found by the clang check: // the GNU targets put `i` at 2, the windows-msvc ones at 8. const members = [_]Member{ m("c", .char), .{ .name = "i", .type_name = "int", .primitive = .int, .aligned = 8 } }; const def: Definition = .{ .members = &members, .pack = 2 }; var gnu = try compute(testing.allocator, def, findAbi("sysv64").?, null); defer gnu.deinit(testing.allocator); try testing.expectEqual(Abi.Rules.gnu, gnu.abi.rules); try expectPlacement(gnu, 1, 2, 4, 2, 1); try testing.expectEqual(@as(u64, 6), gnu.size); var microsoft = try compute(testing.allocator, def, findAbi("win64").?, null); defer microsoft.deinit(testing.allocator); try testing.expectEqual(Abi.Rules.microsoft, microsoft.abi.rules); try expectPlacement(microsoft, 1, 8, 4, 8, 7); try testing.expectEqual(@as(u64, 16), microsoft.size); // Without an explicit alignment the two agree: pack caps the natural one. const plain = [_]Member{ m("c", .char), m("d", .double) }; for ([_][]const u8{ "sysv64", "win64" }) |id| { var layout = try compute(testing.allocator, .{ .members = &plain, .pack = 2 }, findAbi(id).?, null); defer layout.deinit(testing.allocator); try expectPlacement(layout, 1, 2, 8, 2, 1); } } test "segments walk the struct in address order, every member included" { // struct { char c; int i; short s; char flex[]; } : c, pad, i, s, tail. const members = [_]Member{ m("c", .char), m("i", .int), m("s", .short), .{ .name = "flex", .type_name = "char", .primitive = .char, .flexible = true }, }; var layout = try compute(testing.allocator, .{ .members = &members }, default_abi, null); defer layout.deinit(testing.allocator); try testing.expectEqual(@as(u64, 12), layout.size); const expected = [_]Segment{ .{ .offset = 0, .len = 1, .kind = .{ .member = 0 } }, .{ .offset = 1, .len = 3, .kind = .{ .padding = 1 } }, .{ .offset = 4, .len = 4, .kind = .{ .member = 1 } }, .{ .offset = 8, .len = 2, .kind = .{ .member = 2 } }, // The flexible member has no bytes and is still there to be listed. .{ .offset = 10, .len = 0, .kind = .{ .member = 3 } }, .{ .offset = 10, .len = 2, .kind = .tail }, }; var it = layout.segments(); for (expected) |want| { const got = it.next() orelse return error.TestUnexpectedResult; try testing.expectEqualDeep(want, got); } try testing.expect(it.next() == null); // The segments tile the struct exactly. var covered: u64 = 0; var again = layout.segments(); while (again.next()) |seg| { try testing.expectEqual(covered, seg.offset); covered += seg.len; } try testing.expectEqual(layout.size, covered); } test "a struct with no padding has one segment per member" { const members = [_]Member{ m("a", .int), m("b", .int) }; var layout = try compute(testing.allocator, .{ .members = &members }, default_abi, null); defer layout.deinit(testing.allocator); var it = layout.segments(); var count: usize = 0; while (it.next()) |seg| : (count += 1) { try testing.expect(seg.kind == .member); } try testing.expectEqual(@as(usize, 2), count); } test "definitions that cannot be laid out say why and where" { var diag: Diagnostic = .{}; try testing.expectError(error.EmptyStruct, compute(testing.allocator, .{ .members = &.{} }, default_abi, &diag)); const dup = [_]Member{ m("a", .int), m("b", .int), m("a", .char) }; try testing.expectError(error.DuplicateMember, compute(testing.allocator, .{ .members = &dup }, default_abi, &diag)); try testing.expectEqual(@as(?usize, 2), diag.member); const zero = [_]Member{ m("a", .int), .{ .name = "z", .type_name = "char", .primitive = .char, .dims = &.{ 2, 0 } } }; diag = .{}; try testing.expectError(error.InvalidArrayLength, compute(testing.allocator, .{ .members = &zero }, default_abi, &diag)); try testing.expectEqual(@as(?usize, 1), diag.member); const flex_first = [_]Member{ .{ .name = "f", .type_name = "char", .primitive = .char, .flexible = true }, m("a", .int) }; diag = .{}; try testing.expectError(error.MisplacedFlexibleArray, compute(testing.allocator, .{ .members = &flex_first }, default_abi, &diag)); try testing.expectEqual(@as(?usize, 0), diag.member); const odd = [_]Member{.{ .name = "a", .type_name = "int", .primitive = .int, .aligned = 3 }}; diag = .{}; try testing.expectError(error.InvalidAlignment, compute(testing.allocator, .{ .members = &odd }, default_abi, &diag)); try testing.expectEqual(@as(?usize, 0), diag.member); const plain = [_]Member{m("a", .int)}; diag = .{}; try testing.expectError(error.InvalidAlignment, compute(testing.allocator, .{ .members = &plain, .pack = 6 }, default_abi, &diag)); try testing.expectEqual(@as(?usize, null), diag.member); try testing.expectError(error.InvalidAlignment, compute(testing.allocator, .{ .members = &plain, .aligned = 0 }, default_abi, null)); } test "too many members is refused before any work is done" { const many = try testing.allocator.alloc(Member, max_members + 1); defer testing.allocator.free(many); for (many) |*member| member.* = m("same", .char); // Every name is the same, so reaching the duplicate check would be a different // error: the count is checked first. try testing.expectError(error.TooManyMembers, compute(testing.allocator, .{ .members = many }, default_abi, null)); const names = try testing.allocator.alloc([8]u8, max_members); defer testing.allocator.free(names); for (many[0..max_members], names, 0..) |*member, *name, i| { member.* = m(std.fmt.bufPrint(name, "f{d}", .{i}) catch unreachable, .char); } var layout = try compute(testing.allocator, .{ .members = many[0..max_members] }, default_abi, null); defer layout.deinit(testing.allocator); try testing.expectEqual(@as(u64, max_members), layout.size); } test "a struct larger than the ABI can address is refused" { // 2^31 bytes is one past PTRDIFF_MAX on a 32-bit target and fine on a 64-bit one. const members = [_]Member{.{ .name = "buf", .type_name = "char", .primitive = .char, .dims = &.{1 << 31} }}; var diag: Diagnostic = .{}; try testing.expectError(error.StructTooLarge, compute(testing.allocator, .{ .members = &members }, findAbi("i386").?, &diag)); try testing.expectEqual(@as(?usize, 0), diag.member); var layout = try compute(testing.allocator, .{ .members = &members }, default_abi, null); defer layout.deinit(testing.allocator); try testing.expectEqual(@as(u64, 1 << 31), layout.size); // Dimensions whose product overflows u64 are the same error, not a wrap. const huge = [_]Member{.{ .name = "h", .type_name = "long long", .primitive = .long_long, .dims = &.{ 1 << 40, 1 << 40 } }}; try testing.expectError(error.StructTooLarge, compute(testing.allocator, .{ .members = &huge }, default_abi, null)); // Rounding up to the struct's alignment can push it over too: the members fit // in PTRDIFF_MAX, the padded struct does not. const edge = [_]Member{ m("i", .int), .{ .name = "buf", .type_name = "char", .primitive = .char, .dims = &.{(1 << 31) - 5} }, }; try testing.expectError(error.StructTooLarge, compute(testing.allocator, .{ .members = &edge }, findAbi("i386").?, null)); } test "every profile has the scalars C guarantees, and pointer-sized limits" { for (&abis) |*abi| { for ([_]Primitive{ .char, .bool, .short, .int, .long, .long_long, .float, .double, .pointer, .wchar, .enumeration }) |p| { const shape = abi.shapes.get(p) orelse return error.TestUnexpectedResult; try testing.expect(shape.size > 0); try testing.expect(std.math.isPowerOfTwo(shape.alignment)); try testing.expect(shape.alignment <= shape.size); } try testing.expectEqual(@as(u8, 1), abi.shapes.get(.char).?.size); try testing.expect(abi.shapes.get(.int128) == null or abi.shapes.get(.pointer).?.size == 8); try testing.expect(abi.label.len > 0); try testing.expect(findAbi(abi.id) == abi); } try testing.expectEqual(@as(u64, std.math.maxInt(i64)), default_abi.maxObjectSize()); try testing.expectEqual(@as(u64, std.math.maxInt(i32)), findAbi("arm32").?.maxObjectSize()); try testing.expect(findAbi("vax") == null); } test "compute releases everything on any allocation failure" { try testing.checkAllAllocationFailures(testing.allocator, struct { fn run(a: Allocator) !void { const members = [_]Member{ m("x", .int), m("y", .char) }; var layout = try compute(a, .{ .members = &members }, default_abi, null); layout.deinit(a); } }.run, .{}); }