-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
More layout sanity checks #99983
More layout sanity checks #99983
Conversation
fea513d
to
c618abe
Compare
This comment was marked as resolved.
This comment was marked as resolved.
b180d53
to
6e27150
Compare
This comment has been minimized.
This comment has been minimized.
@@ -124,6 +124,7 @@ mod erase_regions; | |||
mod generics; | |||
mod impls_ty; | |||
mod instance; | |||
mod layout_sanity_check; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(this is most of a comment first written when I started a review on the original state of the PR but didn't submit it right away)
You could deal with the large file by putting the checks in ty/layout_checks.rs
or ty/layout/checks.rs
(the latter would mean mod checks;
in layout.rs
and would be able to do use super::*;
to improve everything including private items - I have mixed feelings but it's practical I think).
(thoughts unactionable in this PR follow below)
Honestly we should consider to what crate to move the entire layout
module, since it's mostly just providing queries, and it feels awkward where it is.
I also wish it was rustc_middle::ty::abi
or rustc_middle::abi
(could even go into mir
but I doubt that makes as much sense) for consistency with rustc_target::abi
but that's another mess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wish we used the term 'abi' less, not more, given how ambiguous it is.^^
/// Yields non-1-ZST fields of the type | ||
fn non_zst_fields<'tcx, 'a>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this have 1zst
/1_zst
or align1zst
/align1_zst
in the name instead of zst
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't actually produce 1-ZST though, I had to change it to produce all ZST. Will adjust the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW this function seems useful to have elsewhere as well? Not sure where to put it though.
Hmm, so you know how tools like It might be easier to make an abstraction like this, if only for the layout checks (note: "cases" below refers to // HACK(eddyb) used below to disambiguate between the two uses of `Size`,
// but we should probably have this as a completely separate type, with:
// `Size`: measured in bytes and/or bits; Add+Mul ops (Sub not needed)
// `Offset`: measured in bytes (no "bit offsets"); Add+Sub ops (Mul not needed)
type Offset = Size;
// HACK(eddyb) necessary method sadly missing from `std::ops::Range`.
impl<T> Range<T> {
fn includes(&self, other: Range<T>) -> bool {
self.start <= other.start && other.end <= self.end
}
}
struct ScalarLeaf {
/// Where in the parent layout this leaf is.
///
/// Must be the right size, i.e. `extent.end - extent.start == primitive`.
//
// FIXME(eddyb) does the above condition make `extent.end` redundant?
// (move to a `fn extent(&self, cx: ...) -> Range<Offset>` helper method?)
extent: Range<Offset>,
// FIXME(eddyb) instead of ignoring validity ranges, should they be merged
// across e.g. `enum` variants?
primitive: Primitive,
}
struct ScalarLeafWithinError {
/// No leaves found in `search_extent`.
OnlyPadding,
/// A leaf intersects `search_extent` without being contained in it,
/// i.e. the leaf is "straddling" `search_extent`'s start or end.
PartialIntersection(ScalarLeaf),
/// Two leaves found within `search_extent` that differ in their `extent`,
/// `primitive`, or both.
Mismatch(ScalarLeaf, ScalarLeaf),
}
/// Find the unique `Scalar` (but ignoring validity ranges, so... `Primitive`¹?)
/// leaf field wholly contained within `search_extent` in `layout`.
///
/// An `Ok(leaf)` result indicates a leaf field that is:
/// * "wholly contained within": `search_extent.includes(leaf.extent)`
/// * "unique": `layout` always contains (across all `enum`/`union` cases):
/// * `Scalar(leaf.primitive)` exactly filling `leaf.extent`
/// * some `enum`/`union` cases may contain padding at `leaf.extent` instead,
/// but all other `enum`/`union` cases must agree on the same `leaf`
/// * `Scalar` validity might vary, but the `Primitive` must match exactly
/// * only padding, in `search_extent` "around" `leaf.extent`,
/// i.e. neither of these extents contain any leaves of their own:
/// * `search_extent.start..leaf.extent.start` (before `leaf`)
/// * `leaf.extent.end..search_extent.end` (after `leaf`)
///
/// ¹ I wish `Primitive` was called `Scalar` but what would we call `Scalar`?
/// (maybe `Primitive` should be renamed to `ScalarType`/`ScalarKind`?)
fn scalar_leaf_within(
layout: &TyAndLayout<'tcx>,
search_extent: Range<Offset>,
) -> Result<ScalarLeaf, ScalarLeafWithinError> {
fn try_for_each_leaf_intersecting<E>(
base_offset: Offset,
layout: &TyAndLayout<'tcx>,
filter_extent: Range<Offset>,
each: &mut impl FnMut(ScalarLeaf) -> Result<(), E>
) -> Result<(), E> {
for (i, field_offset) in layout.fields.enumerate() {
if field_offset < filter_extent.end {
let field = layout.field(i);
if filter_extent.start < field_offset + field.size {
try_for_each_leaf_intersecting(
base_offset + field_offset,
field,
filter_extent,
each,
)?;
}
}
}
for v in 0..layout.variants.len() {
try_for_each_leaf_intersecting(base_offset, layout.for_variant(v), filter_extent, each)?;
}
if layout.fields.is_empty() && !layout.is_zst() {
match layout.abi {
Abi::Scalar(scalar) => each(ScalarLeaf {
extent: base_offset..base_offset+scalar.size(),
primitive: scalar.primitive,
})?,
// FIXME(eddyb) can we guarantee all non-ZST leaves are `Scalar`?
_ => unreachable!(),
}
}
Ok(())
}
let mut found = None;
try_for_each_leaf_intersecting(
Offset::ZERO,
layout,
search_extent,
|candidate| match found {
_ if !search_extent.includes(&candidate.extent) => {
Err(ScalarLeafError::PartialIntersection(candidate))
}
Some(previous) if candidate != previous => {
Err(ScalarLeafError::Mismatch(previous, candidate))
}
_ => {
found = Some(candidate);
Ok(())
}
}
)?;
Ok(found.ok_or(ScalarLeafWithinError::OnlyPadding))
} With that abstraction in hand, we can define the conditions for:
// HACK(eddyb) needed to deal with `Offset` vs `Size` type differences.
let layout_extent = Offset::ZERO..Offset::ZERO+layout.size;
assert_eq!(
scalar_leaf_within(layout, layout_extent),
Ok(ScalarLeaf {
extent: layout_extent,
primitive: scalar.primitive,
}),
);
// HACK(eddyb) needed to deal with `Offset` vs `Size` type differences.
let layout_extent = Offset::ZERO..Offset::ZERO+layout.size;
let a_extent = Offset::ZERO..Offset::ZERO+a.size;
// FIXME(eddyb) there should be a nicer way to make `start..start+len` ranges.
let b_extent = a_extent.end.align_to(b.align)..;
let b_extent = b_extent.start..b_extent.start+b.size;
assert_eq!(
scalar_leaf_within(layout, layout_extent.start..b_extent.start),
Ok(ScalarLeaf {
extent: a_extent,
primitive: a.primitive,
}),
);
assert_eq!(
scalar_leaf_within(layout, b_extent.start..layout_extent.end),
Ok(ScalarLeaf {
extent: b_extent,
primitive: b.primitive,
}),
); This should cover any field hierarchy, but here are some extra thoughts:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r=me if you don't want to take up getting The Fun Layout Search pseudocode working (would still be good to have an issue tracking how good these checks are etc.)
☀️ Test successful - checks-actions |
Finished benchmarking commit (5a9c3a2): comparison url. Instruction countThis benchmark run did not return any relevant results for this metric. Max RSS (memory usage)Results
CyclesResults
If you disagree with this performance assessment, please file an issue in rust-lang/rustc-perf. @rustbot label: -perf-regression Footnotes |
r? @eddyb