authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-06-04 09:35:01+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-06-05 09:20:31+02:00
log5434f85c47f6412a8d5faf681419c15533bb388c
treec096f78678b0d36c0b44915e1f242c51aa5463b0
parent7b31e360a1c000e1e050a00c9b9bff290c7f3d97

cbe: improve struct/union defs on 32-bit targets

Some of these changes are kind of hacky, and arguably indicate that we should deal with field alignment in CBE a bit differently, but for now these changes do manage to fix *some* bugs. This solves the problems demonstrated in #35552. However, when trying to actually build that example, GCC emits these errors from the generated C code in `compiler_rt.c`: ``` /tmp/ccgboCZq.s: Assembler messages: /tmp/ccgboCZq.s:29321: Error: bad expression -- `bl #__udivmodsi4' /tmp/ccgboCZq.s:29344: Error: bad expression -- `bl #__divmodsi4' /tmp/ccgboCZq.s:30200: Error: bad expression -- `bl #__udivmoddi4' /tmp/ccgboCZq.s:30263: Error: bad expression -- `bl #__divmoddi4' ``` This looks to be an unrelated bug, which I am not going to attempt to tackle for now. The static assertions passing indicates that we're now emitting at least semi-reasonable type definitions in this example, so the bug actually tracked by #35552 is fixed. Resolves: https://codeberg.org/ziglang/zig/issues/35552

2 files changed, 67 insertions(+), 30 deletions(-)

src/Type.zig+7-2
...@@ -935,7 +935,7 @@ pub fn abiAlignment(ty: Type, zcu: *const Zcu) Alignment {...@@ -935,7 +935,7 @@ pub fn abiAlignment(ty: Type, zcu: *const Zcu) Alignment {
935 const bytes = ((elem_bits * vector_type.len) + 7) / 8;935 const bytes = ((elem_bits * vector_type.len) + 7) / 8;
936 return .fromByteUnits(std.math.ceilPowerOfTwoAssert(u32, bytes));936 return .fromByteUnits(std.math.ceilPowerOfTwoAssert(u32, bytes));
937 },937 },
938 .stage2_c, .stage2_wasm => return Type.fromInterned(vector_type.child).abiAlignment(zcu),938 .stage2_c, .stage2_wasm => return Type.fromInterned(vector_type.child).defaultStructFieldAlignment(.auto, zcu),
939 .stage2_x86_64 => {939 .stage2_x86_64 => {
940 if (vector_type.child == .bool_type) {940 if (vector_type.child == .bool_type) {
941 if (vector_type.len > 256 and target.cpu.has(.x86, .avx512f)) return .@"64";941 if (vector_type.len > 256 and target.cpu.has(.x86, .avx512f)) return .@"64";
...@@ -2492,7 +2492,12 @@ pub fn defaultStructFieldAlignment(...@@ -2492,7 +2492,12 @@ pub fn defaultStructFieldAlignment(
2492 };2492 };
2493 const abi_align = field_ty.abiAlignment(zcu);2493 const abi_align = field_ty.abiAlignment(zcu);
2494 assert(abi_align != .none);2494 assert(abi_align != .none);
2495 if (overalign_big_int and field_ty.isAbiInt(zcu) and field_ty.intInfo(zcu).bits >= 128) {2495 // We check for anything over 64 here, because the C backend will lower e.g. u64 to a 128-bit
2496 // integer, which has 16-byte alignment.
2497 if (overalign_big_int and
2498 ((field_ty.isAbiInt(zcu) and field_ty.intInfo(zcu).bits > 64) or
2499 (field_ty.toIntern() == .f80_type and zcu.getTarget().cTypeBitSize(.longdouble) != 80)))
2500 {
2496 return abi_align.maxStrict(.@"16");2501 return abi_align.maxStrict(.@"16");
2497 }2502 }
2498 return abi_align;2503 return abi_align;
src/codegen/c/type/render_defs.zig+60-28
...@@ -284,8 +284,9 @@ pub fn defineComplete(...@@ -284,8 +284,9 @@ pub fn defineComplete(
284 },284 },
285 },285 },
286 .array => if (ty.hasRuntimeBits(zcu)) {286 .array => if (ty.hasRuntimeBits(zcu)) {
287 const elem_ty = ty.childType(zcu);
287 const name_cty: CType = .{ .arr = ty };288 const name_cty: CType = .{ .arr = ty };
288 const elem_cty: CType = try .lower(ty.childType(zcu), deps, arena, zcu);289 const elem_cty: CType = try .lower(elem_ty, deps, arena, zcu);
289 const array_cty: CType = .{ .array = .{290 const array_cty: CType = .{ .array = .{
290 .len = ty.arrayLenIncludingSentinel(zcu),291 .len = ty.arrayLenIncludingSentinel(zcu),
291 .elem_ty = &elem_cty,292 .elem_ty = &elem_cty,
...@@ -295,17 +296,28 @@ pub fn defineComplete(...@@ -295,17 +296,28 @@ pub fn defineComplete(
295 break :nonstring Value.compareHetero(s, .neq, .zero_comptime_int, zcu);296 break :nonstring Value.compareHetero(s, .neq, .zero_comptime_int, zcu);
296 },297 },
297 } };298 } };
298 try w.print("{f} {{ {f}array{f}; }}; /* {f} */\n", .{299 if (elem_ty.defaultStructFieldAlignment(.auto, zcu) == elem_ty.abiAlignment(zcu)) {
299 name_cty.fmtTypeName(zcu),300 try w.print("{f} {{ {f}array{f}; }}; /* {f} */\n", .{
300 array_cty.fmtDeclaratorPrefix(zcu),301 name_cty.fmtTypeName(zcu),
301 array_cty.fmtDeclaratorSuffix(zcu),302 array_cty.fmtDeclaratorPrefix(zcu),
302 ty.fmt(pt),303 array_cty.fmtDeclaratorSuffix(zcu),
303 });304 ty.fmt(pt),
305 });
306 } else {
307 try w.print("zig_packed({f} {{ zig_under_align({d}) {f}array{f}; }}); /* {f} */\n", .{
308 name_cty.fmtTypeName(zcu),
309 elem_ty.abiAlignment(zcu).toByteUnits().?,
310 array_cty.fmtDeclaratorPrefix(zcu),
311 array_cty.fmtDeclaratorSuffix(zcu),
312 ty.fmt(pt),
313 });
314 }
304 try writeStaticAssertLayout(ty, name_cty, w, zcu);315 try writeStaticAssertLayout(ty, name_cty, w, zcu);
305 },316 },
306 .vector => if (ty.hasRuntimeBits(zcu)) {317 .vector => if (ty.hasRuntimeBits(zcu)) {
318 const elem_ty = ty.childType(zcu);
307 const name_cty: CType = .{ .vec = ty };319 const name_cty: CType = .{ .vec = ty };
308 const elem_cty: CType = try .lower(ty.childType(zcu), deps, arena, zcu);320 const elem_cty: CType = try .lower(elem_ty, deps, arena, zcu);
309 const array_cty: CType = .{ .array = .{321 const array_cty: CType = .{ .array = .{
310 .len = ty.arrayLenIncludingSentinel(zcu),322 .len = ty.arrayLenIncludingSentinel(zcu),
311 .elem_ty = &elem_cty,323 .elem_ty = &elem_cty,
...@@ -351,21 +363,39 @@ fn defineTuple(...@@ -351,21 +363,39 @@ fn defineTuple(
351 const ip = &zcu.intern_pool;363 const ip = &zcu.intern_pool;
352 const tuple = ip.indexToKey(ty.toIntern()).tuple_type;364 const tuple = ip.indexToKey(ty.toIntern()).tuple_type;
353365
354 // Fields cannot be underaligned, because tuple fields cannot have specified alignments.
355 // However, overaligned fields are possible thanks to intermediate zero-bit fields.
356
357 const tuple_align = ty.abiAlignment(zcu);366 const tuple_align = ty.abiAlignment(zcu);
358367
368 // If there are any underaligned fields, we need to byte-pack the tuple.
369 const pack: bool = pack: {
370 var offset: u64 = 0;
371 for (tuple.types.get(ip)) |field_ty_ip| {
372 const field_ty: Type = .fromInterned(field_ty_ip);
373 if (!field_ty.hasRuntimeBits(zcu)) continue;
374 const natural_align = field_ty.defaultStructFieldAlignment(.auto, zcu);
375 const natural_offset = natural_align.forward(offset);
376 offset = field_ty.abiAlignment(zcu).forward(offset);
377 if (offset < natural_offset) break :pack true;
378 // Also pack if any field is more aligned than the tuple should be.
379 if (natural_align.compareStrict(.gt, tuple_align)) break :pack true;
380 offset += field_ty.abiSize(zcu);
381 }
382 break :pack false;
383 };
384
359 // If the alignment of other fields would not give the tuple sufficient alignment, we385 // If the alignment of other fields would not give the tuple sufficient alignment, we
360 // need to align the first field (which does not affect its offset, because 0 is always386 // need to align the first field (which does not affect its offset, because 0 is always
361 // well-aligned) to indirectly specify the tuple alignment.387 // well-aligned) to indirectly specify the tuple alignment.
362 const overalign: bool = for (tuple.types.get(ip)) |field_ty_ip| {388 const overalign: bool = switch (pack) {
363 const field_ty: Type = .fromInterned(field_ty_ip);389 true => tuple_align.compareStrict(.gt, .@"1"),
364 if (!field_ty.hasRuntimeBits(zcu)) continue;390 false => for (tuple.types.get(ip)) |field_ty_ip| {
365 const natural_align = field_ty.defaultStructFieldAlignment(.auto, zcu);391 const field_ty: Type = .fromInterned(field_ty_ip);
366 if (natural_align.compareStrict(.gte, tuple_align)) break false;392 if (!field_ty.hasRuntimeBits(zcu)) continue;
367 } else true;393 const natural_align = field_ty.defaultStructFieldAlignment(.auto, zcu);
394 if (natural_align.compareStrict(.gte, tuple_align)) break false;
395 } else true,
396 };
368397
398 if (pack) try w.writeAll("zig_packed(");
369 const name_cty: CType = .{ .@"struct" = ty };399 const name_cty: CType = .{ .@"struct" = ty };
370 try w.print("{f} {{ /* {f} */\n", .{400 try w.print("{f} {{ /* {f} */\n", .{
371 name_cty.fmtTypeName(zcu),401 name_cty.fmtTypeName(zcu),
...@@ -376,18 +406,18 @@ fn defineTuple(...@@ -376,18 +406,18 @@ fn defineTuple(
376 for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty_ip, field_val_ip, field_index| {406 for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty_ip, field_val_ip, field_index| {
377 if (field_val_ip != .none) continue; // `comptime` field407 if (field_val_ip != .none) continue; // `comptime` field
378 const field_ty: Type = .fromInterned(field_ty_ip);408 const field_ty: Type = .fromInterned(field_ty_ip);
379 const field_align = field_ty.abiAlignment(zcu);409 zig_offset = field_ty.abiAlignment(zcu).forward(zig_offset);
380 zig_offset = field_align.forward(zig_offset);
381 if (!field_ty.hasRuntimeBits(zcu)) continue;410 if (!field_ty.hasRuntimeBits(zcu)) continue;
382 c_offset = field_align.forward(c_offset);411 if (!pack) c_offset = field_ty.defaultStructFieldAlignment(.auto, zcu).forward(c_offset);
383 try w.writeByte(' ');412 try w.writeByte(' ');
384 if (zig_offset == 0 and overalign) {413 if (zig_offset == 0 and overalign) {
385 // This is the first field; specify its alignment to align the tuple.414 // This is the first field; specify its alignment to align the tuple.
386 try writeFieldAlign(field_ty, tuple_align, w, zcu);415 try writeFieldAlign(field_ty, tuple_align, w, zcu);
387 } else if (zig_offset > c_offset) {416 } else if (zig_offset > c_offset) {
388 // This field needs to be overaligned compared to what its offset would otherwise be.417 // This field needs to be underaligned or overaligned compared to what its
418 // offset would otherwise be.
389 const need_align: Alignment = .minStrict(419 const need_align: Alignment = .minStrict(
390 tuple_align, // don't make the struct more aligned than it should be420 tuple_align, // don't make the tuple more aligned than it should be
391 .fromLog2Units(@ctz(zig_offset)),421 .fromLog2Units(@ctz(zig_offset)),
392 );422 );
393 try writeFieldAlign(field_ty, need_align, w, zcu);423 try writeFieldAlign(field_ty, need_align, w, zcu);
...@@ -403,7 +433,9 @@ fn defineTuple(...@@ -403,7 +433,9 @@ fn defineTuple(
403 zig_offset += field_size;433 zig_offset += field_size;
404 c_offset += field_size;434 c_offset += field_size;
405 }435 }
406 try w.writeAll("};\n");436 try w.writeByte('}');
437 if (pack) try w.writeByte(')');
438 try w.writeAll(";\n");
407439
408 try writeStaticAssertLayout(ty, name_cty, w, zcu);440 try writeStaticAssertLayout(ty, name_cty, w, zcu);
409}441}
...@@ -521,7 +553,7 @@ fn defineUnionAuto(...@@ -521,7 +553,7 @@ fn defineUnionAuto(
521 const pack: bool = for (union_type.field_types.get(ip)) |field_ty_ip| {553 const pack: bool = for (union_type.field_types.get(ip)) |field_ty_ip| {
522 const field_ty: Type = .fromInterned(field_ty_ip);554 const field_ty: Type = .fromInterned(field_ty_ip);
523 if (!field_ty.hasRuntimeBits(zcu)) continue;555 if (!field_ty.hasRuntimeBits(zcu)) continue;
524 const natural_align = field_ty.abiAlignment(zcu);556 const natural_align = field_ty.defaultStructFieldAlignment(.auto, zcu);
525 if (natural_align.compareStrict(.gt, union_type.alignment)) break true;557 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 would558 // 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 not559 // otherwise be padding on the payload union, because if the most-aligned union field is not
...@@ -539,7 +571,7 @@ fn defineUnionAuto(...@@ -539,7 +571,7 @@ fn defineUnionAuto(
539 false => for (union_type.field_types.get(ip)) |field_ty_ip| {571 false => for (union_type.field_types.get(ip)) |field_ty_ip| {
540 const field_ty: Type = .fromInterned(field_ty_ip);572 const field_ty: Type = .fromInterned(field_ty_ip);
541 if (!field_ty.hasRuntimeBits(zcu)) continue;573 if (!field_ty.hasRuntimeBits(zcu)) continue;
542 const natural_align = field_ty.abiAlignment(zcu);574 const natural_align = field_ty.defaultStructFieldAlignment(.auto, zcu);
543 if (natural_align.compareStrict(.gte, union_type.alignment)) break false;575 if (natural_align.compareStrict(.gte, union_type.alignment)) break false;
544 } else overalign: {576 } else overalign: {
545 if (union_type.has_runtime_tag) {577 if (union_type.has_runtime_tag) {
...@@ -610,7 +642,7 @@ fn defineUnionExtern(...@@ -610,7 +642,7 @@ fn defineUnionExtern(
610 const pack: bool = for (union_type.field_types.get(ip)) |field_ty_ip| {642 const pack: bool = for (union_type.field_types.get(ip)) |field_ty_ip| {
611 const field_ty: Type = .fromInterned(field_ty_ip);643 const field_ty: Type = .fromInterned(field_ty_ip);
612 if (!field_ty.hasRuntimeBits(zcu)) continue;644 if (!field_ty.hasRuntimeBits(zcu)) continue;
613 const natural_align = field_ty.abiAlignment(zcu);645 const natural_align = field_ty.defaultStructFieldAlignment(.@"extern", zcu);
614 if (natural_align.compareStrict(.gt, union_type.alignment)) break true;646 if (natural_align.compareStrict(.gt, union_type.alignment)) break true;
615 } else false;647 } else false;
616648
...@@ -622,7 +654,7 @@ fn defineUnionExtern(...@@ -622,7 +654,7 @@ fn defineUnionExtern(
622 false => for (union_type.field_types.get(ip)) |field_ty_ip| {654 false => for (union_type.field_types.get(ip)) |field_ty_ip| {
623 const field_ty: Type = .fromInterned(field_ty_ip);655 const field_ty: Type = .fromInterned(field_ty_ip);
624 if (!field_ty.hasRuntimeBits(zcu)) continue;656 if (!field_ty.hasRuntimeBits(zcu)) continue;
625 const natural_align = field_ty.abiAlignment(zcu);657 const natural_align = field_ty.defaultStructFieldAlignment(.@"extern", zcu);
626 if (natural_align.compareStrict(.gte, union_type.alignment)) break false;658 if (natural_align.compareStrict(.gte, union_type.alignment)) break false;
627 } else overalign: {659 } else overalign: {
628 if (union_type.has_runtime_tag) {660 if (union_type.has_runtime_tag) {
...@@ -672,7 +704,7 @@ fn writeFieldAlign(...@@ -672,7 +704,7 @@ fn writeFieldAlign(
672 w: *Writer,704 w: *Writer,
673 zcu: *const Zcu,705 zcu: *const Zcu,
674) Writer.Error!void {706) Writer.Error!void {
675 if (alignment.compareStrict(.lt, ty.abiAlignment(zcu))) {707 if (alignment.compareStrict(.lt, ty.defaultStructFieldAlignment(.auto, zcu))) {
676 try w.print("zig_under_align({d}) ", .{alignment.toByteUnits().?});708 try w.print("zig_under_align({d}) ", .{alignment.toByteUnits().?});
677 } else {709 } else {
678 try w.print("zig_align({d}) ", .{alignment.toByteUnits().?});710 try w.print("zig_align({d}) ", .{alignment.toByteUnits().?});