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 @@...@@ -151,6 +151,14 @@
151#define zig_has_attribute(attribute) 0151#define zig_has_attribute(attribute) 0
152#endif152#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
154#if __STDC_VERSION__ >= 202311L162#if __STDC_VERSION__ >= 202311L
155#define zig_threadlocal thread_local163#define zig_threadlocal thread_local
156#elif __STDC_VERSION__ >= 201112L164#elif __STDC_VERSION__ >= 201112L
src/codegen/c/type/render_defs.zig+80-21
...@@ -246,6 +246,8 @@ pub fn defineComplete(...@@ -246,6 +246,8 @@ pub fn defineComplete(
246 ptr_cty.fmtDeclaratorPrefix(zcu),246 ptr_cty.fmtDeclaratorPrefix(zcu),
247 ptr_cty.fmtDeclaratorSuffix(zcu),247 ptr_cty.fmtDeclaratorSuffix(zcu),
248 });248 });
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.
249 },251 },
250 .optional => switch (CType.classifyOptional(ty, zcu)) {252 .optional => switch (CType.classifyOptional(ty, zcu)) {
251 .error_set,253 .error_set,
...@@ -260,6 +262,7 @@ pub fn defineComplete(...@@ -260,6 +262,7 @@ pub fn defineComplete(
260 name_cty.fmtTypeName(zcu),262 name_cty.fmtTypeName(zcu),
261 ty.fmt(pt),263 ty.fmt(pt),
262 });264 });
265 try writeStaticAssertLayout(ty, name_cty, w, zcu);
263 },266 },
264267
265 .@"struct" => {268 .@"struct" => {
...@@ -277,6 +280,7 @@ pub fn defineComplete(...@@ -277,6 +280,7 @@ pub fn defineComplete(
277 payload_cty.fmtDeclaratorPrefix(zcu),280 payload_cty.fmtDeclaratorPrefix(zcu),
278 payload_cty.fmtDeclaratorSuffix(zcu),281 payload_cty.fmtDeclaratorSuffix(zcu),
279 });282 });
283 try writeStaticAssertLayout(ty, name_cty, w, zcu);
280 },284 },
281 },285 },
282 .array => if (ty.hasRuntimeBits(zcu)) {286 .array => if (ty.hasRuntimeBits(zcu)) {
...@@ -297,6 +301,7 @@ pub fn defineComplete(...@@ -297,6 +301,7 @@ pub fn defineComplete(
297 array_cty.fmtDeclaratorSuffix(zcu),301 array_cty.fmtDeclaratorSuffix(zcu),
298 ty.fmt(pt),302 ty.fmt(pt),
299 });303 });
304 try writeStaticAssertLayout(ty, name_cty, w, zcu);
300 },305 },
301 .vector => if (ty.hasRuntimeBits(zcu)) {306 .vector => if (ty.hasRuntimeBits(zcu)) {
302 const name_cty: CType = .{ .vec = ty };307 const name_cty: CType = .{ .vec = ty };
...@@ -312,6 +317,7 @@ pub fn defineComplete(...@@ -312,6 +317,7 @@ pub fn defineComplete(
312 array_cty.fmtDeclaratorSuffix(zcu),317 array_cty.fmtDeclaratorSuffix(zcu),
313 ty.fmt(pt),318 ty.fmt(pt),
314 });319 });
320 try writeStaticAssertLayout(ty, name_cty, w, zcu);
315 },321 },
316 else => {},322 else => {},
317 }323 }
...@@ -374,18 +380,21 @@ fn defineTuple(...@@ -374,18 +380,21 @@ fn defineTuple(
374 zig_offset = field_align.forward(zig_offset);380 zig_offset = field_align.forward(zig_offset);
375 if (!field_ty.hasRuntimeBits(zcu)) continue;381 if (!field_ty.hasRuntimeBits(zcu)) continue;
376 c_offset = field_align.forward(c_offset);382 c_offset = field_align.forward(c_offset);
383 try w.writeByte(' ');
377 if (zig_offset == 0 and overalign) {384 if (zig_offset == 0 and overalign) {
378 // This is the first field; specify its alignment to align the tuple.385 // 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);
380 } else if (zig_offset > c_offset) {387 } else if (zig_offset > c_offset) {
381 // This field needs to be overaligned compared to what its offset would otherwise be.388 // This field needs to be overaligned compared to what its offset would otherwise be.
382 const need_align: Alignment = .fromLog2Units(@ctz(zig_offset));389 const need_align: Alignment = .minStrict(
383 try w.print(" zig_align({d})", .{need_align.toByteUnits().?});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);
384 c_offset = need_align.forward(c_offset);394 c_offset = need_align.forward(c_offset);
385 assert(c_offset == zig_offset);
386 }395 }
387 const field_cty: CType = try .lower(field_ty, deps, arena, zcu);396 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", .{
389 field_cty.fmtDeclaratorPrefix(zcu),398 field_cty.fmtDeclaratorPrefix(zcu),
390 field_index,399 field_index,
391 field_cty.fmtDeclaratorSuffix(zcu),400 field_cty.fmtDeclaratorSuffix(zcu),
...@@ -395,6 +404,8 @@ fn defineTuple(...@@ -395,6 +404,8 @@ fn defineTuple(
395 c_offset += field_size;404 c_offset += field_size;
396 }405 }
397 try w.writeAll("};\n");406 try w.writeAll("};\n");
407
408 try writeStaticAssertLayout(ty, name_cty, w, zcu);
398}409}
399fn defineStruct(410fn defineStruct(
400 ty: Type,411 ty: Type,
...@@ -420,6 +431,8 @@ fn defineStruct(...@@ -420,6 +431,8 @@ fn defineStruct(
420 const natural_offset = natural_align.forward(offset);431 const natural_offset = natural_align.forward(offset);
421 const actual_offset = struct_type.field_offsets.get(ip)[field_index];432 const actual_offset = struct_type.field_offsets.get(ip)[field_index];
422 if (actual_offset < natural_offset) break :pack true;433 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;
423 offset = actual_offset + field_ty.abiSize(zcu);436 offset = actual_offset + field_ty.abiSize(zcu);
424 }437 }
425 break :pack false;438 break :pack false;
...@@ -459,22 +472,22 @@ fn defineStruct(...@@ -459,22 +472,22 @@ fn defineStruct(
459 false => natural_align.forward(offset),472 false => natural_align.forward(offset),
460 };473 };
461 const actual_offset = struct_type.field_offsets.get(ip)[field_index];474 const actual_offset = struct_type.field_offsets.get(ip)[field_index];
475 try w.writeByte(' ');
462 if (actual_offset == 0 and overalign) {476 if (actual_offset == 0 and overalign) {
463 // This is the first field; specify its alignment to align the struct.477 // 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);
465 } else if (actual_offset > natural_offset) {479 } else if (actual_offset > natural_offset) {
466 // This field needs to be underaligned or overaligned compared to what its480 // This field needs to be underaligned or overaligned compared to what its
467 // offset would otherwise be.481 // offset would otherwise be.
468 const need_align: Alignment = .fromLog2Units(@ctz(actual_offset));482 const need_align: Alignment = .minStrict(
469 if (need_align.compareStrict(.lt, natural_align)) {483 struct_type.alignment, // don't make the struct more aligned than it should be
470 try w.print(" zig_under_align({d})", .{need_align.toByteUnits().?});484 .fromLog2Units(@ctz(actual_offset)),
471 } else {485 );
472 try w.print(" zig_align({d})", .{need_align.toByteUnits().?});486 try writeFieldAlign(field_ty, need_align, w, zcu);
473 }
474 }487 }
475 const field_cty: CType = try .lower(field_ty, deps, arena, zcu);488 const field_cty: CType = try .lower(field_ty, deps, arena, zcu);
476 const field_name = struct_type.field_names.get(ip)[field_index].toSlice(ip);489 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", .{
478 field_cty.fmtDeclaratorPrefix(zcu),491 field_cty.fmtDeclaratorPrefix(zcu),
479 fmtIdentSolo(field_name),492 fmtIdentSolo(field_name),
480 field_cty.fmtDeclaratorSuffix(zcu),493 field_cty.fmtDeclaratorSuffix(zcu),
...@@ -485,6 +498,8 @@ fn defineStruct(...@@ -485,6 +498,8 @@ fn defineStruct(
485 try w.writeByte('}');498 try w.writeByte('}');
486 if (pack) try w.writeByte(')');499 if (pack) try w.writeByte(')');
487 try w.writeAll(";\n");500 try w.writeAll(";\n");
501
502 try writeStaticAssertLayout(ty, name_cty, w, zcu);
488}503}
489fn defineUnionAuto(504fn defineUnionAuto(
490 ty: Type,505 ty: Type,
...@@ -500,12 +515,20 @@ fn defineUnionAuto(...@@ -500,12 +515,20 @@ fn defineUnionAuto(
500 const union_type = ip.loadUnionType(ty.toIntern());515 const union_type = ip.loadUnionType(ty.toIntern());
501 const enum_tag_ty: Type = .fromInterned(union_type.enum_tag_type);516 const enum_tag_ty: Type = .fromInterned(union_type.enum_tag_type);
502517
518 const layout = Type.getUnionLayout(union_type, zcu);
519
503 // If there are any underaligned fields, we need to byte-pack the union.520 // If there are any underaligned fields, we need to byte-pack the union.
504 const pack: bool = for (union_type.field_types.get(ip)) |field_ty_ip| {521 const pack: bool = for (union_type.field_types.get(ip)) |field_ty_ip| {
505 const field_ty: Type = .fromInterned(field_ty_ip);522 const field_ty: Type = .fromInterned(field_ty_ip);
506 if (!field_ty.hasRuntimeBits(zcu)) continue;523 if (!field_ty.hasRuntimeBits(zcu)) continue;
507 const natural_align = field_ty.abiAlignment(zcu);524 const natural_align = field_ty.abiAlignment(zcu);
508 if (natural_align.compareStrict(.gt, union_type.alignment)) break true;525 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;
509 } else false;532 } else false;
510533
511 // If the alignment of other fields would not give the union sufficient alignment, we534 // If the alignment of other fields would not give the union sufficient alignment, we
...@@ -536,6 +559,10 @@ fn defineUnionAuto(...@@ -536,6 +559,10 @@ fn defineUnionAuto(
536 });559 });
537 if (payload_has_bits) {560 if (payload_has_bits) {
538 try w.writeByte(' ');561 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 }
539 if (pack) try w.writeAll("zig_packed(");566 if (pack) try w.writeAll("zig_packed(");
540 try w.writeAll("union {\n");567 try w.writeAll("union {\n");
541 for (0..enum_tag_ty.enumFieldCount(zcu)) |field_index| {568 for (0..enum_tag_ty.enumFieldCount(zcu)) |field_index| {
...@@ -543,12 +570,7 @@ fn defineUnionAuto(...@@ -543,12 +570,7 @@ fn defineUnionAuto(
543 if (!field_ty.hasRuntimeBits(zcu)) continue;570 if (!field_ty.hasRuntimeBits(zcu)) continue;
544 const field_name = enum_tag_ty.enumFieldName(field_index, zcu).toSlice(ip);571 const field_name = enum_tag_ty.enumFieldName(field_index, zcu).toSlice(ip);
545 const field_cty: CType = try .lower(field_ty, deps, arena, zcu);572 const field_cty: CType = try .lower(field_ty, deps, arena, zcu);
546 try w.writeAll(" ");573 try w.print(" {f}{f}{f};\n", .{
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", .{
552 field_cty.fmtDeclaratorPrefix(zcu),574 field_cty.fmtDeclaratorPrefix(zcu),
553 fmtIdentSolo(field_name),575 fmtIdentSolo(field_name),
554 field_cty.fmtDeclaratorSuffix(zcu),576 field_cty.fmtDeclaratorSuffix(zcu),
...@@ -566,6 +588,8 @@ fn defineUnionAuto(...@@ -566,6 +588,8 @@ fn defineUnionAuto(
566 });588 });
567 }589 }
568 try w.writeAll("};\n");590 try w.writeAll("};\n");
591
592 try writeStaticAssertLayout(ty, name_cty, w, zcu);
569}593}
570fn defineUnionExtern(594fn defineUnionExtern(
571 ty: Type,595 ty: Type,
...@@ -622,11 +646,12 @@ fn defineUnionExtern(...@@ -622,11 +646,12 @@ fn defineUnionExtern(
622 if (!field_ty.hasRuntimeBits(zcu)) continue;646 if (!field_ty.hasRuntimeBits(zcu)) continue;
623 const field_name = enum_tag_ty.enumFieldName(field_index, zcu).toSlice(ip);647 const field_name = enum_tag_ty.enumFieldName(field_index, zcu).toSlice(ip);
624 const field_cty: CType = try .lower(field_ty, deps, arena, zcu);648 const field_cty: CType = try .lower(field_ty, deps, arena, zcu);
649 try w.writeByte(' ');
625 if (overalign and field_index == 0) {650 if (overalign and field_index == 0) {
626 // This is the first field; specify its alignment to align the union.651 // 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);
628 }653 }
629 try w.print(" {f}{f}{f};\n", .{654 try w.print("{f}{f}{f};\n", .{
630 field_cty.fmtDeclaratorPrefix(zcu),655 field_cty.fmtDeclaratorPrefix(zcu),
631 fmtIdentSolo(field_name),656 fmtIdentSolo(field_name),
632 field_cty.fmtDeclaratorSuffix(zcu),657 field_cty.fmtDeclaratorSuffix(zcu),
...@@ -635,6 +660,40 @@ fn defineUnionExtern(...@@ -635,6 +660,40 @@ fn defineUnionExtern(
635 try w.writeByte('}');660 try w.writeByte('}');
636 if (pack) try w.writeByte(')');661 if (pack) try w.writeByte(')');
637 try w.writeAll(";\n");662 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 });
638}697}
639698
640const std = @import("std");699const std = @import("std");