//! Record layout code adapted from https://github.com/mahkoh/repr-c
//! Licensed under MIT license: https://github.com/mahkoh/repr-c/tree/master/repc/facade

const std = @import("std");

const Attribute = @import("Attribute.zig");
const Compilation = @import("Compilation.zig");
const Parser = @import("Parser.zig");
const Target = @import("Target.zig");
const TypeStore = @import("TypeStore.zig");
const QualType = TypeStore.QualType;
const Type = TypeStore.Type;
const Record = Type.Record;
const Field = Record.Field;
const RecordLayout = Type.Record.Layout;
const FieldLayout = Type.Record.Field.Layout;

const BITS_PER_BYTE = 8;

const OngoingBitfield = struct {
    size_bits: u64,
    unused_size_bits: u64,
};

pub const Error = error{Overflow};

fn alignForward(addr: u64, alignment: u64) !u64 {
    const forward_addr = try std.math.add(u64, addr, alignment - 1);
    return std.mem.alignBackward(u64, forward_addr, alignment);
}

const SysVContext = struct {
    /// Does the record have an __attribute__((packed)) annotation.
    attr_packed: bool,
    /// The value of #pragma pack(N) at the type level if any.
    max_field_align_bits: ?u64,
    /// The alignment of this record.
    aligned_bits: u32,
    is_union: bool,
    /// The size of the record. This might not be a multiple of 8 if the record contains bit-fields.
    /// For structs, this is also the offset of the first bit after the last field.
    size_bits: u64,
    /// non-null if the previous field was a non-zero-sized bit-field. Only used by MinGW.
    ongoing_bitfield: ?OngoingBitfield,

    comp: *const Compilation,

    fn init(qt: QualType, comp: *const Compilation, pragma_pack: ?u8) SysVContext {
        const pack_value: ?u64 = if (pragma_pack) |pak| @as(u64, pak) * BITS_PER_BYTE else null;
        const req_align = @as(u32, (qt.requestedAlignment(comp) orelse 1)) * BITS_PER_BYTE;
        return SysVContext{
            .attr_packed = qt.hasAttribute(comp, .@"packed"),
            .max_field_align_bits = pack_value,
            .aligned_bits = req_align,
            .is_union = qt.is(comp, .@"union"),
            .size_bits = 0,
            .comp = comp,
            .ongoing_bitfield = null,
        };
    }

    fn layoutFields(self: *SysVContext, fields: []Type.Record.Field) !void {
        for (fields) |*field| {
            if (field.qt.isInvalid()) continue;
            const type_layout = computeLayout(field.qt, self.comp);

            const attributes = field.attributes(self.comp);
            if (self.comp.target.isMinGW()) {
                field.layout = try self.layoutMinGWField(field, attributes, type_layout);
            } else {
                if (field.bit_width.unpack()) |bit_width| {
                    field.layout = try self.layoutBitField(attributes, type_layout, field.name_tok != 0, bit_width);
                } else {
                    field.layout = try self.layoutRegularField(attributes, type_layout);
                }
            }
        }
    }

    /// On MinGW the alignment of the field is calculated in the usual way except that the alignment of
    /// the underlying type is ignored in three cases
    /// - the field is packed
    /// - the field is a bit-field and the previous field was a non-zero-sized bit-field with the same type size
    /// - the field is a zero-sized bit-field and the previous field was not a non-zero-sized bit-field
    /// See test case 0068.
    fn ignoreTypeAlignment(is_attr_packed: bool, bit_width: ?u32, ongoing_bitfield: ?OngoingBitfield, fld_layout: RecordLayout) bool {
        if (is_attr_packed) return true;
        if (bit_width) |width| {
            if (ongoing_bitfield) |ongoing| {
                if (ongoing.size_bits == fld_layout.size_bits) return true;
            } else {
                if (width == 0) return true;
            }
        }
        return false;
    }

    fn layoutMinGWField(
        self: *SysVContext,
        field: *const Field,
        field_attrs: []const Attribute,
        field_layout: RecordLayout,
    ) !FieldLayout {
        const annotation_alignment_bits = BITS_PER_BYTE * (QualType.annotationAlignment(self.comp, Attribute.Iterator.initSlice(field_attrs)) orelse 1);
        const is_attr_packed = self.attr_packed or isPacked(field_attrs);
        const ignore_type_alignment = ignoreTypeAlignment(is_attr_packed, field.bit_width.unpack(), self.ongoing_bitfield, field_layout);

        var field_alignment_bits: u64 = field_layout.field_alignment_bits;
        if (ignore_type_alignment) {
            field_alignment_bits = BITS_PER_BYTE;
        }
        field_alignment_bits = @max(field_alignment_bits, annotation_alignment_bits);
        if (self.max_field_align_bits) |bits| {
            field_alignment_bits = @min(field_alignment_bits, bits);
        }

        // The field affects the record alignment in one of three cases
        // - the field is a regular field
        // - the field is a zero-width bit-field following a non-zero-width bit-field
        // - the field is a non-zero-width bit-field and not packed.
        // See test case 0069.
        const update_record_alignment =
            field.bit_width == .null or
            (field.bit_width.unpack().? == 0 and self.ongoing_bitfield != null) or
            (field.bit_width.unpack().? != 0 and !is_attr_packed);

        // If a field affects the alignment of a record, the alignment is calculated in the
        // usual way except that __attribute__((packed)) is ignored on a zero-width bit-field.
        // See test case 0068.
        if (update_record_alignment) {
            var ty_alignment_bits = field_layout.field_alignment_bits;
            if (is_attr_packed and (field.bit_width == .null or field.bit_width.unpack().? != 0)) {
                ty_alignment_bits = BITS_PER_BYTE;
            }
            ty_alignment_bits = @max(ty_alignment_bits, annotation_alignment_bits);
            if (self.max_field_align_bits) |bits| {
                ty_alignment_bits = @intCast(@min(ty_alignment_bits, bits));
            }
            self.aligned_bits = @max(self.aligned_bits, ty_alignment_bits);
        }

        // NOTE: ty_alignment_bits and field_alignment_bits are different in the following case:
        // Y = { size: 64, alignment: 64 }struct {
        //     { offset: 0, size: 1 }c { size: 8, alignment: 8 }char:1,
        //     @attr_packed _ { size: 64, alignment: 64 }long long:0,
        //     { offset: 8, size: 8 }d { size: 8, alignment: 8 }char,
        // }
        if (field.bit_width.unpack()) |bit_width| {
            return self.layoutBitFieldMinGW(field_layout.size_bits, field_alignment_bits, field.name_tok != 0, bit_width);
        } else {
            return self.layoutRegularFieldMinGW(field_layout.size_bits, field_alignment_bits);
        }
    }

    fn layoutBitFieldMinGW(
        self: *SysVContext,
        ty_size_bits: u64,
        field_alignment_bits: u64,
        is_named: bool,
        width: u64,
    ) !FieldLayout {
        std.debug.assert(width <= ty_size_bits); // validated in parser

        // In a union, the size of the underlying type does not affect the size of the union.
        // See test case 0070.
        if (self.is_union) {
            self.size_bits = @max(self.size_bits, width);
            if (!is_named) return .{};
            return .{
                .offset_bits = 0,
                .size_bits = width,
            };
        }
        if (width == 0) {
            self.ongoing_bitfield = null;
        } else {
            // If there is an ongoing bit-field in a struct whose underlying type has the same size and
            // if there is enough space left to place this bit-field, then this bit-field is placed in
            // the ongoing bit-field and the size of the struct is not affected by this
            // bit-field. See test case 0037.
            if (self.ongoing_bitfield) |*ongoing| {
                if (ongoing.size_bits == ty_size_bits and ongoing.unused_size_bits >= width) {
                    const offset_bits = self.size_bits - ongoing.unused_size_bits;
                    ongoing.unused_size_bits -= width;
                    if (!is_named) return .{};
                    return .{
                        .offset_bits = offset_bits,
                        .size_bits = width,
                    };
                }
            }
            // Otherwise this field is part of a new ongoing bit-field.
            self.ongoing_bitfield = .{
                .size_bits = ty_size_bits,
                .unused_size_bits = ty_size_bits - width,
            };
        }
        const offset_bits = try alignForward(self.size_bits, field_alignment_bits);
        self.size_bits = if (width == 0) offset_bits else try std.math.add(u64, offset_bits, ty_size_bits);
        if (!is_named) return .{};
        return .{
            .offset_bits = offset_bits,
            .size_bits = width,
        };
    }

    fn layoutRegularFieldMinGW(
        self: *SysVContext,
        ty_size_bits: u64,
        field_alignment_bits: u64,
    ) !FieldLayout {
        self.ongoing_bitfield = null;
        // A struct field starts at the next offset in the struct that is properly
        // aligned with respect to the start of the struct. See test case 0033.
        // A union field always starts at offset 0.
        const offset_bits = if (self.is_union) 0 else try alignForward(self.size_bits, field_alignment_bits);

        // Set the size of the record to the maximum of the current size and the end of
        // the field. See test case 0034.
        self.size_bits = @max(self.size_bits, try std.math.add(u64, offset_bits, ty_size_bits));

        return .{
            .offset_bits = offset_bits,
            .size_bits = ty_size_bits,
        };
    }

    fn layoutRegularField(
        self: *SysVContext,
        fld_attrs: []const Attribute,
        fld_layout: RecordLayout,
    ) !FieldLayout {
        var fld_align_bits = fld_layout.field_alignment_bits;

        // If the struct or the field is packed, then the alignment of the underlying type is
        // ignored. See test case 0084.
        if (self.attr_packed or isPacked(fld_attrs)) {
            fld_align_bits = BITS_PER_BYTE;
        }

        // The field alignment can be increased by __attribute__((aligned)) annotations on the
        // field. See test case 0085.
        if (QualType.annotationAlignment(self.comp, Attribute.Iterator.initSlice(fld_attrs))) |anno| {
            fld_align_bits = @max(fld_align_bits, @as(u32, anno) * BITS_PER_BYTE);
        }

        // #pragma pack takes precedence over all other attributes. See test cases 0084 and
        // 0085.
        if (self.max_field_align_bits) |req_bits| {
            fld_align_bits = @intCast(@min(fld_align_bits, req_bits));
        }

        // A struct field starts at the next offset in the struct that is properly
        // aligned with respect to the start of the struct.
        const offset_bits = if (self.is_union) 0 else try alignForward(self.size_bits, fld_align_bits);
        const size_bits = fld_layout.size_bits;

        // The alignment of a record is the maximum of its field alignments. See test cases
        // 0084, 0085, 0086.
        self.size_bits = @max(self.size_bits, try std.math.add(u64, offset_bits, size_bits));
        self.aligned_bits = @max(self.aligned_bits, fld_align_bits);

        return .{
            .offset_bits = offset_bits,
            .size_bits = size_bits,
        };
    }

    fn layoutBitField(
        self: *SysVContext,
        fld_attrs: []const Attribute,
        fld_layout: RecordLayout,
        is_named: bool,
        bit_width: u64,
    ) !FieldLayout {
        const ty_size_bits = fld_layout.size_bits;
        var ty_fld_algn_bits: u32 = fld_layout.field_alignment_bits;

        if (bit_width > 0) {
            std.debug.assert(bit_width <= ty_size_bits); // Checked in parser
            // Some targets ignore the alignment of the underlying type when laying out
            // non-zero-sized bit-fields. See test case 0072. On such targets, bit-fields never
            // cross a storage boundary. See test case 0081.
            if (self.comp.target.ignoreNonZeroSizedBitfieldTypeAlignment()) {
                ty_fld_algn_bits = 1;
            }
        } else {
            // Some targets ignore the alignment of the underlying type when laying out
            // zero-sized bit-fields. See test case 0073.
            if (self.comp.target.ignoreZeroSizedBitfieldTypeAlignment()) {
                ty_fld_algn_bits = 1;
            }
            // Some targets have a minimum alignment of zero-sized bit-fields. See test case
            // 0074.
            if (self.comp.target.minZeroWidthBitfieldAlignment()) |target_align| {
                ty_fld_algn_bits = @max(ty_fld_algn_bits, target_align);
            }
        }

        // __attribute__((packed)) on the record is identical to __attribute__((packed)) on each
        // field. See test case 0067.
        const attr_packed = self.attr_packed or isPacked(fld_attrs);
        const has_packing_annotation = attr_packed or self.max_field_align_bits != null;

        const annotation_alignment = if (QualType.annotationAlignment(self.comp, Attribute.Iterator.initSlice(fld_attrs))) |anno| @as(u32, anno) * BITS_PER_BYTE else 1;

        const first_unused_bit: u64 = if (self.is_union) 0 else self.size_bits;
        var field_align_bits: u64 = 1;

        if (bit_width == 0) {
            field_align_bits = @max(ty_fld_algn_bits, annotation_alignment);
        } else if (self.comp.langopts.emulate == .gcc) {
            // On GCC, the field alignment is at least the alignment requested by annotations
            // except as restricted by #pragma pack. See test case 0083.
            field_align_bits = annotation_alignment;
            if (self.max_field_align_bits) |max_bits| {
                field_align_bits = @min(annotation_alignment, max_bits);
            }

            // On GCC, if there are no packing annotations and
            // - the field would otherwise start at an offset such that it would cross a
            //   storage boundary or
            // - the alignment of the type is larger than its size,
            // then it is aligned to the type's field alignment. See test case 0083.
            if (!has_packing_annotation) {
                const start_bit = try alignForward(first_unused_bit, field_align_bits);

                const does_field_cross_boundary = start_bit % ty_fld_algn_bits + bit_width > ty_size_bits;

                if (ty_fld_algn_bits > ty_size_bits or does_field_cross_boundary) {
                    field_align_bits = @max(field_align_bits, ty_fld_algn_bits);
                }
            }
        } else {
            std.debug.assert(self.comp.langopts.emulate == .no or self.comp.langopts.emulate == .clang);

            // On Clang, the alignment requested by annotations is not respected if it is
            // larger than the value of #pragma pack. See test case 0083.
            if (annotation_alignment <= self.max_field_align_bits orelse std.math.maxInt(u29)) {
                field_align_bits = @max(field_align_bits, annotation_alignment);
            }
            // On Clang, if there are no packing annotations and the field would cross a
            // storage boundary if it were positioned at the first unused bit in the record,
            // it is aligned to the type's field alignment. See test case 0083.
            if (!has_packing_annotation) {
                const does_field_cross_boundary = first_unused_bit % ty_fld_algn_bits + bit_width > ty_size_bits;

                if (does_field_cross_boundary)
                    field_align_bits = @max(field_align_bits, ty_fld_algn_bits);
            }
        }

        const offset_bits = try alignForward(first_unused_bit, field_align_bits);
        self.size_bits = @max(self.size_bits, try std.math.add(u64, offset_bits, bit_width));

        // Unnamed fields do not contribute to the record alignment except on a few targets.
        // See test case 0079.
        if (is_named or self.comp.target.unnamedFieldAffectsAlignment()) {
            var inherited_align_bits: u32 = undefined;

            if (bit_width == 0) {
                // If the width is 0, #pragma pack and __attribute__((packed)) are ignored.
                // See test case 0075.
                inherited_align_bits = @max(ty_fld_algn_bits, annotation_alignment);
            } else if (self.max_field_align_bits) |max_align_bits| {
                // Otherwise, if a #pragma pack is in effect, __attribute__((packed)) on the field or
                // record is ignored. See test case 0076.
                inherited_align_bits = @max(ty_fld_algn_bits, annotation_alignment);
                inherited_align_bits = @intCast(@min(inherited_align_bits, max_align_bits));
            } else if (attr_packed) {
                // Otherwise, if the field or the record is packed, the field alignment is 1 bit unless
                // it is explicitly increased with __attribute__((aligned)). See test case 0077.
                inherited_align_bits = annotation_alignment;
            } else {
                // Otherwise, the field alignment is the field alignment of the underlying type unless
                // it is explicitly increased with __attribute__((aligned)). See test case 0078.
                inherited_align_bits = @max(ty_fld_algn_bits, annotation_alignment);
            }
            self.aligned_bits = @max(self.aligned_bits, inherited_align_bits);
        }

        if (!is_named) return .{};
        return .{
            .size_bits = bit_width,
            .offset_bits = offset_bits,
        };
    }
};

const MsvcContext = struct {
    req_align_bits: u32,
    max_field_align_bits: ?u32,
    /// The alignment of pointers that point to an object of this type. This is greater than or equal
    /// to the required alignment. Once all fields have been laid out, the size of the record will be
    /// rounded up to this value.
    pointer_align_bits: u32,
    /// The alignment of this type when it is used as a record field. This is greater than or equal to
    /// the pointer alignment.
    field_align_bits: u32,
    size_bits: u64,
    ongoing_bitfield: ?OngoingBitfield,
    contains_non_bitfield: bool,
    is_union: bool,
    comp: *const Compilation,

    fn init(qt: QualType, comp: *const Compilation, pragma_pack: ?u8) MsvcContext {
        var pack_value: ?u32 = null;
        if (qt.hasAttribute(comp, .@"packed")) {
            // __attribute__((packed)) behaves like #pragma pack(1) in clang. See test case 0056.
            pack_value = BITS_PER_BYTE;
        }
        if (pack_value == null) {
            if (pragma_pack) |pack| {
                pack_value = pack * BITS_PER_BYTE;
            }
        }
        if (pack_value) |pack| {
            pack_value = msvcPragmaPack(comp, pack);
        }

        // The required alignment can be increased by adding a __declspec(align)
        // annotation. See test case 0023.
        const must_align = @as(u32, (qt.requestedAlignment(comp) orelse 1)) * BITS_PER_BYTE;
        return .{
            .req_align_bits = must_align,
            .pointer_align_bits = must_align,
            .field_align_bits = must_align,
            .size_bits = 0,
            .max_field_align_bits = pack_value,
            .ongoing_bitfield = null,
            .contains_non_bitfield = false,
            .is_union = qt.is(comp, .@"union"),
            .comp = comp,
        };
    }

    fn layoutField(self: *MsvcContext, fld: *const Field, fld_attrs: []const Attribute) !FieldLayout {
        const type_layout = computeLayout(fld.qt, self.comp);

        // The required alignment of the field is the maximum of the required alignment of the
        // underlying type and the __declspec(align) annotation on the field itself.
        // See test case 0028.
        var req_align = type_layout.required_alignment_bits;
        if (QualType.annotationAlignment(self.comp, Attribute.Iterator.initSlice(fld_attrs))) |anno| {
            req_align = @max(@as(u32, anno) * BITS_PER_BYTE, req_align);
        }

        // The required alignment of a record is the maximum of the required alignments of its
        // fields except that the required alignment of bitfields is ignored.
        // See test case 0029.
        if (fld.bit_width == .null) {
            self.req_align_bits = @max(self.req_align_bits, req_align);
        }

        // The offset of the field is based on the field alignment of the underlying type.
        // See test case 0027.
        var fld_align_bits = type_layout.field_alignment_bits;
        if (self.max_field_align_bits) |max_align| {
            fld_align_bits = @min(fld_align_bits, max_align);
        }
        // check the requested alignment of the field type.
        if (fld.qt.requestedAlignment(self.comp)) |type_req_align| {
            fld_align_bits = @max(fld_align_bits, type_req_align * 8);
        }

        if (isPacked(fld_attrs)) {
            // __attribute__((packed)) on a field is a clang extension. It behaves as if #pragma
            // pack(1) had been applied only to this field. See test case 0057.
            fld_align_bits = BITS_PER_BYTE;
        }
        // __attribute__((packed)) on a field is a clang extension. It behaves as if #pragma
        // pack(1) had been applied only to this field. See test case 0057.
        fld_align_bits = @max(fld_align_bits, req_align);
        if (fld.bit_width.unpack()) |bit_width| {
            return self.layoutBitField(type_layout.size_bits, fld_align_bits, bit_width);
        } else {
            return self.layoutRegularField(type_layout.size_bits, fld_align_bits);
        }
    }

    fn layoutBitField(self: *MsvcContext, ty_size_bits: u64, field_align: u32, bit_width: u32) !FieldLayout {
        if (bit_width == 0) {
            // A zero-sized bit-field that does not follow a non-zero-sized bit-field does not affect
            // the overall layout of the record. Even in a union where the order would otherwise
            // not matter. See test case 0035.
            if (self.ongoing_bitfield) |_| {
                self.ongoing_bitfield = null;
            } else {
                // this field takes 0 space.
                return .{ .offset_bits = self.size_bits, .size_bits = bit_width };
            }
        } else {
            std.debug.assert(bit_width <= ty_size_bits);
            // If there is an ongoing bit-field in a struct whose underlying type has the same size and
            // if there is enough space left to place this bit-field, then this bit-field is placed in
            // the ongoing bit-field and the overall layout of the struct is not affected by this
            // bit-field. See test case 0037.
            if (!self.is_union) {
                if (self.ongoing_bitfield) |*p| {
                    if (p.size_bits == ty_size_bits and p.unused_size_bits >= bit_width) {
                        const offset_bits = self.size_bits - p.unused_size_bits;
                        p.unused_size_bits -= bit_width;
                        return .{ .offset_bits = offset_bits, .size_bits = bit_width };
                    }
                }
            }
            // Otherwise this field is part of a new ongoing bit-field.
            self.ongoing_bitfield = .{ .size_bits = ty_size_bits, .unused_size_bits = ty_size_bits - bit_width };
        }
        const offset_bits = if (!self.is_union) bits: {
            // This is the one place in the layout of a record where the pointer alignment might
            // get assigned a smaller value than the field alignment. This can only happen if
            // the field or the type of the field has a required alignment. Otherwise the value
            // of field_alignment_bits is already bound by max_field_alignment_bits.
            // See test case 0038.
            const p_align = if (self.max_field_align_bits) |max_fld_align|
                @min(max_fld_align, field_align)
            else
                field_align;
            self.pointer_align_bits = @max(self.pointer_align_bits, p_align);
            self.field_align_bits = @max(self.field_align_bits, field_align);

            const offset_bits = try alignForward(self.size_bits, field_align);
            self.size_bits = if (bit_width == 0) offset_bits else offset_bits + ty_size_bits;

            break :bits offset_bits;
        } else bits: {
            // Bit-fields do not affect the alignment of a union. See test case 0041.
            self.size_bits = @max(self.size_bits, ty_size_bits);
            break :bits 0;
        };
        return .{ .offset_bits = offset_bits, .size_bits = bit_width };
    }

    fn layoutRegularField(self: *MsvcContext, size_bits: u64, field_align: u32) !FieldLayout {
        self.contains_non_bitfield = true;
        self.ongoing_bitfield = null;
        // The alignment of the field affects both the pointer alignment and the field
        // alignment of the record. See test case 0032.
        self.pointer_align_bits = @max(self.pointer_align_bits, field_align);
        self.field_align_bits = @max(self.field_align_bits, field_align);
        const offset_bits = switch (self.is_union) {
            true => 0,
            false => try alignForward(self.size_bits, field_align),
        };
        self.size_bits = @max(self.size_bits, offset_bits + size_bits);
        return .{ .offset_bits = offset_bits, .size_bits = size_bits };
    }
    fn handleZeroSizedRecord(self: *MsvcContext) void {
        if (self.is_union) {
            // MSVC does not allow unions without fields.
            // If all fields in a union have size 0, the size of the union is set to
            // - its field alignment if it contains at least one non-bitfield
            // - 4 bytes if it contains only bitfields
            // See test case 0025.
            if (self.contains_non_bitfield) {
                self.size_bits = self.field_align_bits;
            } else {
                self.size_bits = 4 * BITS_PER_BYTE;
            }
        } else {
            // If all fields in a struct have size 0, its size is set to its required alignment
            // but at least to 4 bytes. See test case 0026.
            self.size_bits = @max(self.req_align_bits, 4 * BITS_PER_BYTE);
            self.pointer_align_bits = @intCast(@min(self.pointer_align_bits, self.size_bits));
        }
    }
};

pub fn compute(fields: []Type.Record.Field, qt: QualType, comp: *const Compilation, pragma_pack: ?u8) Error!Type.Record.Layout {
    switch (comp.langopts.emulate) {
        .no, .gcc, .clang => {
            var context = SysVContext.init(qt, comp, pragma_pack);

            try context.layoutFields(fields);

            context.size_bits = try alignForward(context.size_bits, context.aligned_bits);

            return .{
                .size_bits = context.size_bits,
                .field_alignment_bits = context.aligned_bits,
                .pointer_alignment_bits = context.aligned_bits,
                .required_alignment_bits = BITS_PER_BYTE,
            };
        },
        .msvc => {
            var context = MsvcContext.init(qt, comp, pragma_pack);
            for (fields) |*field| {
                if (field.qt.isInvalid()) continue;
                field.layout = try context.layoutField(field, field.attributes(comp));
            }
            if (context.size_bits == 0) {
                // As an extension, MSVC allows records that only contain zero-sized bitfields and empty
                // arrays. Such records would be zero-sized but this case is handled here separately to
                // ensure that there are no zero-sized records.
                context.handleZeroSizedRecord();
            }
            context.size_bits = try alignForward(context.size_bits, context.pointer_align_bits);
            return .{
                .size_bits = context.size_bits,
                .field_alignment_bits = context.field_align_bits,
                .pointer_alignment_bits = context.pointer_align_bits,
                .required_alignment_bits = context.req_align_bits,
            };
        },
    }
}

fn computeLayout(qt: QualType, comp: *const Compilation) RecordLayout {
    switch (qt.base(comp).type) {
        .@"struct", .@"union" => |record| {
            const requested = BITS_PER_BYTE * (qt.requestedAlignment(comp) orelse 0);
            return .{
                .size_bits = record.layout.?.size_bits,
                .pointer_alignment_bits = @max(requested, record.layout.?.pointer_alignment_bits),
                .field_alignment_bits = @max(requested, record.layout.?.field_alignment_bits),
                .required_alignment_bits = record.layout.?.required_alignment_bits,
            };
        },
        else => {
            const type_align = qt.alignof(comp) * BITS_PER_BYTE;
            return .{
                .size_bits = BITS_PER_BYTE * (qt.sizeofOrNull(comp) orelse 0),
                .pointer_alignment_bits = type_align,
                .field_alignment_bits = type_align,
                .required_alignment_bits = BITS_PER_BYTE,
            };
        },
    }
}

fn isPacked(attrs: ?[]const Attribute) bool {
    const a = attrs orelse return false;

    for (a) |attribute| {
        if (attribute.tag != .@"packed") continue;
        return true;
    }
    return false;
}

// The effect of #pragma pack(N) depends on the target.
//
// x86: By default, there is no maximum field alignment. N={1,2,4} set the maximum field
//      alignment to that value. All other N activate the default.
// x64: By default, there is no maximum field alignment. N={1,2,4,8} set the maximum field
//      alignment to that value. All other N activate the default.
// arm: By default, the maximum field alignment is 8. N={1,2,4,8,16} set the maximum field
//      alignment to that value. All other N activate the default.
// arm64: By default, the maximum field alignment is 8. N={1,2,4,8} set the maximum field
//        alignment to that value. N=16 disables the maximum field alignment. All other N
//        activate the default.
//
// See test case 0020.
pub fn msvcPragmaPack(comp: *const Compilation, pack: u32) ?u32 {
    return switch (pack) {
        8, 16, 32 => pack,
        64 => if (comp.target.cpu.arch == .x86) null else pack,
        128 => if (comp.target.cpu.arch == .thumb) pack else null,
        else => {
            return switch (comp.target.cpu.arch) {
                .thumb, .aarch64 => 64,
                else => null,
            };
        },
    };
}
