Skip to content

Commit 7a17f57

Browse files
committed
Auto merge of rust-lang#112157 - erikdesjardins:align, r=nikic
Resurrect: rustc_target: Add alignment to indirectly-passed by-value types, correcting the alignment of byval on x86 in the process. Same as rust-lang#111551, which I [accidentally closed](rust-lang#111551 (comment)) :/ --- This resurrects PR rust-lang#103830, which has sat idle for a while. Beyond rust-lang#103830, this also: - fixes byval alignment for types containing vectors on Darwin (see `tests/codegen/align-byval-vector.rs`) - fixes byval alignment for overaligned types on x86 Windows (see `tests/codegen/align-byval.rs`) - fixes ABI for types with 128bit requested alignment on ARM64 Linux (see `tests/codegen/aarch64-struct-align-128.rs`) r? `@nikic` --- `@pcwalton's` original PR description is reproduced below: Commit 88e4d2c from five years ago removed support for alignment on indirectly-passed arguments because of problems with the `i686-pc-windows-msvc` target. Unfortunately, the `memcpy` optimizations I recently added to LLVM 16 depend on this to forward `memcpy`s. This commit attempts to fix the problems with `byval` parameters on that target and now correctly adds the `align` attribute. The problem is summarized in [this comment] by `@eddyb.` Briefly, 32-bit x86 has special alignment rules for `byval` parameters: for the most part, their alignment is forced to 4. This is not well-documented anywhere but in the Clang source. I looked at the logic in Clang `TargetInfo.cpp` and tried to replicate it here. The relevant methods in that file are `X86_32ABIInfo::getIndirectResult()` and `X86_32ABIInfo::getTypeStackAlignInBytes()`. The `align` parameter attribute for `byval` parameters in LLVM must match the platform ABI, or miscompilations will occur. Note that this doesn't use the approach suggested by eddyb, because I felt it was overkill to store the alignment in `on_stack` when special handling is really only needed for 32-bit x86. As a side effect, this should fix rust-lang#80127, because it will make the `align` parameter attribute for `byval` parameters match the platform ABI on LLVM x86-64. [this comment]: rust-lang#80822 (comment)
2 parents 4d6e426 + 2daacf5 commit 7a17f57

32 files changed

+1251
-93
lines changed

Diff for: compiler/rustc_abi/src/layout.rs

+57-4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ pub trait LayoutCalculator {
4040
largest_niche,
4141
align,
4242
size,
43+
max_repr_align: None,
44+
unadjusted_abi_align: align.abi,
4345
}
4446
}
4547

@@ -122,6 +124,8 @@ pub trait LayoutCalculator {
122124
largest_niche: None,
123125
align: dl.i8_align,
124126
size: Size::ZERO,
127+
max_repr_align: None,
128+
unadjusted_abi_align: dl.i8_align.abi,
125129
}
126130
}
127131

@@ -289,13 +293,18 @@ pub trait LayoutCalculator {
289293
}
290294

291295
let mut align = dl.aggregate_align;
296+
let mut max_repr_align = repr.align;
297+
let mut unadjusted_abi_align = align.abi;
298+
292299
let mut variant_layouts = variants
293300
.iter_enumerated()
294301
.map(|(j, v)| {
295302
let mut st = self.univariant(dl, v, repr, StructKind::AlwaysSized)?;
296303
st.variants = Variants::Single { index: j };
297304

298305
align = align.max(st.align);
306+
max_repr_align = max_repr_align.max(st.max_repr_align);
307+
unadjusted_abi_align = unadjusted_abi_align.max(st.unadjusted_abi_align);
299308

300309
Some(st)
301310
})
@@ -422,6 +431,8 @@ pub trait LayoutCalculator {
422431
largest_niche,
423432
size,
424433
align,
434+
max_repr_align,
435+
unadjusted_abi_align,
425436
};
426437

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

458469
let mut align = dl.aggregate_align;
470+
let mut max_repr_align = repr.align;
471+
let mut unadjusted_abi_align = align.abi;
472+
459473
let mut size = Size::ZERO;
460474

461475
// We're interested in the smallest alignment, so start large.
@@ -498,6 +512,8 @@ pub trait LayoutCalculator {
498512
}
499513
size = cmp::max(size, st.size);
500514
align = align.max(st.align);
515+
max_repr_align = max_repr_align.max(st.max_repr_align);
516+
unadjusted_abi_align = unadjusted_abi_align.max(st.unadjusted_abi_align);
501517
Some(st)
502518
})
503519
.collect::<Option<IndexVec<VariantIdx, _>>>()?;
@@ -691,6 +707,8 @@ pub trait LayoutCalculator {
691707
abi,
692708
align,
693709
size,
710+
max_repr_align,
711+
unadjusted_abi_align,
694712
};
695713

696714
let tagged_layout = TmpLayout { layout: tagged_layout, variants: layout_variants };
@@ -730,10 +748,7 @@ pub trait LayoutCalculator {
730748
let dl = self.current_data_layout();
731749
let dl = dl.borrow();
732750
let mut align = if repr.pack.is_some() { dl.i8_align } else { dl.aggregate_align };
733-
734-
if let Some(repr_align) = repr.align {
735-
align = align.max(AbiAndPrefAlign::new(repr_align));
736-
}
751+
let mut max_repr_align = repr.align;
737752

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

753768
align = align.max(field.align());
769+
max_repr_align = max_repr_align.max(field.max_repr_align());
754770
size = cmp::max(size, field.size());
755771

756772
if field.0.is_zst() {
@@ -787,6 +803,14 @@ pub trait LayoutCalculator {
787803
if let Some(pack) = repr.pack {
788804
align = align.min(AbiAndPrefAlign::new(pack));
789805
}
806+
// The unadjusted ABI alignment does not include repr(align), but does include repr(pack).
807+
// See documentation on `LayoutS::unadjusted_abi_align`.
808+
let unadjusted_abi_align = align.abi;
809+
if let Some(repr_align) = repr.align {
810+
align = align.max(AbiAndPrefAlign::new(repr_align));
811+
}
812+
// `align` must not be modified after this, or `unadjusted_abi_align` could be inaccurate.
813+
let align = align;
790814

791815
// If all non-ZST fields have the same ABI, we may forward that ABI
792816
// for the union as a whole, unless otherwise inhibited.
@@ -809,6 +833,8 @@ pub trait LayoutCalculator {
809833
largest_niche: None,
810834
align,
811835
size: size.align_to(align.abi),
836+
max_repr_align,
837+
unadjusted_abi_align,
812838
})
813839
}
814840
}
@@ -829,6 +855,7 @@ fn univariant(
829855
) -> Option<LayoutS> {
830856
let pack = repr.pack;
831857
let mut align = if pack.is_some() { dl.i8_align } else { dl.aggregate_align };
858+
let mut max_repr_align = repr.align;
832859
let mut inverse_memory_index: IndexVec<u32, FieldIdx> = fields.indices().collect();
833860
let optimize = !repr.inhibit_struct_field_reordering_opt();
834861
if optimize && fields.len() > 1 {
@@ -997,6 +1024,7 @@ fn univariant(
9971024
};
9981025
offset = offset.align_to(field_align.abi);
9991026
align = align.max(field_align);
1027+
max_repr_align = max_repr_align.max(field.max_repr_align());
10001028

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

10191047
offset = offset.checked_add(field.size(), dl)?;
10201048
}
1049+
1050+
// The unadjusted ABI alignment does not include repr(align), but does include repr(pack).
1051+
// See documentation on `LayoutS::unadjusted_abi_align`.
1052+
let unadjusted_abi_align = align.abi;
10211053
if let Some(repr_align) = repr.align {
10221054
align = align.max(AbiAndPrefAlign::new(repr_align));
10231055
}
1056+
// `align` must not be modified after this point, or `unadjusted_abi_align` could be inaccurate.
1057+
let align = align;
1058+
10241059
debug!("univariant min_size: {:?}", offset);
10251060
let min_size = offset;
10261061
// As stated above, inverse_memory_index holds field indices by increasing offset.
@@ -1036,6 +1071,7 @@ fn univariant(
10361071
inverse_memory_index.into_iter().map(FieldIdx::as_u32).collect()
10371072
};
10381073
let size = min_size.align_to(align.abi);
1074+
let mut layout_of_single_non_zst_field = None;
10391075
let mut abi = Abi::Aggregate { sized };
10401076
// Unpack newtype ABIs and find scalar pairs.
10411077
if sized && size.bytes() > 0 {
@@ -1045,6 +1081,8 @@ fn univariant(
10451081
match (non_zst_fields.next(), non_zst_fields.next(), non_zst_fields.next()) {
10461082
// We have exactly one non-ZST field.
10471083
(Some((i, field)), None, None) => {
1084+
layout_of_single_non_zst_field = Some(field);
1085+
10481086
// Field fills the struct and it has a scalar or scalar pair ABI.
10491087
if offsets[i].bytes() == 0 && align.abi == field.align().abi && size == field.size()
10501088
{
@@ -1102,13 +1140,28 @@ fn univariant(
11021140
if fields.iter().any(|f| f.abi().is_uninhabited()) {
11031141
abi = Abi::Uninhabited;
11041142
}
1143+
1144+
let unadjusted_abi_align = if repr.transparent() {
1145+
match layout_of_single_non_zst_field {
1146+
Some(l) => l.unadjusted_abi_align(),
1147+
None => {
1148+
// `repr(transparent)` with all ZST fields.
1149+
align.abi
1150+
}
1151+
}
1152+
} else {
1153+
unadjusted_abi_align
1154+
};
1155+
11051156
Some(LayoutS {
11061157
variants: Variants::Single { index: FIRST_VARIANT },
11071158
fields: FieldsShape::Arbitrary { offsets, memory_index },
11081159
abi,
11091160
largest_niche,
11101161
align,
11111162
size,
1163+
max_repr_align,
1164+
unadjusted_abi_align,
11121165
})
11131166
}
11141167

Diff for: compiler/rustc_abi/src/lib.rs

+32-1
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,16 @@ pub struct LayoutS {
15311531

15321532
pub align: AbiAndPrefAlign,
15331533
pub size: Size,
1534+
1535+
/// The largest alignment explicitly requested with `repr(align)` on this type or any field.
1536+
/// Only used on i686-windows, where the argument passing ABI is different when alignment is
1537+
/// requested, even if the requested alignment is equal to the natural alignment.
1538+
pub max_repr_align: Option<Align>,
1539+
1540+
/// The alignment the type would have, ignoring any `repr(align)` but including `repr(packed)`.
1541+
/// Only used on aarch64-linux, where the argument passing ABI ignores the requested alignment
1542+
/// in some cases.
1543+
pub unadjusted_abi_align: Align,
15341544
}
15351545

15361546
impl LayoutS {
@@ -1545,6 +1555,8 @@ impl LayoutS {
15451555
largest_niche,
15461556
size,
15471557
align,
1558+
max_repr_align: None,
1559+
unadjusted_abi_align: align.abi,
15481560
}
15491561
}
15501562
}
@@ -1554,14 +1566,25 @@ impl fmt::Debug for LayoutS {
15541566
// This is how `Layout` used to print before it become
15551567
// `Interned<LayoutS>`. We print it like this to avoid having to update
15561568
// expected output in a lot of tests.
1557-
let LayoutS { size, align, abi, fields, largest_niche, variants } = self;
1569+
let LayoutS {
1570+
size,
1571+
align,
1572+
abi,
1573+
fields,
1574+
largest_niche,
1575+
variants,
1576+
max_repr_align,
1577+
unadjusted_abi_align,
1578+
} = self;
15581579
f.debug_struct("Layout")
15591580
.field("size", size)
15601581
.field("align", align)
15611582
.field("abi", abi)
15621583
.field("fields", fields)
15631584
.field("largest_niche", largest_niche)
15641585
.field("variants", variants)
1586+
.field("max_repr_align", max_repr_align)
1587+
.field("unadjusted_abi_align", unadjusted_abi_align)
15651588
.finish()
15661589
}
15671590
}
@@ -1602,6 +1625,14 @@ impl<'a> Layout<'a> {
16021625
self.0.0.size
16031626
}
16041627

1628+
pub fn max_repr_align(self) -> Option<Align> {
1629+
self.0.0.max_repr_align
1630+
}
1631+
1632+
pub fn unadjusted_abi_align(self) -> Align {
1633+
self.0.0.unadjusted_abi_align
1634+
}
1635+
16051636
/// Whether the layout is from a type that implements [`std::marker::PointerLike`].
16061637
///
16071638
/// Currently, that means that the type is pointer-sized, pointer-aligned,

Diff for: compiler/rustc_codegen_cranelift/src/abi/comments.rs

+1-8
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,7 @@ pub(super) fn add_local_place_comments<'tcx>(
8080
return;
8181
}
8282
let TyAndLayout { ty, layout } = place.layout();
83-
let rustc_target::abi::LayoutS {
84-
size,
85-
align,
86-
abi: _,
87-
variants: _,
88-
fields: _,
89-
largest_niche: _,
90-
} = layout.0.0;
83+
let rustc_target::abi::LayoutS { size, align, .. } = layout.0.0;
9184

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

Diff for: compiler/rustc_codegen_ssa/src/mir/block.rs

+46-22
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ use rustc_target::abi::call::{ArgAbi, FnAbi, PassMode, Reg};
2323
use rustc_target::abi::{self, HasDataLayout, WrappingRange};
2424
use rustc_target::spec::abi::Abi;
2525

26+
use std::cmp;
27+
2628
// Indicates if we are in the middle of merging a BB's successor into it. This
2729
// can happen when BB jumps directly to its successor and the successor has no
2830
// other predecessors.
@@ -1360,36 +1362,58 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
13601362
// Force by-ref if we have to load through a cast pointer.
13611363
let (mut llval, align, by_ref) = match op.val {
13621364
Immediate(_) | Pair(..) => match arg.mode {
1363-
PassMode::Indirect { .. } | PassMode::Cast(..) => {
1365+
PassMode::Indirect { attrs, .. } => {
1366+
// Indirect argument may have higher alignment requirements than the type's alignment.
1367+
// This can happen, e.g. when passing types with <4 byte alignment on the stack on x86.
1368+
let required_align = match attrs.pointee_align {
1369+
Some(pointee_align) => cmp::max(pointee_align, arg.layout.align.abi),
1370+
None => arg.layout.align.abi,
1371+
};
1372+
let scratch = PlaceRef::alloca_aligned(bx, arg.layout, required_align);
1373+
op.val.store(bx, scratch);
1374+
(scratch.llval, scratch.align, true)
1375+
}
1376+
PassMode::Cast(..) => {
13641377
let scratch = PlaceRef::alloca(bx, arg.layout);
13651378
op.val.store(bx, scratch);
13661379
(scratch.llval, scratch.align, true)
13671380
}
13681381
_ => (op.immediate_or_packed_pair(bx), arg.layout.align.abi, false),
13691382
},
1370-
Ref(llval, _, align) => {
1371-
if arg.is_indirect() && align < arg.layout.align.abi {
1372-
// `foo(packed.large_field)`. We can't pass the (unaligned) field directly. I
1373-
// think that ATM (Rust 1.16) we only pass temporaries, but we shouldn't
1374-
// have scary latent bugs around.
1375-
1376-
let scratch = PlaceRef::alloca(bx, arg.layout);
1377-
base::memcpy_ty(
1378-
bx,
1379-
scratch.llval,
1380-
scratch.align,
1381-
llval,
1382-
align,
1383-
op.layout,
1384-
MemFlags::empty(),
1385-
);
1386-
(scratch.llval, scratch.align, true)
1387-
} else {
1388-
(llval, align, true)
1383+
Ref(llval, _, align) => match arg.mode {
1384+
PassMode::Indirect { attrs, .. } => {
1385+
let required_align = match attrs.pointee_align {
1386+
Some(pointee_align) => cmp::max(pointee_align, arg.layout.align.abi),
1387+
None => arg.layout.align.abi,
1388+
};
1389+
if align < required_align {
1390+
// For `foo(packed.large_field)`, and types with <4 byte alignment on x86,
1391+
// alignment requirements may be higher than the type's alignment, so copy
1392+
// to a higher-aligned alloca.
1393+
let scratch = PlaceRef::alloca_aligned(bx, arg.layout, required_align);
1394+
base::memcpy_ty(
1395+
bx,
1396+
scratch.llval,
1397+
scratch.align,
1398+
llval,
1399+
align,
1400+
op.layout,
1401+
MemFlags::empty(),
1402+
);
1403+
(scratch.llval, scratch.align, true)
1404+
} else {
1405+
(llval, align, true)
1406+
}
13891407
}
1390-
}
1408+
_ => (llval, align, true),
1409+
},
13911410
ZeroSized => match arg.mode {
1392-
PassMode::Indirect { .. } => {
1411+
PassMode::Indirect { on_stack, .. } => {
1412+
if on_stack {
1413+
// It doesn't seem like any target can have `byval` ZSTs, so this assert
1414+
// is here to replace a would-be untested codepath.
1415+
bug!("ZST {op:?} passed on stack with abi {arg:?}");
1416+
}
13931417
// Though `extern "Rust"` doesn't pass ZSTs, some ABIs pass
13941418
// a pointer for `repr(C)` structs even when empty, so get
13951419
// one from an `alloca` (which can be left uninitialized).

Diff for: compiler/rustc_codegen_ssa/src/mir/place.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,18 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
4747
pub fn alloca<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
4848
bx: &mut Bx,
4949
layout: TyAndLayout<'tcx>,
50+
) -> Self {
51+
Self::alloca_aligned(bx, layout, layout.align.abi)
52+
}
53+
54+
pub fn alloca_aligned<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
55+
bx: &mut Bx,
56+
layout: TyAndLayout<'tcx>,
57+
align: Align,
5058
) -> Self {
5159
assert!(layout.is_sized(), "tried to statically allocate unsized place");
52-
let tmp = bx.alloca(bx.cx().backend_type(layout), layout.align.abi);
53-
Self::new_sized(tmp, layout)
60+
let tmp = bx.alloca(bx.cx().backend_type(layout), align);
61+
Self::new_sized_aligned(tmp, layout, align)
5462
}
5563

5664
/// Returns a place for an indirect reference to an unsized place.

0 commit comments

Comments
 (0)