290 lines
13 KiB
Zig
290 lines
13 KiB
Zig
//! 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 <abi-id> <output.c> [<abi-id> <output.c> ...]
|
|
//! 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 <abi-id> <output.c> [...]", .{});
|
|
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 <stddef.h>
|
|
\\#include <stdint.h>
|
|
\\#include <stdbool.h>
|
|
\\#include <stdalign.h>
|
|
\\
|
|
\\/* 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 });
|
|
}
|
|
}
|