This function inspects the generated layout of a record to determine the alignment for a particular field. This approach is necessary because unlike Zig, a C compiler is not required to fulfill the requested alignment, which means we'd risk generating different code if we only look at the user-requested alignment.
Returns a ?c_uint to match Clang's behavior of using c_uint. The return type can be changed after the Clang frontend for translate-c is removed. A null value indicates that a field is 'naturally aligned'.
fn alignmentForField(
t: *Translator,
record_decl: aro.Type.Record,
head_field_alignment: ?c_uint,
field_index: usize,
) ?c_uint
fn alignmentForField(
t: *Translator,
record_decl: aro.Type.Record,
head_field_alignment: ?c_uint,
field_index: usize,
) ?c_uint {
const fields = record_decl.fields;
assert(fields.len != 0);
const field = fields[field_index];
const bits_per_byte = 8;
const parent_ptr_alignment_bits = record_decl.layout.?.pointer_alignment_bits;
const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte;
// bitfields aren't supported yet. Until support is added, records with bitfields
// should be demoted to opaque, and this function shouldn't be called for them.
if (field.bit_width != .null) {
@panic("TODO: add bitfield support for records");
}
const field_offset_bits: u64 = field.layout.offset_bits;
const field_size_bits: u64 = field.layout.size_bits;
// Fields with zero width always have an alignment of 1
if (field_size_bits == 0) {
return 1;
}
// Fields with 0 offset inherit the parent's pointer alignment.
if (field_offset_bits == 0) {
return head_field_alignment;
}
// Records have a natural alignment when used as a field, and their size is
// a multiple of this alignment value. For all other types, the natural alignment
// is their size.
const field_natural_alignment_bits: u64 = bits_per_byte * field.qt.alignof(t.comp);
const rem_bits = field_offset_bits % field_natural_alignment_bits;
// If there's a remainder, then the alignment is smaller than the field's
// natural alignment
if (rem_bits > 0) {
const rem_alignment = rem_bits / bits_per_byte;
if (rem_alignment > 0 and std.math.isPowerOfTwo(rem_alignment)) {
const actual_alignment = @min(rem_alignment, parent_ptr_alignment);
return @as(c_uint, @truncate(actual_alignment));
} else {
return 1;
}
}
// A field may have an offset which positions it to be naturally aligned, but the
// parent's pointer alignment determines if this is actually true, so we take the minimum
// value.
// For example, a float field (4 bytes wide) with a 4 byte offset is positioned to have natural
// alignment, but if the parent pointer alignment is 2, then the actual alignment of the
// float is 2.
const field_natural_alignment: u64 = field_natural_alignment_bits / bits_per_byte;
const offset_alignment = field_offset_bits / bits_per_byte;
const possible_alignment = @min(parent_ptr_alignment, offset_alignment);
if (possible_alignment == field_natural_alignment) {
return null;
} else if (possible_alignment < field_natural_alignment) {
if (std.math.isPowerOfTwo(possible_alignment)) {
return possible_alignment;
} else {
return 1;
}
} else { // possible_alignment > field_natural_alignment
// Here, the field is positioned be at a higher alignment than it's natural alignment. This means we
// need to determine whether it's a specified alignment. We can determine that from the padding preceding
// the field.
const padding_from_prev_field: u64 = blk: {
if (field_offset_bits != 0) {
const previous_field = fields[field_index - 1];
break :blk (field_offset_bits - previous_field.layout.offset_bits) - previous_field.layout.size_bits;
} else {
break :blk 0;
}
};
if (padding_from_prev_field < field_natural_alignment_bits) {
return null;
} else {
return possible_alignment;
}
}
}