authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-03-03 13:14:05+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-03-10 10:26:15+00:00
log3a3ac1034519d1b0c58279345e7e0f6272bccbee
tree6ee4e3e061dd994222487b3bde2c198e2e38543c
parentc2b42383eb058f4c2bd17738cd73382e6a6672eb
signaturelock-open Commit is signed but in an unrecognized format.

cbe: fix type layouts, and statically assert them


2 files changed, 88 insertions(+), 21 deletions(-)

lib/zig.h+8
......@@ -151,6 +151,14 @@
151151#define zig_has_attribute(attribute) 0
152152#endif
153153
154#if __STDC_VERSION__ >= 201112L
155#define zig_static_assert(cond, msg) _Static_assert(cond, msg)
156#elif zig_has_attribute(unused)
157#define zig_static_assert(cond, _) typedef char zig_expand_concat(zig_static_assert_fail_, __LINE__)[!!(cond)] __attribute__((unused))
158#else
159#define zig_static_assert(cond, _) typedef char zig_expand_concat(zig_static_assert_fail_, __LINE__)[!!(cond)]
160#endif
161
154162#if __STDC_VERSION__ >= 202311L
155163#define zig_threadlocal thread_local
156164#elif __STDC_VERSION__ >= 201112L
src/codegen/c/type/render_defs.zig+80-21
......@@ -246,6 +246,8 @@ pub fn defineComplete(
246246 ptr_cty.fmtDeclaratorPrefix(zcu),
247247 ptr_cty.fmtDeclaratorSuffix(zcu),
248248 });
249 // Don't bother with `writeStaticAssertLayout`---there's not really any way we could mess
250 // slices up, and they're all obviously the same layout.
249251 },
250252 .optional => switch (CType.classifyOptional(ty, zcu)) {
251253 .error_set,
......@@ -260,6 +262,7 @@ pub fn defineComplete(
260262 name_cty.fmtTypeName(zcu),
261263 ty.fmt(pt),
262264 });
265 try writeStaticAssertLayout(ty, name_cty, w, zcu);
263266 },
264267
265268 .@"struct" => {
......@@ -277,6 +280,7 @@ pub fn defineComplete(
277280 payload_cty.fmtDeclaratorPrefix(zcu),
278281 payload_cty.fmtDeclaratorSuffix(zcu),
279282 });
283 try writeStaticAssertLayout(ty, name_cty, w, zcu);
280284 },
281285 },
282286 .array => if (ty.hasRuntimeBits(zcu)) {
......@@ -297,6 +301,7 @@ pub fn defineComplete(
297301 array_cty.fmtDeclaratorSuffix(zcu),
298302 ty.fmt(pt),
299303 });
304 try writeStaticAssertLayout(ty, name_cty, w, zcu);
300305 },
301306 .vector => if (ty.hasRuntimeBits(zcu)) {
302307 const name_cty: CType = .{ .vec = ty };
......@@ -312,6 +317,7 @@ pub fn defineComplete(
312317 array_cty.fmtDeclaratorSuffix(zcu),
313318 ty.fmt(pt),
314319 });
320 try writeStaticAssertLayout(ty, name_cty, w, zcu);
315321 },
316322 else => {},
317323 }
......@@ -374,18 +380,21 @@ fn defineTuple(
374380 zig_offset = field_align.forward(zig_offset);
375381 if (!field_ty.hasRuntimeBits(zcu)) continue;
376382 c_offset = field_align.forward(c_offset);
383 try w.writeByte(' ');
377384 if (zig_offset == 0 and overalign) {
378385 // This is the first field; specify its alignment to align the tuple.
379 try w.print(" zig_align({d})", .{tuple_align.toByteUnits().?});
386 try writeFieldAlign(field_ty, tuple_align, w, zcu);
380387 } else if (zig_offset > c_offset) {
381388 // This field needs to be overaligned compared to what its offset would otherwise be.
382 const need_align: Alignment = .fromLog2Units(@ctz(zig_offset));
383 try w.print(" zig_align({d})", .{need_align.toByteUnits().?});
389 const need_align: Alignment = .minStrict(
390 tuple_align, // don't make the struct more aligned than it should be
391 .fromLog2Units(@ctz(zig_offset)),
392 );
393 try writeFieldAlign(field_ty, need_align, w, zcu);
384394 c_offset = need_align.forward(c_offset);
385 assert(c_offset == zig_offset);
386395 }
387396 const field_cty: CType = try .lower(field_ty, deps, arena, zcu);
388 try w.print(" {f}f{d}{f};\n", .{
397 try w.print("{f}f{d}{f};\n", .{
389398 field_cty.fmtDeclaratorPrefix(zcu),
390399 field_index,
391400 field_cty.fmtDeclaratorSuffix(zcu),
......@@ -395,6 +404,8 @@ fn defineTuple(
395404 c_offset += field_size;
396405 }
397406 try w.writeAll("};\n");
407
408 try writeStaticAssertLayout(ty, name_cty, w, zcu);
398409}
399410fn defineStruct(
400411 ty: Type,
......@@ -420,6 +431,8 @@ fn defineStruct(
420431 const natural_offset = natural_align.forward(offset);
421432 const actual_offset = struct_type.field_offsets.get(ip)[field_index];
422433 if (actual_offset < natural_offset) break :pack true;
434 // Also pack if any field is more aligned than the struct should be.
435 if (natural_align.compareStrict(.gt, struct_type.alignment)) break :pack true;
423436 offset = actual_offset + field_ty.abiSize(zcu);
424437 }
425438 break :pack false;
......@@ -459,22 +472,22 @@ fn defineStruct(
459472 false => natural_align.forward(offset),
460473 };
461474 const actual_offset = struct_type.field_offsets.get(ip)[field_index];
475 try w.writeByte(' ');
462476 if (actual_offset == 0 and overalign) {
463477 // This is the first field; specify its alignment to align the struct.
464 try w.print(" zig_align({d})", .{struct_type.alignment.toByteUnits().?});
478 try writeFieldAlign(field_ty, struct_type.alignment, w, zcu);
465479 } else if (actual_offset > natural_offset) {
466480 // This field needs to be underaligned or overaligned compared to what its
467481 // offset would otherwise be.
468 const need_align: Alignment = .fromLog2Units(@ctz(actual_offset));
469 if (need_align.compareStrict(.lt, natural_align)) {
470 try w.print(" zig_under_align({d})", .{need_align.toByteUnits().?});
471 } else {
472 try w.print(" zig_align({d})", .{need_align.toByteUnits().?});
473 }
482 const need_align: Alignment = .minStrict(
483 struct_type.alignment, // don't make the struct more aligned than it should be
484 .fromLog2Units(@ctz(actual_offset)),
485 );
486 try writeFieldAlign(field_ty, need_align, w, zcu);
474487 }
475488 const field_cty: CType = try .lower(field_ty, deps, arena, zcu);
476489 const field_name = struct_type.field_names.get(ip)[field_index].toSlice(ip);
477 try w.print(" {f}{f}{f};\n", .{
490 try w.print("{f}{f}{f};\n", .{
478491 field_cty.fmtDeclaratorPrefix(zcu),
479492 fmtIdentSolo(field_name),
480493 field_cty.fmtDeclaratorSuffix(zcu),
......@@ -485,6 +498,8 @@ fn defineStruct(
485498 try w.writeByte('}');
486499 if (pack) try w.writeByte(')');
487500 try w.writeAll(";\n");
501
502 try writeStaticAssertLayout(ty, name_cty, w, zcu);
488503}
489504fn defineUnionAuto(
490505 ty: Type,
......@@ -500,12 +515,20 @@ fn defineUnionAuto(
500515 const union_type = ip.loadUnionType(ty.toIntern());
501516 const enum_tag_ty: Type = .fromInterned(union_type.enum_tag_type);
502517
518 const layout = Type.getUnionLayout(union_type, zcu);
519
503520 // If there are any underaligned fields, we need to byte-pack the union.
504521 const pack: bool = for (union_type.field_types.get(ip)) |field_ty_ip| {
505522 const field_ty: Type = .fromInterned(field_ty_ip);
506523 if (!field_ty.hasRuntimeBits(zcu)) continue;
507524 const natural_align = field_ty.abiAlignment(zcu);
508525 if (natural_align.compareStrict(.gt, union_type.alignment)) break true;
526 // The tag will immediately follow the payload. This layout may put the tag in what would
527 // otherwise be padding on the payload union, because if the most-aligned union field is not
528 // the largest one, a larger field may make the payload "underaligned" overall. As such, we
529 // need to check whether this field is okay with the payload size, and if not then we must
530 // byte-pack.
531 if (!natural_align.check(layout.payload_size)) break true;
509532 } else false;
510533
511534 // If the alignment of other fields would not give the union sufficient alignment, we
......@@ -536,6 +559,10 @@ fn defineUnionAuto(
536559 });
537560 if (payload_has_bits) {
538561 try w.writeByte(' ');
562 if (overalign) {
563 // Specify the alignment of `union { ... } payload;` to align the union's `struct`.
564 try w.print("zig_align({d}) ", .{union_type.alignment.toByteUnits().?});
565 }
539566 if (pack) try w.writeAll("zig_packed(");
540567 try w.writeAll("union {\n");
541568 for (0..enum_tag_ty.enumFieldCount(zcu)) |field_index| {
......@@ -543,12 +570,7 @@ fn defineUnionAuto(
543570 if (!field_ty.hasRuntimeBits(zcu)) continue;
544571 const field_name = enum_tag_ty.enumFieldName(field_index, zcu).toSlice(ip);
545572 const field_cty: CType = try .lower(field_ty, deps, arena, zcu);
546 try w.writeAll(" ");
547 if (overalign and field_index == 0) {
548 // This is the first field; specify its alignment to align the union.
549 try w.print("zig_align({d}) ", .{union_type.alignment.toByteUnits().?});
550 }
551 try w.print("{f}{f}{f};\n", .{
573 try w.print(" {f}{f}{f};\n", .{
552574 field_cty.fmtDeclaratorPrefix(zcu),
553575 fmtIdentSolo(field_name),
554576 field_cty.fmtDeclaratorSuffix(zcu),
......@@ -566,6 +588,8 @@ fn defineUnionAuto(
566588 });
567589 }
568590 try w.writeAll("};\n");
591
592 try writeStaticAssertLayout(ty, name_cty, w, zcu);
569593}
570594fn defineUnionExtern(
571595 ty: Type,
......@@ -622,11 +646,12 @@ fn defineUnionExtern(
622646 if (!field_ty.hasRuntimeBits(zcu)) continue;
623647 const field_name = enum_tag_ty.enumFieldName(field_index, zcu).toSlice(ip);
624648 const field_cty: CType = try .lower(field_ty, deps, arena, zcu);
649 try w.writeByte(' ');
625650 if (overalign and field_index == 0) {
626651 // This is the first field; specify its alignment to align the union.
627 try w.print(" zig_align({d})", .{union_type.alignment.toByteUnits().?});
652 try writeFieldAlign(field_ty, union_type.alignment, w, zcu);
628653 }
629 try w.print(" {f}{f}{f};\n", .{
654 try w.print("{f}{f}{f};\n", .{
630655 field_cty.fmtDeclaratorPrefix(zcu),
631656 fmtIdentSolo(field_name),
632657 field_cty.fmtDeclaratorSuffix(zcu),
......@@ -635,6 +660,40 @@ fn defineUnionExtern(
635660 try w.writeByte('}');
636661 if (pack) try w.writeByte(')');
637662 try w.writeAll(";\n");
663
664 try writeStaticAssertLayout(ty, name_cty, w, zcu);
665}
666
667/// Writes an annotation which, placed before a struct/union field declaration with field type `ty`,
668/// will specify that field as having the given alignment.
669fn writeFieldAlign(
670 ty: Type,
671 alignment: Alignment,
672 w: *Writer,
673 zcu: *const Zcu,
674) Writer.Error!void {
675 if (alignment.compareStrict(.lt, ty.abiAlignment(zcu))) {
676 try w.print("zig_under_align({d}) ", .{alignment.toByteUnits().?});
677 } else {
678 try w.print("zig_align({d}) ", .{alignment.toByteUnits().?});
679 }
680}
681
682/// Emits static assertions that the size and alignment of `cty` match those of the Zig type `ty`.
683fn writeStaticAssertLayout(
684 ty: Type,
685 cty: CType,
686 w: *Writer,
687 zcu: *const Zcu,
688) Writer.Error!void {
689 try w.print(
690 \\zig_static_assert(sizeof ({f}) == {d}, "incorrect size");
691 \\zig_static_assert(_Alignof ({f}) == {d}, "incorrect alignment");
692 \\
693 , .{
694 cty.fmtTypeName(zcu), ty.abiSize(zcu),
695 cty.fmtTypeName(zcu), ty.abiAlignment(zcu).toByteUnits().?,
696 });
638697}
639698
640699const std = @import("std");