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 {
935935 const bytes = ((elem_bits * vector_type.len) + 7) / 8;
936936 return .fromByteUnits(std.math.ceilPowerOfTwoAssert(u32, bytes));
937937 },
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),
939939 .stage2_x86_64 => {
940940 if (vector_type.child == .bool_type) {
941941 if (vector_type.len > 256 and target.cpu.has(.x86, .avx512f)) return .@"64";
......@@ -2492,7 +2492,12 @@ pub fn defaultStructFieldAlignment(
24922492 };
24932493 const abi_align = field_ty.abiAlignment(zcu);
24942494 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 {
24962501 return abi_align.maxStrict(.@"16");
24972502 }
24982503 return abi_align;
src/codegen/c/type/render_defs.zig+60-28
......@@ -284,8 +284,9 @@ pub fn defineComplete(
284284 },
285285 },
286286 .array => if (ty.hasRuntimeBits(zcu)) {
287 const elem_ty = ty.childType(zcu);
287288 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);
289290 const array_cty: CType = .{ .array = .{
290291 .len = ty.arrayLenIncludingSentinel(zcu),
291292 .elem_ty = &elem_cty,
......@@ -295,17 +296,28 @@ pub fn defineComplete(
295296 break :nonstring Value.compareHetero(s, .neq, .zero_comptime_int, zcu);
296297 },
297298 } };
298 try w.print("{f} {{ {f}array{f}; }}; /* {f} */\n", .{
299 name_cty.fmtTypeName(zcu),
300 array_cty.fmtDeclaratorPrefix(zcu),
301 array_cty.fmtDeclaratorSuffix(zcu),
302 ty.fmt(pt),
303 });
299 if (elem_ty.defaultStructFieldAlignment(.auto, zcu) == elem_ty.abiAlignment(zcu)) {
300 try w.print("{f} {{ {f}array{f}; }}; /* {f} */\n", .{
301 name_cty.fmtTypeName(zcu),
302 array_cty.fmtDeclaratorPrefix(zcu),
303 array_cty.fmtDeclaratorSuffix(zcu),
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 }
304315 try writeStaticAssertLayout(ty, name_cty, w, zcu);
305316 },
306317 .vector => if (ty.hasRuntimeBits(zcu)) {
318 const elem_ty = ty.childType(zcu);
307319 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);
309321 const array_cty: CType = .{ .array = .{
310322 .len = ty.arrayLenIncludingSentinel(zcu),
311323 .elem_ty = &elem_cty,
......@@ -351,21 +363,39 @@ fn defineTuple(
351363 const ip = &zcu.intern_pool;
352364 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
357366 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
359385 // If the alignment of other fields would not give the tuple sufficient alignment, we
360386 // need to align the first field (which does not affect its offset, because 0 is always
361387 // well-aligned) to indirectly specify the tuple alignment.
362 const overalign: bool = for (tuple.types.get(ip)) |field_ty_ip| {
363 const field_ty: Type = .fromInterned(field_ty_ip);
364 if (!field_ty.hasRuntimeBits(zcu)) continue;
365 const natural_align = field_ty.defaultStructFieldAlignment(.auto, zcu);
366 if (natural_align.compareStrict(.gte, tuple_align)) break false;
367 } else true;
388 const overalign: bool = switch (pack) {
389 true => tuple_align.compareStrict(.gt, .@"1"),
390 false => for (tuple.types.get(ip)) |field_ty_ip| {
391 const field_ty: Type = .fromInterned(field_ty_ip);
392 if (!field_ty.hasRuntimeBits(zcu)) continue;
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(");
369399 const name_cty: CType = .{ .@"struct" = ty };
370400 try w.print("{f} {{ /* {f} */\n", .{
371401 name_cty.fmtTypeName(zcu),
......@@ -376,18 +406,18 @@ fn defineTuple(
376406 for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty_ip, field_val_ip, field_index| {
377407 if (field_val_ip != .none) continue; // `comptime` field
378408 const field_ty: Type = .fromInterned(field_ty_ip);
379 const field_align = field_ty.abiAlignment(zcu);
380 zig_offset = field_align.forward(zig_offset);
409 zig_offset = field_ty.abiAlignment(zcu).forward(zig_offset);
381410 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);
383412 try w.writeByte(' ');
384413 if (zig_offset == 0 and overalign) {
385414 // This is the first field; specify its alignment to align the tuple.
386415 try writeFieldAlign(field_ty, tuple_align, w, zcu);
387416 } 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.
389419 const need_align: Alignment = .minStrict(
390 tuple_align, // don't make the struct more aligned than it should be
420 tuple_align, // don't make the tuple more aligned than it should be
391421 .fromLog2Units(@ctz(zig_offset)),
392422 );
393423 try writeFieldAlign(field_ty, need_align, w, zcu);
......@@ -403,7 +433,9 @@ fn defineTuple(
403433 zig_offset += field_size;
404434 c_offset += field_size;
405435 }
406 try w.writeAll("};\n");
436 try w.writeByte('}');
437 if (pack) try w.writeByte(')');
438 try w.writeAll(";\n");
407439
408440 try writeStaticAssertLayout(ty, name_cty, w, zcu);
409441}
......@@ -521,7 +553,7 @@ fn defineUnionAuto(
521553 const pack: bool = for (union_type.field_types.get(ip)) |field_ty_ip| {
522554 const field_ty: Type = .fromInterned(field_ty_ip);
523555 if (!field_ty.hasRuntimeBits(zcu)) continue;
524 const natural_align = field_ty.abiAlignment(zcu);
556 const natural_align = field_ty.defaultStructFieldAlignment(.auto, zcu);
525557 if (natural_align.compareStrict(.gt, union_type.alignment)) break true;
526558 // The tag will immediately follow the payload. This layout may put the tag in what would
527559 // otherwise be padding on the payload union, because if the most-aligned union field is not
......@@ -539,7 +571,7 @@ fn defineUnionAuto(
539571 false => for (union_type.field_types.get(ip)) |field_ty_ip| {
540572 const field_ty: Type = .fromInterned(field_ty_ip);
541573 if (!field_ty.hasRuntimeBits(zcu)) continue;
542 const natural_align = field_ty.abiAlignment(zcu);
574 const natural_align = field_ty.defaultStructFieldAlignment(.auto, zcu);
543575 if (natural_align.compareStrict(.gte, union_type.alignment)) break false;
544576 } else overalign: {
545577 if (union_type.has_runtime_tag) {
......@@ -610,7 +642,7 @@ fn defineUnionExtern(
610642 const pack: bool = for (union_type.field_types.get(ip)) |field_ty_ip| {
611643 const field_ty: Type = .fromInterned(field_ty_ip);
612644 if (!field_ty.hasRuntimeBits(zcu)) continue;
613 const natural_align = field_ty.abiAlignment(zcu);
645 const natural_align = field_ty.defaultStructFieldAlignment(.@"extern", zcu);
614646 if (natural_align.compareStrict(.gt, union_type.alignment)) break true;
615647 } else false;
616648
......@@ -622,7 +654,7 @@ fn defineUnionExtern(
622654 false => for (union_type.field_types.get(ip)) |field_ty_ip| {
623655 const field_ty: Type = .fromInterned(field_ty_ip);
624656 if (!field_ty.hasRuntimeBits(zcu)) continue;
625 const natural_align = field_ty.abiAlignment(zcu);
657 const natural_align = field_ty.defaultStructFieldAlignment(.@"extern", zcu);
626658 if (natural_align.compareStrict(.gte, union_type.alignment)) break false;
627659 } else overalign: {
628660 if (union_type.has_runtime_tag) {
......@@ -672,7 +704,7 @@ fn writeFieldAlign(
672704 w: *Writer,
673705 zcu: *const Zcu,
674706) Writer.Error!void {
675 if (alignment.compareStrict(.lt, ty.abiAlignment(zcu))) {
707 if (alignment.compareStrict(.lt, ty.defaultStructFieldAlignment(.auto, zcu))) {
676708 try w.print("zig_under_align({d}) ", .{alignment.toByteUnits().?});
677709 } else {
678710 try w.print("zig_align({d}) ", .{alignment.toByteUnits().?});