union padding computation: add fast-path for ZST
Also avoid even tracking empty ranges, and add fast-path for arrays of scalars
This commit is contained in:
parent
11bd99de8c
commit
65c70900ce
3 changed files with 43 additions and 12 deletions
|
@ -220,6 +220,10 @@ pub struct RangeSet(Vec<(Size, Size)>);
|
||||||
|
|
||||||
impl RangeSet {
|
impl RangeSet {
|
||||||
fn add_range(&mut self, offset: Size, size: Size) {
|
fn add_range(&mut self, offset: Size, size: Size) {
|
||||||
|
if size.bytes() == 0 {
|
||||||
|
// No need to track empty ranges.
|
||||||
|
return;
|
||||||
|
}
|
||||||
let v = &mut self.0;
|
let v = &mut self.0;
|
||||||
// We scan for a partition point where the left partition is all the elements that end
|
// We scan for a partition point where the left partition is all the elements that end
|
||||||
// strictly before we start. Those are elements that are too "low" to merge with us.
|
// strictly before we start. Those are elements that are too "low" to merge with us.
|
||||||
|
@ -938,42 +942,53 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
|
||||||
let layout_cx = LayoutCx { tcx: *ecx.tcx, param_env: ecx.param_env };
|
let layout_cx = LayoutCx { tcx: *ecx.tcx, param_env: ecx.param_env };
|
||||||
return M::cached_union_data_range(ecx, layout.ty, || {
|
return M::cached_union_data_range(ecx, layout.ty, || {
|
||||||
let mut out = RangeSet(Vec::new());
|
let mut out = RangeSet(Vec::new());
|
||||||
union_data_range_(&layout_cx, layout, Size::ZERO, &mut out);
|
union_data_range_uncached(&layout_cx, layout, Size::ZERO, &mut out);
|
||||||
out
|
out
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Helper for recursive traversal: add data ranges of the given type to `out`.
|
/// Helper for recursive traversal: add data ranges of the given type to `out`.
|
||||||
fn union_data_range_<'tcx>(
|
fn union_data_range_uncached<'tcx>(
|
||||||
cx: &LayoutCx<'tcx, TyCtxt<'tcx>>,
|
cx: &LayoutCx<'tcx, TyCtxt<'tcx>>,
|
||||||
layout: TyAndLayout<'tcx>,
|
layout: TyAndLayout<'tcx>,
|
||||||
base_offset: Size,
|
base_offset: Size,
|
||||||
out: &mut RangeSet,
|
out: &mut RangeSet,
|
||||||
) {
|
) {
|
||||||
|
// If this is a ZST, we don't contain any data. In particular, this helps us to quickly
|
||||||
|
// skip over huge arrays of ZST.
|
||||||
|
if layout.is_zst() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Just recursively add all the fields of everything to the output.
|
// Just recursively add all the fields of everything to the output.
|
||||||
match &layout.fields {
|
match &layout.fields {
|
||||||
FieldsShape::Primitive => {
|
FieldsShape::Primitive => {
|
||||||
out.add_range(base_offset, layout.size);
|
out.add_range(base_offset, layout.size);
|
||||||
}
|
}
|
||||||
&FieldsShape::Union(fields) => {
|
&FieldsShape::Union(fields) => {
|
||||||
// Currently, all fields start at offset 0.
|
// Currently, all fields start at offset 0 (relative to `base_offset`).
|
||||||
for field in 0..fields.get() {
|
for field in 0..fields.get() {
|
||||||
let field = layout.field(cx, field);
|
let field = layout.field(cx, field);
|
||||||
union_data_range_(cx, field, base_offset, out);
|
union_data_range_uncached(cx, field, base_offset, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&FieldsShape::Array { stride, count } => {
|
&FieldsShape::Array { stride, count } => {
|
||||||
let elem = layout.field(cx, 0);
|
let elem = layout.field(cx, 0);
|
||||||
for idx in 0..count {
|
|
||||||
// This repeats the same computation for every array elements... but the alternative
|
// Fast-path for large arrays of simple types that do not contain any padding.
|
||||||
// is to allocate temporary storage for a dedicated `out` set for the array element,
|
if elem.abi.is_scalar() {
|
||||||
// and replicating that N times. Is that better?
|
out.add_range(base_offset, elem.size * count);
|
||||||
union_data_range_(cx, elem, base_offset + idx * stride, out);
|
} else {
|
||||||
|
for idx in 0..count {
|
||||||
|
// This repeats the same computation for every array element... but the alternative
|
||||||
|
// is to allocate temporary storage for a dedicated `out` set for the array element,
|
||||||
|
// and replicating that N times. Is that better?
|
||||||
|
union_data_range_uncached(cx, elem, base_offset + idx * stride, out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FieldsShape::Arbitrary { offsets, .. } => {
|
FieldsShape::Arbitrary { offsets, .. } => {
|
||||||
for (field, &offset) in offsets.iter_enumerated() {
|
for (field, &offset) in offsets.iter_enumerated() {
|
||||||
let field = layout.field(cx, field.as_usize());
|
let field = layout.field(cx, field.as_usize());
|
||||||
union_data_range_(cx, field, base_offset + offset, out);
|
union_data_range_uncached(cx, field, base_offset + offset, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -985,7 +1000,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
|
||||||
Variants::Multiple { variants, .. } => {
|
Variants::Multiple { variants, .. } => {
|
||||||
for variant in variants.indices() {
|
for variant in variants.indices() {
|
||||||
let variant = layout.for_variant(cx, variant);
|
let variant = layout.for_variant(cx, variant);
|
||||||
union_data_range_(cx, variant, base_offset, out);
|
union_data_range_uncached(cx, variant, base_offset, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1185,7 +1200,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
|
||||||
// This is the size in bytes of the whole array. (This checks for overflow.)
|
// This is the size in bytes of the whole array. (This checks for overflow.)
|
||||||
let size = layout.size * len;
|
let size = layout.size * len;
|
||||||
// If the size is 0, there is nothing to check.
|
// If the size is 0, there is nothing to check.
|
||||||
// (`size` can only be 0 of `len` is 0, and empty arrays are always valid.)
|
// (`size` can only be 0 if `len` is 0, and empty arrays are always valid.)
|
||||||
if size == Size::ZERO {
|
if size == Size::ZERO {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1136,6 +1136,7 @@ impl<'tcx> Ty<'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests if this is any kind of primitive pointer type (reference, raw pointer, fn pointer).
|
/// Tests if this is any kind of primitive pointer type (reference, raw pointer, fn pointer).
|
||||||
|
/// `Box` is *not* considered a pointer here!
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_any_ptr(self) -> bool {
|
pub fn is_any_ptr(self) -> bool {
|
||||||
self.is_ref() || self.is_unsafe_ptr() || self.is_fn_ptr()
|
self.is_ref() || self.is_unsafe_ptr() || self.is_fn_ptr()
|
||||||
|
|
|
@ -61,6 +61,20 @@ fn debug() {
|
||||||
println!("{:?}", array);
|
println!("{:?}", array);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn huge_zst() {
|
||||||
|
fn id<T>(x: T) -> T { x }
|
||||||
|
|
||||||
|
// A "huge" zero-sized array. Make sure we don't loop over it in any part of Miri.
|
||||||
|
let val = [(); usize::MAX];
|
||||||
|
id(val); // make a copy
|
||||||
|
|
||||||
|
let val = [val; 2];
|
||||||
|
id(val);
|
||||||
|
|
||||||
|
// Also wrap it in a union (which, in particular, hits the logic for computing union padding).
|
||||||
|
let _copy = std::mem::MaybeUninit::new(val);
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
assert_eq!(empty_array(), []);
|
assert_eq!(empty_array(), []);
|
||||||
assert_eq!(index_unsafe(), 20);
|
assert_eq!(index_unsafe(), 20);
|
||||||
|
@ -73,4 +87,5 @@ fn main() {
|
||||||
from();
|
from();
|
||||||
eq();
|
eq();
|
||||||
debug();
|
debug();
|
||||||
|
huge_zst();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue