Skip to content
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

Resurrect: rustc_target: Add alignment to indirectly-passed by-value types, correcting the alignment of byval on x86 in the process. #112157

Merged
merged 26 commits into from
Jul 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0becc89
rustc_target: Add alignment to indirectly-passed by-value types, corr…
pcwalton Nov 1, 2022
1022926
align-byval test: use revisions to test different targets
erikdesjardins May 14, 2023
be1d4e3
update array-map test for removed alloca
erikdesjardins May 14, 2023
a07eb0a
implement vector-containing aggregate alignment for x86 darwin
erikdesjardins May 15, 2023
0f7d333
add ignore-cross-compile to run-make/extern-fn-explicit-align
erikdesjardins May 15, 2023
fdaaf86
add align attr to addr-of-mutate test
erikdesjardins May 17, 2023
84ff2e3
extern-fn-explicit-align test: use ffi::c_char instead of i8
erikdesjardins May 17, 2023
5f4472e
extern-fn-explicit-align test: add MSVC compatible alignment attribute
erikdesjardins May 18, 2023
bc9d26a
extern-fn-explicit-align test: cleanup
erikdesjardins May 20, 2023
08d1892
align-byval test: add x86
erikdesjardins May 20, 2023
8ec90f6
align-byval test: add cases distinguishing natural vs forced/requeste…
erikdesjardins May 20, 2023
7089321
extern-fn-struct-passing-abi test: ensure we don't start passing stru…
erikdesjardins May 20, 2023
ed317e4
i686-windows: pass arguments with requested alignment > 4 indirectly
erikdesjardins May 20, 2023
209ed07
align-byval test: add cases for <= align 4
erikdesjardins May 20, 2023
0e76446
ensure byval allocas are sufficiently aligned
erikdesjardins Jun 11, 2023
f704396
align-byval test: add cases for lower requested alignment, wrapped, a…
erikdesjardins Jun 11, 2023
65d11b5
extern-fn-explicit-align test: add wrapped and lower requested alignm…
erikdesjardins Jun 11, 2023
00b3eca
move has_repr to layout, handle repr(transparent) properly
erikdesjardins Jun 11, 2023
4c1dbc3
bless layout tests for has_repr_align in debug output
erikdesjardins Jun 11, 2023
2591c30
cg_clif: add has_repr_align
erikdesjardins Jun 11, 2023
7e933b4
repr(align) <= 4 should still be byval
erikdesjardins Jun 11, 2023
d1e764c
aarch64-linux: properly handle 128bit aligned aggregates
erikdesjardins Jun 15, 2023
c858d34
cg_clif: just ignore all the unused LayoutS fields
erikdesjardins Jun 15, 2023
ecf2390
extern fn-explicit-align test: don't use uint128_t
erikdesjardins Jul 13, 2023
f297f32
extern-fn-explicit-align test: remove unnecessary derives
erikdesjardins Jul 14, 2023
2daacf5
i686-windows: make requested alignment > 4 special case apply transit…
erikdesjardins Jul 14, 2023
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
61 changes: 57 additions & 4 deletions compiler/rustc_abi/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub trait LayoutCalculator {
largest_niche,
align,
size,
max_repr_align: None,
unadjusted_abi_align: align.abi,
}
}

Expand Down Expand Up @@ -122,6 +124,8 @@ pub trait LayoutCalculator {
largest_niche: None,
align: dl.i8_align,
size: Size::ZERO,
max_repr_align: None,
unadjusted_abi_align: dl.i8_align.abi,
}
}

Expand Down Expand Up @@ -289,13 +293,18 @@ pub trait LayoutCalculator {
}

let mut align = dl.aggregate_align;
let mut max_repr_align = repr.align;
let mut unadjusted_abi_align = align.abi;

let mut variant_layouts = variants
.iter_enumerated()
.map(|(j, v)| {
let mut st = self.univariant(dl, v, repr, StructKind::AlwaysSized)?;
st.variants = Variants::Single { index: j };

align = align.max(st.align);
max_repr_align = max_repr_align.max(st.max_repr_align);
unadjusted_abi_align = unadjusted_abi_align.max(st.unadjusted_abi_align);

Some(st)
})
Expand Down Expand Up @@ -422,6 +431,8 @@ pub trait LayoutCalculator {
largest_niche,
size,
align,
max_repr_align,
unadjusted_abi_align,
};

Some(TmpLayout { layout, variants: variant_layouts })
Expand Down Expand Up @@ -456,6 +467,9 @@ pub trait LayoutCalculator {
let (min_ity, signed) = discr_range_of_repr(min, max); //Integer::repr_discr(tcx, ty, &repr, min, max);

let mut align = dl.aggregate_align;
let mut max_repr_align = repr.align;
let mut unadjusted_abi_align = align.abi;

let mut size = Size::ZERO;

// We're interested in the smallest alignment, so start large.
Expand Down Expand Up @@ -498,6 +512,8 @@ pub trait LayoutCalculator {
}
size = cmp::max(size, st.size);
align = align.max(st.align);
max_repr_align = max_repr_align.max(st.max_repr_align);
unadjusted_abi_align = unadjusted_abi_align.max(st.unadjusted_abi_align);
Some(st)
})
.collect::<Option<IndexVec<VariantIdx, _>>>()?;
Expand Down Expand Up @@ -691,6 +707,8 @@ pub trait LayoutCalculator {
abi,
align,
size,
max_repr_align,
unadjusted_abi_align,
};

let tagged_layout = TmpLayout { layout: tagged_layout, variants: layout_variants };
Expand Down Expand Up @@ -730,10 +748,7 @@ pub trait LayoutCalculator {
let dl = self.current_data_layout();
let dl = dl.borrow();
let mut align = if repr.pack.is_some() { dl.i8_align } else { dl.aggregate_align };

if let Some(repr_align) = repr.align {
align = align.max(AbiAndPrefAlign::new(repr_align));
}
let mut max_repr_align = repr.align;

// If all the non-ZST fields have the same ABI and union ABI optimizations aren't
// disabled, we can use that common ABI for the union as a whole.
Expand All @@ -751,6 +766,7 @@ pub trait LayoutCalculator {
assert!(field.0.is_sized());

align = align.max(field.align());
max_repr_align = max_repr_align.max(field.max_repr_align());
size = cmp::max(size, field.size());

if field.0.is_zst() {
Expand Down Expand Up @@ -787,6 +803,14 @@ pub trait LayoutCalculator {
if let Some(pack) = repr.pack {
align = align.min(AbiAndPrefAlign::new(pack));
}
// The unadjusted ABI alignment does not include repr(align), but does include repr(pack).
// See documentation on `LayoutS::unadjusted_abi_align`.
let unadjusted_abi_align = align.abi;
if let Some(repr_align) = repr.align {
align = align.max(AbiAndPrefAlign::new(repr_align));
}
// `align` must not be modified after this, or `unadjusted_abi_align` could be inaccurate.
let align = align;

// If all non-ZST fields have the same ABI, we may forward that ABI
// for the union as a whole, unless otherwise inhibited.
Expand All @@ -809,6 +833,8 @@ pub trait LayoutCalculator {
largest_niche: None,
align,
size: size.align_to(align.abi),
max_repr_align,
unadjusted_abi_align,
})
}
}
Expand All @@ -829,6 +855,7 @@ fn univariant(
) -> Option<LayoutS> {
let pack = repr.pack;
let mut align = if pack.is_some() { dl.i8_align } else { dl.aggregate_align };
let mut max_repr_align = repr.align;
let mut inverse_memory_index: IndexVec<u32, FieldIdx> = fields.indices().collect();
let optimize = !repr.inhibit_struct_field_reordering_opt();
if optimize && fields.len() > 1 {
Expand Down Expand Up @@ -997,6 +1024,7 @@ fn univariant(
};
offset = offset.align_to(field_align.abi);
align = align.max(field_align);
max_repr_align = max_repr_align.max(field.max_repr_align());

debug!("univariant offset: {:?} field: {:#?}", offset, field);
offsets[i] = offset;
Expand All @@ -1018,9 +1046,16 @@ fn univariant(

offset = offset.checked_add(field.size(), dl)?;
}

// The unadjusted ABI alignment does not include repr(align), but does include repr(pack).
// See documentation on `LayoutS::unadjusted_abi_align`.
let unadjusted_abi_align = align.abi;
if let Some(repr_align) = repr.align {
align = align.max(AbiAndPrefAlign::new(repr_align));
}
// `align` must not be modified after this point, or `unadjusted_abi_align` could be inaccurate.
let align = align;

debug!("univariant min_size: {:?}", offset);
let min_size = offset;
// As stated above, inverse_memory_index holds field indices by increasing offset.
Expand All @@ -1036,6 +1071,7 @@ fn univariant(
inverse_memory_index.into_iter().map(FieldIdx::as_u32).collect()
};
let size = min_size.align_to(align.abi);
let mut layout_of_single_non_zst_field = None;
let mut abi = Abi::Aggregate { sized };
// Unpack newtype ABIs and find scalar pairs.
if sized && size.bytes() > 0 {
Expand All @@ -1045,6 +1081,8 @@ fn univariant(
match (non_zst_fields.next(), non_zst_fields.next(), non_zst_fields.next()) {
// We have exactly one non-ZST field.
(Some((i, field)), None, None) => {
layout_of_single_non_zst_field = Some(field);

// Field fills the struct and it has a scalar or scalar pair ABI.
if offsets[i].bytes() == 0 && align.abi == field.align().abi && size == field.size()
{
Expand Down Expand Up @@ -1102,13 +1140,28 @@ fn univariant(
if fields.iter().any(|f| f.abi().is_uninhabited()) {
abi = Abi::Uninhabited;
}

let unadjusted_abi_align = if repr.transparent() {
match layout_of_single_non_zst_field {
Some(l) => l.unadjusted_abi_align(),
None => {
// `repr(transparent)` with all ZST fields.
align.abi
}
}
} else {
unadjusted_abi_align
};

Some(LayoutS {
variants: Variants::Single { index: FIRST_VARIANT },
fields: FieldsShape::Arbitrary { offsets, memory_index },
abi,
largest_niche,
align,
size,
max_repr_align,
unadjusted_abi_align,
})
}

Expand Down
33 changes: 32 additions & 1 deletion compiler/rustc_abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1531,6 +1531,16 @@ pub struct LayoutS {

pub align: AbiAndPrefAlign,
pub size: Size,

/// The largest alignment explicitly requested with `repr(align)` on this type or any field.
/// Only used on i686-windows, where the argument passing ABI is different when alignment is
/// requested, even if the requested alignment is equal to the natural alignment.
pub max_repr_align: Option<Align>,

/// The alignment the type would have, ignoring any `repr(align)` but including `repr(packed)`.
/// Only used on aarch64-linux, where the argument passing ABI ignores the requested alignment
/// in some cases.
pub unadjusted_abi_align: Align,
}

impl LayoutS {
Expand All @@ -1545,6 +1555,8 @@ impl LayoutS {
largest_niche,
size,
align,
max_repr_align: None,
unadjusted_abi_align: align.abi,
}
}
}
Expand All @@ -1554,14 +1566,25 @@ impl fmt::Debug for LayoutS {
// This is how `Layout` used to print before it become
// `Interned<LayoutS>`. We print it like this to avoid having to update
// expected output in a lot of tests.
let LayoutS { size, align, abi, fields, largest_niche, variants } = self;
let LayoutS {
size,
align,
abi,
fields,
largest_niche,
variants,
max_repr_align,
unadjusted_abi_align,
} = self;
f.debug_struct("Layout")
.field("size", size)
.field("align", align)
.field("abi", abi)
.field("fields", fields)
.field("largest_niche", largest_niche)
.field("variants", variants)
.field("max_repr_align", max_repr_align)
.field("unadjusted_abi_align", unadjusted_abi_align)
.finish()
}
}
Expand Down Expand Up @@ -1602,6 +1625,14 @@ impl<'a> Layout<'a> {
self.0.0.size
}

pub fn max_repr_align(self) -> Option<Align> {
self.0.0.max_repr_align
}

pub fn unadjusted_abi_align(self) -> Align {
self.0.0.unadjusted_abi_align
}

/// Whether the layout is from a type that implements [`std::marker::PointerLike`].
///
/// Currently, that means that the type is pointer-sized, pointer-aligned,
Expand Down
9 changes: 1 addition & 8 deletions compiler/rustc_codegen_cranelift/src/abi/comments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,7 @@ pub(super) fn add_local_place_comments<'tcx>(
return;
}
let TyAndLayout { ty, layout } = place.layout();
let rustc_target::abi::LayoutS {
size,
align,
abi: _,
variants: _,
fields: _,
largest_niche: _,
} = layout.0.0;
let rustc_target::abi::LayoutS { size, align, .. } = layout.0.0;

let (kind, extra) = place.debug_comment();

Expand Down
68 changes: 46 additions & 22 deletions compiler/rustc_codegen_ssa/src/mir/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use rustc_target::abi::call::{ArgAbi, FnAbi, PassMode, Reg};
use rustc_target::abi::{self, HasDataLayout, WrappingRange};
use rustc_target::spec::abi::Abi;

use std::cmp;

// Indicates if we are in the middle of merging a BB's successor into it. This
// can happen when BB jumps directly to its successor and the successor has no
// other predecessors.
Expand Down Expand Up @@ -1360,36 +1362,58 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
// Force by-ref if we have to load through a cast pointer.
let (mut llval, align, by_ref) = match op.val {
Immediate(_) | Pair(..) => match arg.mode {
PassMode::Indirect { .. } | PassMode::Cast(..) => {
PassMode::Indirect { attrs, .. } => {
// Indirect argument may have higher alignment requirements than the type's alignment.
// This can happen, e.g. when passing types with <4 byte alignment on the stack on x86.
let required_align = match attrs.pointee_align {
Some(pointee_align) => cmp::max(pointee_align, arg.layout.align.abi),
None => arg.layout.align.abi,
};
let scratch = PlaceRef::alloca_aligned(bx, arg.layout, required_align);
op.val.store(bx, scratch);
(scratch.llval, scratch.align, true)
}
PassMode::Cast(..) => {
let scratch = PlaceRef::alloca(bx, arg.layout);
op.val.store(bx, scratch);
(scratch.llval, scratch.align, true)
}
_ => (op.immediate_or_packed_pair(bx), arg.layout.align.abi, false),
},
Ref(llval, _, align) => {
if arg.is_indirect() && align < arg.layout.align.abi {
// `foo(packed.large_field)`. We can't pass the (unaligned) field directly. I
// think that ATM (Rust 1.16) we only pass temporaries, but we shouldn't
// have scary latent bugs around.

let scratch = PlaceRef::alloca(bx, arg.layout);
base::memcpy_ty(
bx,
scratch.llval,
scratch.align,
llval,
align,
op.layout,
MemFlags::empty(),
);
(scratch.llval, scratch.align, true)
} else {
(llval, align, true)
Ref(llval, _, align) => match arg.mode {
PassMode::Indirect { attrs, .. } => {
let required_align = match attrs.pointee_align {
Some(pointee_align) => cmp::max(pointee_align, arg.layout.align.abi),
None => arg.layout.align.abi,
};
if align < required_align {
// For `foo(packed.large_field)`, and types with <4 byte alignment on x86,
// alignment requirements may be higher than the type's alignment, so copy
// to a higher-aligned alloca.
let scratch = PlaceRef::alloca_aligned(bx, arg.layout, required_align);
base::memcpy_ty(
bx,
scratch.llval,
scratch.align,
llval,
align,
op.layout,
MemFlags::empty(),
);
(scratch.llval, scratch.align, true)
} else {
(llval, align, true)
}
}
}
_ => (llval, align, true),
},
ZeroSized => match arg.mode {
PassMode::Indirect { .. } => {
PassMode::Indirect { on_stack, .. } => {
if on_stack {
// It doesn't seem like any target can have `byval` ZSTs, so this assert
// is here to replace a would-be untested codepath.
bug!("ZST {op:?} passed on stack with abi {arg:?}");
}
// Though `extern "Rust"` doesn't pass ZSTs, some ABIs pass
// a pointer for `repr(C)` structs even when empty, so get
// one from an `alloca` (which can be left uninitialized).
Expand Down
12 changes: 10 additions & 2 deletions compiler/rustc_codegen_ssa/src/mir/place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,18 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
pub fn alloca<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
bx: &mut Bx,
layout: TyAndLayout<'tcx>,
) -> Self {
Self::alloca_aligned(bx, layout, layout.align.abi)
}

pub fn alloca_aligned<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
bx: &mut Bx,
layout: TyAndLayout<'tcx>,
align: Align,
) -> Self {
assert!(layout.is_sized(), "tried to statically allocate unsized place");
let tmp = bx.alloca(bx.cx().backend_type(layout), layout.align.abi);
Self::new_sized(tmp, layout)
let tmp = bx.alloca(bx.cx().backend_type(layout), align);
Self::new_sized_aligned(tmp, layout, align)
}

/// Returns a place for an indirect reference to an unsized place.
Expand Down
Loading