Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 128 additions & 15 deletions compiler/rustc_abi/src/layout/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,29 +291,42 @@ impl<'a, Ty> TyAndLayout<'a, Ty> {
/// This is the "guaranteed" padding. There may be more bytes that are padding for some
/// but not all variants of this type; those are not included.
/// (E.g. `Option<i8>` has no guaranteed padding so the empty range set is returned, but its `None` value still has padding).
pub fn padding_ranges<C>(&self, cx: &C) -> Vec<Range<Size>>
pub fn variant_independent_padding_ranges<C>(&self, cx: &C) -> Vec<Range<Size>>
where
Ty: TyAbiInterface<'a, C> + Copy,
{
let mut data = RangeSet::new();
self.add_data_ranges(cx, Size::ZERO, &mut data);

// Find gaps between the data ranges.
let mut uninit_ranges = Vec::new();
let mut covered_until = Size::ZERO;
for &(offset, size) in data.0.iter() {
if offset > covered_until {
uninit_ranges.push(covered_until..offset);
}
covered_until = Ord::max(covered_until, offset + size);
}
let mut full = RangeSet::new();
full.add_range(Size::ZERO, self.size);

// Add trailing padding.
if self.size > covered_until {
uninit_ranges.push(covered_until..self.size);
}
full.difference(&data).0.into_iter().map(|(a, b)| a..b).collect()
}

/// The ranges of bytes that are padding for *some, but not all*, valid values of this type.
///
/// These bytes contain data for at least one valid value, but are padding for at least one
/// other valid value. In other words, whether such a byte is initialized depends on the
/// concrete value, hence "value-dependent". This is exactly the padding that is *not*
/// already returned by [`Self::variant_independent_padding_ranges`].
///
/// For example, `Option<i8>` has no guaranteed padding, but the byte holding the payload is
/// value-dependent padding: it is data for `Some(_)` and padding for `None`.
pub fn variant_dependent_padding_ranges<C>(&self, cx: &C) -> Vec<Range<Size>>
where
Ty: TyAbiInterface<'a, C> + Copy,
{
// Bytes that carry data for *at least one* valid value.
let mut maybe_data = RangeSet::new();
self.add_data_ranges(cx, Size::ZERO, &mut maybe_data);

uninit_ranges
// Bytes that carry data for *every* valid value.
let always_data = self.always_data_ranges(cx, Size::ZERO);

// A byte is value-dependent padding iff it is data for some value but padding for some
// other value.
maybe_data.difference(&always_data).0.into_iter().map(|(a, b)| a..b).collect()
}

/// Extend `out` with all ranges of bytes that *may* carry relevant data for values of this type.
Expand Down Expand Up @@ -361,11 +374,111 @@ impl<'a, Ty> TyAndLayout<'a, Ty> {
}
},
Variants::Multiple { variants, .. } => {
// The variants do not contain e.g. the discriminant or coroutine upvars.
let FieldsShape::Arbitrary { offsets, in_memory_order: _ } = &self.fields else {
unreachable!("a multi-variant layout should have `Arbitrary` fields")
};

for (field, &offset) in offsets.iter_enumerated() {
let field = self.field(cx, field.as_usize());
field.add_data_ranges(cx, base_offset + offset, out);
}

for variant in variants.indices() {
let variant = self.for_variant(cx, variant);
variant.add_data_ranges(cx, base_offset, out);
}
}
}
}

/// The ranges of bytes that contain data for *every* valid value of this type.
///
/// This is the dual of [`Self::add_data_ranges`], which returns the bytes that contain data
/// for *some* valid value.
///
/// The difference is in how enums and unions are handled: there a byte may be data for one
/// variant/field, but padding for another.
fn always_data_ranges<C>(self, cx: &C, base_offset: Size) -> RangeSet<Size>
where
Ty: TyAbiInterface<'a, C> + Copy,
{
let mut out = RangeSet::new();

if self.is_zst() {
return out;
}

match &self.variants {
Variants::Empty => { /* done */ }
Variants::Single { index: _ } => match &self.fields {
FieldsShape::Primitive => {
out.add_range(base_offset, self.size);
}
&FieldsShape::Union(field_count) => {
// A byte is data for every value only when it is data for every field.
out = (0..field_count.get())
.map(|field| self.field(cx, field).always_data_ranges(cx, base_offset))
.reduce(|acc, f| acc.intersection(&f))
.unwrap_or(out);
}
&FieldsShape::Array { stride, count } => {
let elem = self.field(cx, 0);

// For scalars we know there is no padding between the elements,
// so the entire array is a single big data range.
if elem.backend_repr.is_scalar() {
out.add_range(base_offset, elem.size * count);
} else {
// FIXME: this is really inefficient for large arrays.
for idx in 0..count {
let elem = elem.always_data_ranges(cx, base_offset + idx * stride);
for &(offset, size) in elem.0.iter() {
out.add_range(offset, size);
}
}
}
}
FieldsShape::Arbitrary { offsets, in_memory_order: _ } => {
// A byte that is always data for any field is always data for the struct.
for (field, &offset) in offsets.iter_enumerated() {
let field = self.field(cx, field.as_usize());
let field = field.always_data_ranges(cx, base_offset + offset);
for &(offset, size) in field.0.iter() {
out.add_range(offset, size);
}
}
}
},
Variants::Multiple { variants, .. } => {
// The variants do not contain e.g. the discriminant or coroutine upvars.
let FieldsShape::Arbitrary { offsets, in_memory_order: _ } = &self.fields else {
unreachable!("a multi-variant layout should have `Arbitrary` fields")
};

// The fields are variant-independent and always data.
for (field, &offset) in offsets.iter_enumerated() {
let field = self.field(cx, field.as_usize());
let field = field.always_data_ranges(cx, base_offset + offset);
for &(offset, size) in field.0.iter() {
out.add_range(offset, size);
}
}

// Otherwise a byte is data for every value only when it is data for every variant.
if let Some(common) = variants
.indices()
.map(|variant| self.for_variant(cx, variant))
.map(|variant| variant.always_data_ranges(cx, base_offset))
.reduce(|acc, f| acc.intersection(&f))
{
for &(offset, size) in common.0.iter() {
out.add_range(offset, size);
}
}
}
}

out
}
}
4 changes: 2 additions & 2 deletions compiler/rustc_codegen_ssa/src/mir/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
//
// Returning a value with value-dependent padding will instead trigger a lint.
let ret_layout = self.fn_abi.ret.layout;
let uninit_ranges = ret_layout.padding_ranges(bx.cx());
let uninit_ranges = ret_layout.variant_independent_padding_ranges(bx.cx());
self.zero_byte_ranges(bx, llslot, ret_layout.size, &uninit_ranges);
}

Expand Down Expand Up @@ -1878,7 +1878,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
bx,
llscratch,
Size::from_bytes(copy_bytes),
&arg.layout.padding_ranges(bx.cx()),
&arg.layout.variant_independent_padding_ranges(bx.cx()),
);
}

Expand Down
69 changes: 69 additions & 0 deletions compiler/rustc_data_structures/src/range_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,73 @@ where
v.insert(idx, (offset, size));
}
}

pub fn intersection(&self, other: &Self) -> Self {
let (a, b) = (self, other);
let mut out = RangeSet::new();

let (mut i, mut j) = (0, 0);
while let (Some(&(a_offset, a_size)), Some(&(b_offset, b_size))) = (a.0.get(i), b.0.get(j))
{
let (a_end, b_end) = (a_offset + a_size, b_offset + b_size);

let start = Ord::max(a_offset, b_offset);
let end = Ord::min(a_end, b_end);

// Add the intersection if nonempty.
if start < end {
out.add_range(start, end - start);
}

// Advance past whichever range ends first.
// The other may still overlap a later range.
if a_end < b_end {
i += 1;
} else {
j += 1;
}
}

out
}

/// The ranges from `self` with any intersection with `other` removed.
pub fn difference(&self, other: &Self) -> Self {
let (a, b) = (self, other);
let mut out = Vec::new();

let mut j = 0;
for &(a_offset, a_size) in a.0.iter() {
let mut cursor = a_offset;
let a_end = a_offset + a_size;

// Skip ranges of `b` that end before this range of `a` begins.
// both sequences are sorted they cannot overlap any later range of `a` either.
while let Some(&(b_offset, b_size)) = b.0.get(j)
&& b_offset + b_size <= cursor
{
j += 1;
}

// Carve out each range of `b` that overlaps this range of `a`. A range of `b` may extend
// past `a_end` and overlap the next range of `a`, so leave `j` pointing at it.
let mut k = j;
while let Some(&(b_offset, b_size)) = b.0.get(k)
&& b_offset < a_end
{
if b_offset > cursor {
out.push((cursor, b_offset));
}
cursor = Ord::max(cursor, b_offset + b_size);
k += 1;
}

// Keep the remainder of the `a`'s range.
if cursor < a_end {
out.push((cursor, a_end));
}
}

Self(out)
}
}
Loading
Loading