Commit 7894703ee7

Jacob Young <jacobly0@users.noreply.github.com>
2025-07-27 03:39:43
aarch64: implement more optional/error union/union support
1 parent 69abc94
lib/compiler_rt.zig
@@ -240,7 +240,7 @@ comptime {
     _ = @import("compiler_rt/udivmodti4.zig");
 
     // extra
-    if (builtin.zig_backend != .stage2_aarch64) _ = @import("compiler_rt/os_version_check.zig");
+    _ = @import("compiler_rt/os_version_check.zig");
     _ = @import("compiler_rt/emutls.zig");
     _ = @import("compiler_rt/arm.zig");
     _ = @import("compiler_rt/aulldiv.zig");
src/codegen/aarch64/Select.zig
@@ -584,7 +584,7 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void {
 
             air_body_index += 1;
         },
-        .@"try", .try_cold, .try_ptr, .try_ptr_cold => {
+        .@"try", .try_cold => {
             const pl_op = air_data[@intFromEnum(air_inst_index)].pl_op;
             const extra = isel.air.extraData(Air.Try, pl_op.payload);
 
@@ -596,6 +596,18 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void {
             air_inst_index = air_body[air_body_index];
             continue :air_tag air_tags[@intFromEnum(air_inst_index)];
         },
+        .try_ptr, .try_ptr_cold => {
+            const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl;
+            const extra = isel.air.extraData(Air.TryPtr, ty_pl.payload);
+
+            try isel.analyzeUse(extra.data.ptr);
+            try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len]));
+            try isel.def_order.putNoClobber(gpa, air_inst_index, {});
+
+            air_body_index += 1;
+            air_inst_index = air_body[air_body_index];
+            continue :air_tag air_tags[@intFromEnum(air_inst_index)];
+        },
         .ret, .ret_safe, .ret_load => {
             const un_op = air_data[@intFromEnum(air_inst_index)].un_op;
             isel.returns = true;
@@ -4760,17 +4772,62 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory,
             const error_set_part_vi = try error_set_part_it.only(isel);
             const error_set_part_mat = try error_set_part_vi.?.matReg(isel);
             try isel.emit(.cbz(
-                switch (error_set_part_vi.?.size(isel)) {
-                    else => unreachable,
-                    1...4 => error_set_part_mat.ra.w(),
-                    5...8 => error_set_part_mat.ra.x(),
-                },
+                error_set_part_mat.ra.w(),
                 @intCast((isel.instructions.items.len + 1 - cont_label) << 2),
             ));
             try error_set_part_mat.finish(isel);
 
             if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
         },
+        .try_ptr, .try_ptr_cold => {
+            const ty_pl = air.data(air.inst_index).ty_pl;
+            const extra = isel.air.extraData(Air.TryPtr, ty_pl.payload);
+            const error_union_ty = isel.air.typeOf(extra.data.ptr, ip).childType(zcu);
+            const error_union_info = ip.indexToKey(error_union_ty.toIntern()).error_union_type;
+            const payload_ty: ZigType = .fromInterned(error_union_info.payload_type);
+
+            const error_union_ptr_vi = try isel.use(extra.data.ptr);
+            const error_union_ptr_mat = try error_union_ptr_vi.matReg(isel);
+            if (isel.live_values.fetchRemove(air.inst_index)) |payload_ptr_vi| unused: {
+                defer payload_ptr_vi.value.deref(isel);
+                switch (codegen.errUnionPayloadOffset(ty_pl.ty.toType().childType(zcu), zcu)) {
+                    0 => try payload_ptr_vi.value.move(isel, extra.data.ptr),
+                    else => |payload_offset| {
+                        const payload_ptr_ra = try payload_ptr_vi.value.defReg(isel) orelse break :unused;
+                        const lo12: u12 = @truncate(payload_offset >> 0);
+                        const hi12: u12 = @intCast(payload_offset >> 12);
+                        if (hi12 > 0) try isel.emit(.add(
+                            payload_ptr_ra.x(),
+                            if (lo12 > 0) payload_ptr_ra.x() else error_union_ptr_mat.ra.x(),
+                            .{ .shifted_immediate = .{ .immediate = hi12, .lsl = .@"12" } },
+                        ));
+                        if (lo12 > 0) try isel.emit(.add(payload_ptr_ra.x(), error_union_ptr_mat.ra.x(), .{ .immediate = lo12 }));
+                    },
+                }
+            }
+
+            const cont_label = isel.instructions.items.len;
+            const cont_live_registers = isel.live_registers;
+            try isel.body(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len]));
+            try isel.merge(&cont_live_registers, .{});
+
+            const error_set_ra = try isel.allocIntReg();
+            defer isel.freeReg(error_set_ra);
+            try isel.loadReg(
+                error_set_ra,
+                ZigType.fromInterned(error_union_info.error_set_type).abiSize(zcu),
+                .unsigned,
+                error_union_ptr_mat.ra,
+                codegen.errUnionErrorOffset(payload_ty, zcu),
+            );
+            try error_union_ptr_mat.finish(isel);
+            try isel.emit(.cbz(
+                error_set_ra.w(),
+                @intCast((isel.instructions.items.len + 1 - cont_label) << 2),
+            ));
+
+            if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
+        },
         .dbg_stmt => {
             if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
         },
@@ -5403,14 +5460,6 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory,
             }
             if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
         },
-        .optional_payload_ptr => {
-            if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| {
-                defer dst_vi.value.deref(isel);
-                const ty_op = air.data(air.inst_index).ty_op;
-                try dst_vi.value.move(isel, ty_op.operand);
-            }
-            if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
-        },
         .optional_payload => {
             if (isel.live_values.fetchRemove(air.inst_index)) |payload_vi| unused: {
                 defer payload_vi.value.deref(isel);
@@ -5429,6 +5478,37 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory,
             }
             if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
         },
+        .optional_payload_ptr => {
+            if (isel.live_values.fetchRemove(air.inst_index)) |payload_ptr_vi| {
+                defer payload_ptr_vi.value.deref(isel);
+                const ty_op = air.data(air.inst_index).ty_op;
+                try payload_ptr_vi.value.move(isel, ty_op.operand);
+            }
+            if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
+        },
+        .optional_payload_ptr_set => {
+            if (isel.live_values.fetchRemove(air.inst_index)) |payload_ptr_vi| {
+                defer payload_ptr_vi.value.deref(isel);
+                const ty_op = air.data(air.inst_index).ty_op;
+                const opt_ty = isel.air.typeOf(ty_op.operand, ip).childType(zcu);
+                if (!opt_ty.optionalReprIsPayload(zcu)) {
+                    const opt_ptr_vi = try isel.use(ty_op.operand);
+                    const opt_ptr_mat = try opt_ptr_vi.matReg(isel);
+                    const has_value_ra = try isel.allocIntReg();
+                    defer isel.freeReg(has_value_ra);
+                    try isel.storeReg(
+                        has_value_ra,
+                        1,
+                        opt_ptr_mat.ra,
+                        opt_ty.optionalChild(zcu).abiSize(zcu),
+                    );
+                    try opt_ptr_mat.finish(isel);
+                    try isel.emit(.movz(has_value_ra.w(), 1, .{ .lsl = .@"0" }));
+                }
+                try payload_ptr_vi.value.move(isel, ty_op.operand);
+            }
+            if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
+        },
         .wrap_optional => {
             if (isel.live_values.fetchRemove(air.inst_index)) |opt_vi| unused: {
                 defer opt_vi.value.deref(isel);
@@ -5486,6 +5566,93 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory,
             }
             if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
         },
+        .unwrap_errunion_payload_ptr => {
+            if (isel.live_values.fetchRemove(air.inst_index)) |payload_ptr_vi| unused: {
+                defer payload_ptr_vi.value.deref(isel);
+                const ty_op = air.data(air.inst_index).ty_op;
+                switch (codegen.errUnionPayloadOffset(ty_op.ty.toType().childType(zcu), zcu)) {
+                    0 => try payload_ptr_vi.value.move(isel, ty_op.operand),
+                    else => |payload_offset| {
+                        const payload_ptr_ra = try payload_ptr_vi.value.defReg(isel) orelse break :unused;
+                        const error_union_ptr_vi = try isel.use(ty_op.operand);
+                        const error_union_ptr_mat = try error_union_ptr_vi.matReg(isel);
+                        const lo12: u12 = @truncate(payload_offset >> 0);
+                        const hi12: u12 = @intCast(payload_offset >> 12);
+                        if (hi12 > 0) try isel.emit(.add(
+                            payload_ptr_ra.x(),
+                            if (lo12 > 0) payload_ptr_ra.x() else error_union_ptr_mat.ra.x(),
+                            .{ .shifted_immediate = .{ .immediate = hi12, .lsl = .@"12" } },
+                        ));
+                        if (lo12 > 0) try isel.emit(.add(payload_ptr_ra.x(), error_union_ptr_mat.ra.x(), .{ .immediate = lo12 }));
+                        try error_union_ptr_mat.finish(isel);
+                    },
+                }
+            }
+            if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
+        },
+        .unwrap_errunion_err_ptr => {
+            if (isel.live_values.fetchRemove(air.inst_index)) |error_ptr_vi| unused: {
+                defer error_ptr_vi.value.deref(isel);
+                const ty_op = air.data(air.inst_index).ty_op;
+                switch (codegen.errUnionErrorOffset(
+                    isel.air.typeOf(ty_op.operand, ip).childType(zcu).errorUnionPayload(zcu),
+                    zcu,
+                )) {
+                    0 => try error_ptr_vi.value.move(isel, ty_op.operand),
+                    else => |error_offset| {
+                        const error_ptr_ra = try error_ptr_vi.value.defReg(isel) orelse break :unused;
+                        const error_union_ptr_vi = try isel.use(ty_op.operand);
+                        const error_union_ptr_mat = try error_union_ptr_vi.matReg(isel);
+                        const lo12: u12 = @truncate(error_offset >> 0);
+                        const hi12: u12 = @intCast(error_offset >> 12);
+                        if (hi12 > 0) try isel.emit(.add(
+                            error_ptr_ra.x(),
+                            if (lo12 > 0) error_ptr_ra.x() else error_union_ptr_mat.ra.x(),
+                            .{ .shifted_immediate = .{ .immediate = hi12, .lsl = .@"12" } },
+                        ));
+                        if (lo12 > 0) try isel.emit(.add(error_ptr_ra.x(), error_union_ptr_mat.ra.x(), .{ .immediate = lo12 }));
+                        try error_union_ptr_mat.finish(isel);
+                    },
+                }
+            }
+            if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
+        },
+        .errunion_payload_ptr_set => {
+            if (isel.live_values.fetchRemove(air.inst_index)) |payload_ptr_vi| unused: {
+                defer payload_ptr_vi.value.deref(isel);
+                const ty_op = air.data(air.inst_index).ty_op;
+                const payload_ty = ty_op.ty.toType().childType(zcu);
+                const error_union_ty = isel.air.typeOf(ty_op.operand, ip).childType(zcu);
+                const error_set_size = error_union_ty.errorUnionSet(zcu).abiSize(zcu);
+                const error_union_ptr_vi = try isel.use(ty_op.operand);
+                const error_union_ptr_mat = try error_union_ptr_vi.matReg(isel);
+                if (error_set_size > 0) try isel.storeReg(
+                    .zr,
+                    error_set_size,
+                    error_union_ptr_mat.ra,
+                    codegen.errUnionErrorOffset(payload_ty, zcu),
+                );
+                switch (codegen.errUnionPayloadOffset(payload_ty, zcu)) {
+                    0 => {
+                        try error_union_ptr_mat.finish(isel);
+                        try payload_ptr_vi.value.move(isel, ty_op.operand);
+                    },
+                    else => |payload_offset| {
+                        const payload_ptr_ra = try payload_ptr_vi.value.defReg(isel) orelse break :unused;
+                        const lo12: u12 = @truncate(payload_offset >> 0);
+                        const hi12: u12 = @intCast(payload_offset >> 12);
+                        if (hi12 > 0) try isel.emit(.add(
+                            payload_ptr_ra.x(),
+                            if (lo12 > 0) payload_ptr_ra.x() else error_union_ptr_mat.ra.x(),
+                            .{ .shifted_immediate = .{ .immediate = hi12, .lsl = .@"12" } },
+                        ));
+                        if (lo12 > 0) try isel.emit(.add(payload_ptr_ra.x(), error_union_ptr_mat.ra.x(), .{ .immediate = lo12 }));
+                        try error_union_ptr_mat.finish(isel);
+                    },
+                }
+            }
+            if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
+        },
         .wrap_errunion_payload => {
             if (isel.live_values.fetchRemove(air.inst_index)) |error_union_vi| {
                 defer error_union_vi.value.deref(isel);
@@ -5672,6 +5839,32 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory,
             }
             if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
         },
+        .set_union_tag => {
+            const bin_op = air.data(air.inst_index).bin_op;
+            const union_ty = isel.air.typeOf(bin_op.lhs, ip).childType(zcu);
+            const union_layout = union_ty.unionGetLayout(zcu);
+            const tag_vi = try isel.use(bin_op.rhs);
+            const union_ptr_vi = try isel.use(bin_op.lhs);
+            const union_ptr_mat = try union_ptr_vi.matReg(isel);
+            try tag_vi.store(isel, isel.air.typeOf(bin_op.rhs, ip), union_ptr_mat.ra, .{
+                .offset = union_layout.tagOffset(),
+            });
+            try union_ptr_mat.finish(isel);
+            if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
+        },
+        .get_union_tag => {
+            if (isel.live_values.fetchRemove(air.inst_index)) |tag_vi| {
+                defer tag_vi.value.deref(isel);
+                const ty_op = air.data(air.inst_index).ty_op;
+                const union_ty = isel.air.typeOf(ty_op.operand, ip);
+                const union_layout = union_ty.unionGetLayout(zcu);
+                const union_vi = try isel.use(ty_op.operand);
+                var tag_part_it = union_vi.field(union_ty, union_layout.tagOffset(), union_layout.tag_size);
+                const tag_part_vi = try tag_part_it.only(isel);
+                try tag_vi.value.copy(isel, ty_op.ty.toType(), tag_part_vi.?);
+            }
+            if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
+        },
         .slice => {
             if (isel.live_values.fetchRemove(air.inst_index)) |slice_vi| {
                 defer slice_vi.value.deref(isel);
@@ -6541,8 +6734,8 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory,
                 if (ptr_part_ra == null and len_part_ra == null) break :unused;
 
                 const un_op = air.data(air.inst_index).un_op;
-                const err_vi = try isel.use(un_op);
-                const err_mat = try err_vi.matReg(isel);
+                const error_vi = try isel.use(un_op);
+                const error_mat = try error_vi.matReg(isel);
                 const ptr_ra = try isel.allocIntReg();
                 defer isel.freeReg(ptr_ra);
                 const start_ra, const end_ra = range_ras: {
@@ -6573,7 +6766,7 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory,
                 if (len_part_ra) |_| try isel.emit(.sub(end_ra.w(), end_ra.w(), .{ .immediate = 1 }));
                 try isel.emit(.ldp(start_ra.w(), end_ra.w(), .{ .base = start_ra.x() }));
                 try isel.emit(.add(start_ra.x(), ptr_ra.x(), .{ .extended_register = .{
-                    .register = err_mat.ra.w(),
+                    .register = error_mat.ra.w(),
                     .extend = switch (zcu.errorSetBits()) {
                         else => unreachable,
                         1...8 => .{ .uxtb = 2 },
@@ -6591,7 +6784,7 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory,
                     .reloc = .{ .label = @intCast(isel.instructions.items.len) },
                 });
                 try isel.emit(.adrp(ptr_ra.x(), 0));
-                try err_mat.finish(isel);
+                try error_mat.finish(isel);
             }
             if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
         },
@@ -6893,11 +7086,11 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory,
                 try isel.emit(.csinc(is_ra.w(), .wzr, .wzr, .invert(.ls)));
 
                 const un_op = air.data(air.inst_index).un_op;
-                const err_vi = try isel.use(un_op);
-                const err_mat = try err_vi.matReg(isel);
+                const error_vi = try isel.use(un_op);
+                const error_mat = try error_vi.matReg(isel);
                 const ptr_ra = try isel.allocIntReg();
                 defer isel.freeReg(ptr_ra);
-                try isel.emit(.subs(.wzr, err_mat.ra.w(), .{ .register = ptr_ra.w() }));
+                try isel.emit(.subs(.wzr, error_mat.ra.w(), .{ .register = ptr_ra.w() }));
                 try isel.lazy_relocs.append(gpa, .{
                     .symbol = .{ .kind = .const_data, .ty = .anyerror_type },
                     .reloc = .{ .label = @intCast(isel.instructions.items.len) },
@@ -6908,7 +7101,7 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory,
                     .reloc = .{ .label = @intCast(isel.instructions.items.len) },
                 });
                 try isel.emit(.adrp(ptr_ra.x(), 0));
-                try err_mat.finish(isel);
+                try error_mat.finish(isel);
             }
             if (air.next()) |next_air_tag| continue :air_tag next_air_tag;
         },
@@ -9529,8 +9722,14 @@ pub const Value = struct {
                         } },
                     },
                     .struct_type => {
-                        const min_part_log2_stride: u5 = if (size > 16) 4 else if (size > 8) 3 else 0;
                         const loaded_struct = ip.loadStructType(ty.toIntern());
+                        switch (loaded_struct.layout) {
+                            .auto, .@"extern" => {},
+                            .@"packed" => continue :type_key .{
+                                .int_type = ip.indexToKey(loaded_struct.backingIntTypeUnordered(ip)).int_type,
+                            },
+                        }
+                        const min_part_log2_stride: u5 = if (size > 16) 4 else if (size > 8) 3 else 0;
                         if (loaded_struct.field_types.len > Value.max_parts and
                             (std.math.divCeil(u64, size, @as(u64, 1) << min_part_log2_stride) catch unreachable) > Value.max_parts)
                             return isel.fail("Value.FieldPartIterator.next({f})", .{isel.fmtType(ty)});
@@ -9638,6 +9837,77 @@ pub const Value = struct {
                             if (part.is_vector) subpart_vi.setIsVector(isel);
                         }
                     },
+                    .union_type => {
+                        const loaded_union = ip.loadUnionType(ty.toIntern());
+                        switch (loaded_union.flagsUnordered(ip).layout) {
+                            .auto, .@"extern" => {},
+                            .@"packed" => continue :type_key .{ .int_type = .{
+                                .signedness = .unsigned,
+                                .bits = @intCast(ty.bitSize(zcu)),
+                            } },
+                        }
+                        const min_part_log2_stride: u5 = if (size > 16) 4 else if (size > 8) 3 else 0;
+                        if ((std.math.divCeil(u64, size, @as(u64, 1) << min_part_log2_stride) catch unreachable) > Value.max_parts)
+                            return isel.fail("Value.FieldPartIterator.next({f})", .{isel.fmtType(ty)});
+                        const union_layout = ZigType.getUnionLayout(loaded_union, zcu);
+                        const alignment = vi.alignment(isel);
+                        const tag_offset = union_layout.tagOffset();
+                        const payload_offset = union_layout.payloadOffset();
+                        const Part = struct { offset: u64, size: u64, signedness: ?std.builtin.Signedness };
+                        var parts: [2]Part = undefined;
+                        var parts_len: Value.PartsLen = 0;
+                        var field_end: u64 = 0;
+                        for (0..2) |field_index| {
+                            const field: enum { tag, payload } = switch (field_index) {
+                                0 => if (tag_offset < payload_offset) .tag else .payload,
+                                1 => if (tag_offset < payload_offset) .payload else .tag,
+                                else => unreachable,
+                            };
+                            const field_size, const field_begin = switch (field) {
+                                .tag => .{ union_layout.tag_size, tag_offset },
+                                .payload => .{ union_layout.payload_size, payload_offset },
+                            };
+                            if (field_begin >= offset + size) break;
+                            if (field_size == 0) continue;
+                            field_end = field_begin + field_size;
+                            if (field_end <= offset) continue;
+                            const field_signedness = field_signedness: switch (field) {
+                                .tag => {
+                                    if (offset >= field_begin and offset + size <= field_begin + field_size) {
+                                        ty = .fromInterned(loaded_union.enum_tag_ty);
+                                        ty_size = field_size;
+                                        offset -= field_begin;
+                                        continue :type_key ip.indexToKey(loaded_union.enum_tag_ty);
+                                    }
+                                    break :field_signedness ip.indexToKey(loaded_union.loadTagType(ip).tag_ty).int_type.signedness;
+                                },
+                                .payload => null,
+                            };
+                            if (parts_len > 0) combine: {
+                                const prev_part = &parts[parts_len - 1];
+                                const combined_size = field_end - prev_part.offset;
+                                if (combined_size > @as(u64, 1) << @min(
+                                    min_part_log2_stride,
+                                    alignment.toLog2Units(),
+                                    @ctz(prev_part.offset),
+                                )) break :combine;
+                                prev_part.size = combined_size;
+                                prev_part.signedness = null;
+                                continue;
+                            }
+                            parts[parts_len] = .{
+                                .offset = field_begin,
+                                .size = field_size,
+                                .signedness = field_signedness,
+                            };
+                            parts_len += 1;
+                        }
+                        vi.setParts(isel, parts_len);
+                        for (parts[0..parts_len]) |part| {
+                            const subpart_vi = vi.addPart(isel, part.offset - offset, part.size);
+                            if (part.signedness) |signedness| subpart_vi.setSignedness(isel, signedness);
+                        }
+                    },
                     .opaque_type, .func_type => continue :type_key .{ .simple_type = .anyopaque },
                     .enum_type => continue :type_key ip.indexToKey(ip.loadEnumType(ty.toIntern()).tag_ty),
                     .error_set_type,
@@ -10075,7 +10345,11 @@ pub const Value = struct {
                                     };
                                 },
                                 .slice => |slice| switch (offset) {
-                                    0 => continue :constant_key .{ .ptr = ip.indexToKey(slice.ptr).ptr },
+                                    0 => continue :constant_key switch (ip.indexToKey(slice.ptr)) {
+                                        else => unreachable,
+                                        .undef => |undef| .{ .undef = undef },
+                                        .ptr => |ptr| .{ .ptr = ptr },
+                                    },
                                     else => {
                                         assert(offset == @divExact(isel.target.ptrBitWidth(), 8));
                                         offset = 0;
@@ -11128,16 +11402,14 @@ pub const CallAbiIterator = struct {
                     {
                         const error_set_ty: ZigType = .fromInterned(error_union_type.error_set_type);
                         const offset = codegen.errUnionErrorOffset(payload_ty, zcu);
-                        const size = error_set_ty.abiSize(zcu);
-                        const end = offset % 8 + size;
+                        const end = offset % 8 + error_set_ty.abiSize(zcu);
                         const part_index: usize = @intCast(offset / 8);
                         sizes[part_index] = @max(sizes[part_index], @min(end, 8));
                         if (end > 8) sizes[part_index + 1] = @max(sizes[part_index + 1], end - 8);
                     }
                     {
                         const offset = codegen.errUnionPayloadOffset(payload_ty, zcu);
-                        const size = payload_ty.abiSize(zcu);
-                        const end = offset % 8 + size;
+                        const end = offset % 8 + payload_ty.abiSize(zcu);
                         const part_index: usize = @intCast(offset / 8);
                         sizes[part_index] = @max(sizes[part_index], @min(end, 8));
                         if (end > 8) sizes[part_index + 1] = @max(sizes[part_index + 1], end - 8);
@@ -11181,8 +11453,14 @@ pub const CallAbiIterator = struct {
                 => unreachable,
             },
             .struct_type => {
-                const size = wip_vi.size(isel);
                 const loaded_struct = ip.loadStructType(ty.toIntern());
+                switch (loaded_struct.layout) {
+                    .auto, .@"extern" => {},
+                    .@"packed" => continue :type_key .{
+                        .int_type = ip.indexToKey(loaded_struct.backingIntTypeUnordered(ip)).int_type,
+                    },
+                }
+                const size = wip_vi.size(isel);
                 if (size <= 16 * 4) homogeneous_aggregate: {
                     const fdt = homogeneousStructBaseType(zcu, &loaded_struct) orelse break :homogeneous_aggregate;
                     const parts_len = @shrExact(size, fdt.log2Size());
@@ -11267,6 +11545,40 @@ pub const CallAbiIterator = struct {
                     else => it.indirect(isel, wip_vi),
                 }
             },
+            .union_type => {
+                const loaded_union = ip.loadUnionType(ty.toIntern());
+                switch (loaded_union.flagsUnordered(ip).layout) {
+                    .auto, .@"extern" => {},
+                    .@"packed" => continue :type_key .{ .int_type = .{
+                        .signedness = .unsigned,
+                        .bits = @intCast(ty.bitSize(zcu)),
+                    } },
+                }
+                switch (wip_vi.size(isel)) {
+                    0 => unreachable,
+                    1...8 => it.integer(isel, wip_vi),
+                    9...16 => {
+                        const union_layout = ZigType.getUnionLayout(loaded_union, zcu);
+                        var sizes: [2]u64 = @splat(0);
+                        {
+                            const offset = union_layout.tagOffset();
+                            const end = offset % 8 + union_layout.tag_size;
+                            const part_index: usize = @intCast(offset / 8);
+                            sizes[part_index] = @max(sizes[part_index], @min(end, 8));
+                            if (end > 8) sizes[part_index + 1] = @max(sizes[part_index + 1], end - 8);
+                        }
+                        {
+                            const offset = union_layout.payloadOffset();
+                            const end = offset % 8 + union_layout.payload_size;
+                            const part_index: usize = @intCast(offset / 8);
+                            sizes[part_index] = @max(sizes[part_index], @min(end, 8));
+                            if (end > 8) sizes[part_index + 1] = @max(sizes[part_index + 1], end - 8);
+                        }
+                        it.integers(isel, wip_vi, sizes);
+                    },
+                    else => it.indirect(isel, wip_vi),
+                }
+            },
             .opaque_type, .func_type => continue :type_key .{ .simple_type = .anyopaque },
             .enum_type => continue :type_key ip.indexToKey(ip.loadEnumType(ty.toIntern()).tag_ty),
             .error_set_type,
test/behavior/decl_literals.zig
@@ -33,7 +33,6 @@ test "decl literal with pointer" {
 }
 
 test "call decl literal with optional" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
@@ -74,7 +73,6 @@ test "call decl literal" {
 }
 
 test "call decl literal with error union" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
 
     const S = struct {
test/behavior/error.zig
@@ -943,7 +943,6 @@ test "optional error set function parameter" {
 }
 
 test "returning an error union containing a type with no runtime bits" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
 
test/behavior/field_parent_ptr.zig
@@ -587,7 +587,6 @@ test "@fieldParentPtr extern struct last zero-bit field" {
 }
 
 test "@fieldParentPtr unaligned packed struct" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
@@ -726,7 +725,6 @@ test "@fieldParentPtr unaligned packed struct" {
 }
 
 test "@fieldParentPtr aligned packed struct" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
test/behavior/inline_switch.zig
@@ -43,7 +43,6 @@ test "inline switch enums" {
 
 const U = union(E) { a: void, b: u2, c: u3, d: u4 };
 test "inline switch unions" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
test/behavior/optional.zig
@@ -319,7 +319,6 @@ test "assigning to an unwrapped optional field in an inline loop" {
 }
 
 test "coerce an anon struct literal to optional struct" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
@@ -447,7 +446,6 @@ test "optional pointer to zero bit optional payload" {
 }
 
 test "optional pointer to zero bit error union payload" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
test/behavior/struct.zig
@@ -797,7 +797,6 @@ test "fn with C calling convention returns struct by value" {
 }
 
 test "non-packed struct with u128 entry in union" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
@@ -1026,7 +1025,6 @@ test "packed struct with undefined initializers" {
 }
 
 test "for loop over pointers to struct, getting field from struct pointer" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
@@ -1093,7 +1091,6 @@ test "anon init through error unions and optionals" {
 }
 
 test "anon init through optional" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
@@ -1113,7 +1110,6 @@ test "anon init through optional" {
 }
 
 test "anon init through error union" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
@@ -1398,7 +1394,6 @@ test "struct has only one reference" {
 }
 
 test "no dependency loop on pointer to optional struct" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
 
test/behavior/switch.zig
@@ -299,7 +299,6 @@ fn switchProngWithVarFn(a: SwitchProngWithVarEnum) !void {
 }
 
 test "switch on enum using pointer capture" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
@@ -360,7 +359,6 @@ fn testSwitchHandleAllCasesRange(x: u8) u8 {
 }
 
 test "switch on union with some prongs capturing" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
@@ -975,8 +973,6 @@ test "switch prong captures range" {
 }
 
 test "prong with inline call to unreachable" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
-
     const U = union(enum) {
         void: void,
         bool: bool,
test/behavior/switch_on_captured_error.zig
@@ -6,7 +6,6 @@ const expectEqual = std.testing.expectEqual;
 const builtin = @import("builtin");
 
 test "switch on error union catch capture" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
test/behavior/union.zig
@@ -160,7 +160,6 @@ test "unions embedded in aggregate types" {
 }
 
 test "constant tagged union with payload" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
@@ -263,7 +262,6 @@ fn testComparison() !void {
 }
 
 test "comparison between union and enum literal" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
@@ -279,7 +277,6 @@ const TheUnion = union(TheTag) {
     C: i32,
 };
 test "cast union to tag type of union" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
@@ -300,7 +297,6 @@ test "union field access gives the enum values" {
 }
 
 test "cast tag type of union to union" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
@@ -316,7 +312,6 @@ const Value2 = union(Letter2) {
 };
 
 test "implicit cast union to its tag type" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
@@ -495,7 +490,6 @@ test "initialize global array of union" {
 }
 
 test "update the tag value for zero-sized unions" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
@@ -734,7 +728,6 @@ test "union with only 1 field casted to its enum type which has enum value speci
 }
 
 test "@intFromEnum works on unions" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
@@ -848,7 +841,6 @@ test "@unionInit stored to a const" {
 }
 
 test "@unionInit can modify a union type" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
@@ -871,7 +863,6 @@ test "@unionInit can modify a union type" {
 }
 
 test "@unionInit can modify a pointer value" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
@@ -990,7 +981,6 @@ test "function call result coerces from tagged union to the tag" {
 }
 
 test "switching on non exhaustive union" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
@@ -1176,7 +1166,6 @@ test "comptime equality of extern unions with same tag" {
 }
 
 test "union tag is set when initiated as a temporary value at runtime" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
@@ -1216,7 +1205,6 @@ test "extern union most-aligned field is smaller" {
 }
 
 test "return an extern union from C calling convention" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
@@ -1248,7 +1236,6 @@ test "return an extern union from C calling convention" {
 }
 
 test "noreturn field in union" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
@@ -1481,8 +1468,6 @@ test "reinterpreting enum value inside packed union" {
 }
 
 test "access the tag of a global tagged union" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
-
     const U = union(enum) {
         a,
         b: u8,
@@ -2111,7 +2096,6 @@ test "runtime union init, most-aligned field != largest" {
 }
 
 test "copied union field doesn't alias source" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
@@ -2334,8 +2318,6 @@ test "assign global tagged union" {
 }
 
 test "set mutable union by switching on same union" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
-
     const U = union(enum) {
         foo,
         bar: usize,
test/behavior/union_with_members.zig
@@ -17,7 +17,6 @@ const ET = union(enum) {
 };
 
 test "enum with members" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
test/behavior/while.zig
@@ -344,7 +344,6 @@ test "else continue outer while" {
 }
 
 test "try terminating an infinite loop" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;