Auto merge of #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 #111551, which I [accidentally closed](https://github.com/rust-lang/rust/pull/111551#issuecomment-1571222612) :/
---
This resurrects PR #103830, which has sat idle for a while.
Beyond #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 #80127, because it will make the `align`
parameter attribute for `byval` parameters match the platform ABI on LLVM
x86-64.
[this comment]: #80822 (comment)
This commit is contained in:
commit
7a17f577b3
32 changed files with 1251 additions and 93 deletions
|
@ -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.
|
||||
|
@ -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).
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue