Skip to content

Commit 6330daa

Browse files
committed
Auto merge of rust-lang#112062 - lukas-code:unsized-layout, r=wesleywiser
Make struct layout not depend on unsizeable tail fixes (after backport) rust-lang#112048 Since unsizing `Ptr<Foo<T>>` -> `Ptr<Foo<U>` just copies the pointer and adds the metadata, the layout of `Foo` must not depend on niches in and alignment of the tail `T`. Nominating for beta 1.71, because it will have this issue: `@rustbot` label beta-nominated
2 parents 371994e + b982f3a commit 6330daa

File tree

3 files changed

+109
-44
lines changed

3 files changed

+109
-44
lines changed

compiler/rustc_abi/src/layout.rs

+54-44
Original file line numberDiff line numberDiff line change
@@ -57,48 +57,54 @@ pub trait LayoutCalculator {
5757
// run and bias niches to the right and then check which one is closer to one of the struct's
5858
// edges.
5959
if let Some(layout) = &layout {
60-
if let Some(niche) = layout.largest_niche {
61-
let head_space = niche.offset.bytes();
62-
let niche_length = niche.value.size(dl).bytes();
63-
let tail_space = layout.size.bytes() - head_space - niche_length;
64-
65-
// This may end up doing redundant work if the niche is already in the last field
66-
// (e.g. a trailing bool) and there is tail padding. But it's non-trivial to get
67-
// the unpadded size so we try anyway.
68-
if fields.len() > 1 && head_space != 0 && tail_space > 0 {
69-
let alt_layout = univariant(self, dl, fields, repr, kind, NicheBias::End)
70-
.expect("alt layout should always work");
71-
let niche = alt_layout
72-
.largest_niche
73-
.expect("alt layout should have a niche like the regular one");
74-
let alt_head_space = niche.offset.bytes();
75-
let alt_niche_len = niche.value.size(dl).bytes();
76-
let alt_tail_space = alt_layout.size.bytes() - alt_head_space - alt_niche_len;
77-
78-
debug_assert_eq!(layout.size.bytes(), alt_layout.size.bytes());
79-
80-
let prefer_alt_layout =
81-
alt_head_space > head_space && alt_head_space > tail_space;
82-
83-
debug!(
84-
"sz: {}, default_niche_at: {}+{}, default_tail_space: {}, alt_niche_at/head_space: {}+{}, alt_tail: {}, num_fields: {}, better: {}\n\
85-
layout: {}\n\
86-
alt_layout: {}\n",
87-
layout.size.bytes(),
88-
head_space,
89-
niche_length,
90-
tail_space,
91-
alt_head_space,
92-
alt_niche_len,
93-
alt_tail_space,
94-
layout.fields.count(),
95-
prefer_alt_layout,
96-
format_field_niches(&layout, &fields, &dl),
97-
format_field_niches(&alt_layout, &fields, &dl),
98-
);
99-
100-
if prefer_alt_layout {
101-
return Some(alt_layout);
60+
// Don't try to calculate an end-biased layout for unsizable structs,
61+
// otherwise we could end up with different layouts for
62+
// Foo<Type> and Foo<dyn Trait> which would break unsizing
63+
if !matches!(kind, StructKind::MaybeUnsized) {
64+
if let Some(niche) = layout.largest_niche {
65+
let head_space = niche.offset.bytes();
66+
let niche_length = niche.value.size(dl).bytes();
67+
let tail_space = layout.size.bytes() - head_space - niche_length;
68+
69+
// This may end up doing redundant work if the niche is already in the last field
70+
// (e.g. a trailing bool) and there is tail padding. But it's non-trivial to get
71+
// the unpadded size so we try anyway.
72+
if fields.len() > 1 && head_space != 0 && tail_space > 0 {
73+
let alt_layout = univariant(self, dl, fields, repr, kind, NicheBias::End)
74+
.expect("alt layout should always work");
75+
let niche = alt_layout
76+
.largest_niche
77+
.expect("alt layout should have a niche like the regular one");
78+
let alt_head_space = niche.offset.bytes();
79+
let alt_niche_len = niche.value.size(dl).bytes();
80+
let alt_tail_space =
81+
alt_layout.size.bytes() - alt_head_space - alt_niche_len;
82+
83+
debug_assert_eq!(layout.size.bytes(), alt_layout.size.bytes());
84+
85+
let prefer_alt_layout =
86+
alt_head_space > head_space && alt_head_space > tail_space;
87+
88+
debug!(
89+
"sz: {}, default_niche_at: {}+{}, default_tail_space: {}, alt_niche_at/head_space: {}+{}, alt_tail: {}, num_fields: {}, better: {}\n\
90+
layout: {}\n\
91+
alt_layout: {}\n",
92+
layout.size.bytes(),
93+
head_space,
94+
niche_length,
95+
tail_space,
96+
alt_head_space,
97+
alt_niche_len,
98+
alt_tail_space,
99+
layout.fields.count(),
100+
prefer_alt_layout,
101+
format_field_niches(&layout, &fields, &dl),
102+
format_field_niches(&alt_layout, &fields, &dl),
103+
);
104+
105+
if prefer_alt_layout {
106+
return Some(alt_layout);
107+
}
102108
}
103109
}
104110
}
@@ -828,6 +834,7 @@ fn univariant(
828834
if optimize && fields.len() > 1 {
829835
let end = if let StructKind::MaybeUnsized = kind { fields.len() - 1 } else { fields.len() };
830836
let optimizing = &mut inverse_memory_index.raw[..end];
837+
let fields_excluding_tail = &fields.raw[..end];
831838

832839
// If `-Z randomize-layout` was enabled for the type definition we can shuffle
833840
// the field ordering to try and catch some code making assumptions about layouts
@@ -844,8 +851,11 @@ fn univariant(
844851
}
845852
// Otherwise we just leave things alone and actually optimize the type's fields
846853
} else {
847-
let max_field_align = fields.iter().map(|f| f.align().abi.bytes()).max().unwrap_or(1);
848-
let largest_niche_size = fields
854+
// To allow unsizing `&Foo<Type>` -> `&Foo<dyn Trait>`, the layout of the struct must
855+
// not depend on the layout of the tail.
856+
let max_field_align =
857+
fields_excluding_tail.iter().map(|f| f.align().abi.bytes()).max().unwrap_or(1);
858+
let largest_niche_size = fields_excluding_tail
849859
.iter()
850860
.filter_map(|f| f.largest_niche())
851861
.map(|n| n.available(dl))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// run-pass
2+
3+
// Check that unsizing doesn't reorder fields.
4+
5+
#![allow(dead_code)]
6+
7+
use std::fmt::Debug;
8+
9+
#[derive(Debug)]
10+
struct GcNode<T: ?Sized> {
11+
gets_swapped_with_next: usize,
12+
next: Option<&'static GcNode<dyn Debug>>,
13+
tail: T,
14+
}
15+
16+
fn main() {
17+
let node: Box<GcNode<dyn Debug>> = Box::new(GcNode {
18+
gets_swapped_with_next: 42,
19+
next: None,
20+
tail: Box::new(1),
21+
});
22+
23+
assert_eq!(node.gets_swapped_with_next, 42);
24+
assert!(node.next.is_none());
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// run-pass
2+
3+
// Check that unsizing does not change which field is considered for niche layout.
4+
5+
#![feature(offset_of)]
6+
#![allow(dead_code)]
7+
8+
#[derive(Clone)]
9+
struct WideptrField<T: ?Sized> {
10+
first: usize,
11+
second: usize,
12+
niche: NicheAtEnd,
13+
tail: T,
14+
}
15+
16+
#[derive(Clone)]
17+
#[repr(C)]
18+
struct NicheAtEnd {
19+
arr: [u8; 7],
20+
b: bool,
21+
}
22+
23+
type Tail = [bool; 8];
24+
25+
fn main() {
26+
assert_eq!(
27+
core::mem::offset_of!(WideptrField<Tail>, niche),
28+
core::mem::offset_of!(WideptrField<dyn Send>, niche)
29+
);
30+
}

0 commit comments

Comments
 (0)