Rollup merge of #122619 - erikdesjardins:cast, r=compiler-errors
Fix some unsoundness with PassMode::Cast ABI Fixes #122617 Reviewable commit-by-commit. More info in each commit message.
This commit is contained in:
commit
bc8415b9e6
8 changed files with 476 additions and 89 deletions
|
@ -16,13 +16,15 @@ pub use rustc_middle::ty::layout::{FAT_PTR_ADDR, FAT_PTR_EXTRA};
|
||||||
use rustc_middle::ty::Ty;
|
use rustc_middle::ty::Ty;
|
||||||
use rustc_session::config;
|
use rustc_session::config;
|
||||||
pub use rustc_target::abi::call::*;
|
pub use rustc_target::abi::call::*;
|
||||||
use rustc_target::abi::{self, HasDataLayout, Int};
|
use rustc_target::abi::{self, HasDataLayout, Int, Size};
|
||||||
pub use rustc_target::spec::abi::Abi;
|
pub use rustc_target::spec::abi::Abi;
|
||||||
use rustc_target::spec::SanitizerSet;
|
use rustc_target::spec::SanitizerSet;
|
||||||
|
|
||||||
use libc::c_uint;
|
use libc::c_uint;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use std::cmp;
|
||||||
|
|
||||||
pub trait ArgAttributesExt {
|
pub trait ArgAttributesExt {
|
||||||
fn apply_attrs_to_llfn(&self, idx: AttributePlace, cx: &CodegenCx<'_, '_>, llfn: &Value);
|
fn apply_attrs_to_llfn(&self, idx: AttributePlace, cx: &CodegenCx<'_, '_>, llfn: &Value);
|
||||||
fn apply_attrs_to_callsite(
|
fn apply_attrs_to_callsite(
|
||||||
|
@ -130,42 +132,36 @@ impl LlvmType for Reg {
|
||||||
impl LlvmType for CastTarget {
|
impl LlvmType for CastTarget {
|
||||||
fn llvm_type<'ll>(&self, cx: &CodegenCx<'ll, '_>) -> &'ll Type {
|
fn llvm_type<'ll>(&self, cx: &CodegenCx<'ll, '_>) -> &'ll Type {
|
||||||
let rest_ll_unit = self.rest.unit.llvm_type(cx);
|
let rest_ll_unit = self.rest.unit.llvm_type(cx);
|
||||||
let (rest_count, rem_bytes) = if self.rest.unit.size.bytes() == 0 {
|
let rest_count = if self.rest.total == Size::ZERO {
|
||||||
(0, 0)
|
0
|
||||||
} else {
|
} else {
|
||||||
(
|
assert_ne!(
|
||||||
self.rest.total.bytes() / self.rest.unit.size.bytes(),
|
self.rest.unit.size,
|
||||||
self.rest.total.bytes() % self.rest.unit.size.bytes(),
|
Size::ZERO,
|
||||||
)
|
"total size {:?} cannot be divided into units of zero size",
|
||||||
|
self.rest.total
|
||||||
|
);
|
||||||
|
if self.rest.total.bytes() % self.rest.unit.size.bytes() != 0 {
|
||||||
|
assert_eq!(self.rest.unit.kind, RegKind::Integer, "only int regs can be split");
|
||||||
|
}
|
||||||
|
self.rest.total.bytes().div_ceil(self.rest.unit.size.bytes())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Simplify to a single unit or an array if there's no prefix.
|
||||||
|
// This produces the same layout, but using a simpler type.
|
||||||
if self.prefix.iter().all(|x| x.is_none()) {
|
if self.prefix.iter().all(|x| x.is_none()) {
|
||||||
// Simplify to a single unit when there is no prefix and size <= unit size
|
if rest_count == 1 {
|
||||||
if self.rest.total <= self.rest.unit.size {
|
|
||||||
return rest_ll_unit;
|
return rest_ll_unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simplify to array when all chunks are the same size and type
|
|
||||||
if rem_bytes == 0 {
|
|
||||||
return cx.type_array(rest_ll_unit, rest_count);
|
return cx.type_array(rest_ll_unit, rest_count);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Create list of fields in the main structure
|
|
||||||
let mut args: Vec<_> = self
|
|
||||||
.prefix
|
|
||||||
.iter()
|
|
||||||
.flat_map(|option_reg| option_reg.map(|reg| reg.llvm_type(cx)))
|
|
||||||
.chain((0..rest_count).map(|_| rest_ll_unit))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Append final integer
|
|
||||||
if rem_bytes != 0 {
|
|
||||||
// Only integers can be really split further.
|
|
||||||
assert_eq!(self.rest.unit.kind, RegKind::Integer);
|
|
||||||
args.push(cx.type_ix(rem_bytes * 8));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Generate a struct type with the prefix and the "rest" arguments.
|
||||||
|
let prefix_args =
|
||||||
|
self.prefix.iter().flat_map(|option_reg| option_reg.map(|reg| reg.llvm_type(cx)));
|
||||||
|
let rest_args = (0..rest_count).map(|_| rest_ll_unit);
|
||||||
|
let args: Vec<_> = prefix_args.chain(rest_args).collect();
|
||||||
cx.type_struct(&args, false)
|
cx.type_struct(&args, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -215,48 +211,34 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> {
|
||||||
bug!("unsized `ArgAbi` must be handled through `store_fn_arg`");
|
bug!("unsized `ArgAbi` must be handled through `store_fn_arg`");
|
||||||
}
|
}
|
||||||
PassMode::Cast { cast, pad_i32: _ } => {
|
PassMode::Cast { cast, pad_i32: _ } => {
|
||||||
// FIXME(eddyb): Figure out when the simpler Store is safe, clang
|
// The ABI mandates that the value is passed as a different struct representation.
|
||||||
// uses it for i16 -> {i8, i8}, but not for i24 -> {i8, i8, i8}.
|
// Spill and reload it from the stack to convert from the ABI representation to
|
||||||
let can_store_through_cast_ptr = false;
|
// the Rust representation.
|
||||||
if can_store_through_cast_ptr {
|
|
||||||
bx.store(val, dst.llval, self.layout.align.abi);
|
|
||||||
} else {
|
|
||||||
// The actual return type is a struct, but the ABI
|
|
||||||
// adaptation code has cast it into some scalar type. The
|
|
||||||
// code that follows is the only reliable way I have
|
|
||||||
// found to do a transform like i64 -> {i32,i32}.
|
|
||||||
// Basically we dump the data onto the stack then memcpy it.
|
|
||||||
//
|
|
||||||
// Other approaches I tried:
|
|
||||||
// - Casting rust ret pointer to the foreign type and using Store
|
|
||||||
// is (a) unsafe if size of foreign type > size of rust type and
|
|
||||||
// (b) runs afoul of strict aliasing rules, yielding invalid
|
|
||||||
// assembly under -O (specifically, the store gets removed).
|
|
||||||
// - Truncating foreign type to correct integral type and then
|
|
||||||
// bitcasting to the struct type yields invalid cast errors.
|
|
||||||
|
|
||||||
// We instead thus allocate some scratch space...
|
|
||||||
let scratch_size = cast.size(bx);
|
let scratch_size = cast.size(bx);
|
||||||
let scratch_align = cast.align(bx);
|
let scratch_align = cast.align(bx);
|
||||||
|
// Note that the ABI type may be either larger or smaller than the Rust type,
|
||||||
|
// due to the presence or absence of trailing padding. For example:
|
||||||
|
// - On some ABIs, the Rust layout { f64, f32, <f32 padding> } may omit padding
|
||||||
|
// when passed by value, making it smaller.
|
||||||
|
// - On some ABIs, the Rust layout { u16, u16, u16 } may be padded up to 8 bytes
|
||||||
|
// when passed by value, making it larger.
|
||||||
|
let copy_bytes = cmp::min(scratch_size.bytes(), self.layout.size.bytes());
|
||||||
|
// Allocate some scratch space...
|
||||||
let llscratch = bx.alloca(cast.llvm_type(bx), scratch_align);
|
let llscratch = bx.alloca(cast.llvm_type(bx), scratch_align);
|
||||||
bx.lifetime_start(llscratch, scratch_size);
|
bx.lifetime_start(llscratch, scratch_size);
|
||||||
|
// ...store the value...
|
||||||
// ... where we first store the value...
|
|
||||||
bx.store(val, llscratch, scratch_align);
|
bx.store(val, llscratch, scratch_align);
|
||||||
|
|
||||||
// ... and then memcpy it to the intended destination.
|
// ... and then memcpy it to the intended destination.
|
||||||
bx.memcpy(
|
bx.memcpy(
|
||||||
dst.llval,
|
dst.llval,
|
||||||
self.layout.align.abi,
|
self.layout.align.abi,
|
||||||
llscratch,
|
llscratch,
|
||||||
scratch_align,
|
scratch_align,
|
||||||
bx.const_usize(self.layout.size.bytes()),
|
bx.const_usize(copy_bytes),
|
||||||
MemFlags::empty(),
|
MemFlags::empty(),
|
||||||
);
|
);
|
||||||
|
|
||||||
bx.lifetime_end(llscratch, scratch_size);
|
bx.lifetime_end(llscratch, scratch_size);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
OperandRef::from_immediate_or_packed_pair(bx, val, self.layout).val.store(bx, dst);
|
OperandRef::from_immediate_or_packed_pair(bx, val, self.layout).val.store(bx, dst);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1505,9 +1505,35 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
|
|
||||||
if by_ref && !arg.is_indirect() {
|
if by_ref && !arg.is_indirect() {
|
||||||
// Have to load the argument, maybe while casting it.
|
// Have to load the argument, maybe while casting it.
|
||||||
if let PassMode::Cast { cast: ty, .. } = &arg.mode {
|
if let PassMode::Cast { cast, pad_i32: _ } = &arg.mode {
|
||||||
let llty = bx.cast_backend_type(ty);
|
// The ABI mandates that the value is passed as a different struct representation.
|
||||||
llval = bx.load(llty, llval, align.min(arg.layout.align.abi));
|
// Spill and reload it from the stack to convert from the Rust representation to
|
||||||
|
// the ABI representation.
|
||||||
|
let scratch_size = cast.size(bx);
|
||||||
|
let scratch_align = cast.align(bx);
|
||||||
|
// Note that the ABI type may be either larger or smaller than the Rust type,
|
||||||
|
// due to the presence or absence of trailing padding. For example:
|
||||||
|
// - On some ABIs, the Rust layout { f64, f32, <f32 padding> } may omit padding
|
||||||
|
// when passed by value, making it smaller.
|
||||||
|
// - On some ABIs, the Rust layout { u16, u16, u16 } may be padded up to 8 bytes
|
||||||
|
// when passed by value, making it larger.
|
||||||
|
let copy_bytes = cmp::min(scratch_size.bytes(), arg.layout.size.bytes());
|
||||||
|
// Allocate some scratch space...
|
||||||
|
let llscratch = bx.alloca(bx.cast_backend_type(cast), scratch_align);
|
||||||
|
bx.lifetime_start(llscratch, scratch_size);
|
||||||
|
// ...memcpy the value...
|
||||||
|
bx.memcpy(
|
||||||
|
llscratch,
|
||||||
|
scratch_align,
|
||||||
|
llval,
|
||||||
|
align,
|
||||||
|
bx.const_usize(copy_bytes),
|
||||||
|
MemFlags::empty(),
|
||||||
|
);
|
||||||
|
// ...and then load it with the ABI type.
|
||||||
|
let cast_ty = bx.cast_backend_type(cast);
|
||||||
|
llval = bx.load(cast_ty, llscratch, scratch_align);
|
||||||
|
bx.lifetime_end(llscratch, scratch_size);
|
||||||
} else {
|
} else {
|
||||||
// We can't use `PlaceRef::load` here because the argument
|
// We can't use `PlaceRef::load` here because the argument
|
||||||
// may have a type we don't treat as immediate, but the ABI
|
// may have a type we don't treat as immediate, but the ABI
|
||||||
|
|
|
@ -251,9 +251,9 @@ pub struct Uniform {
|
||||||
/// The total size of the argument, which can be:
|
/// The total size of the argument, which can be:
|
||||||
/// * equal to `unit.size` (one scalar/vector),
|
/// * equal to `unit.size` (one scalar/vector),
|
||||||
/// * a multiple of `unit.size` (an array of scalar/vectors),
|
/// * a multiple of `unit.size` (an array of scalar/vectors),
|
||||||
/// * if `unit.kind` is `Integer`, the last element
|
/// * if `unit.kind` is `Integer`, the last element can be shorter, i.e., `{ i64, i64, i32 }`
|
||||||
/// can be shorter, i.e., `{ i64, i64, i32 }` for
|
/// for 64-bit integers with a total size of 20 bytes. When the argument is actually passed,
|
||||||
/// 64-bit integers with a total size of 20 bytes.
|
/// this size will be rounded up to the nearest multiple of `unit.size`.
|
||||||
pub total: Size,
|
pub total: Size,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,14 +319,17 @@ impl CastTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size<C: HasDataLayout>(&self, _cx: &C) -> Size {
|
pub fn size<C: HasDataLayout>(&self, _cx: &C) -> Size {
|
||||||
let mut size = self.rest.total;
|
// Prefix arguments are passed in specific designated registers
|
||||||
for i in 0..self.prefix.iter().count() {
|
let prefix_size = self
|
||||||
match self.prefix[i] {
|
.prefix
|
||||||
Some(v) => size += v.size,
|
.iter()
|
||||||
None => {}
|
.filter_map(|x| x.map(|reg| reg.size))
|
||||||
}
|
.fold(Size::ZERO, |acc, size| acc + size);
|
||||||
}
|
// Remaining arguments are passed in chunks of the unit size
|
||||||
return size;
|
let rest_size =
|
||||||
|
self.rest.unit.size * self.rest.total.bytes().div_ceil(self.rest.unit.size.bytes());
|
||||||
|
|
||||||
|
prefix_size + rest_size
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn align<C: HasDataLayout>(&self, cx: &C) -> Align {
|
pub fn align<C: HasDataLayout>(&self, cx: &C) -> Align {
|
||||||
|
|
|
@ -118,6 +118,30 @@ rust_dbg_extern_identity_TwoDoubles(struct TwoDoubles u) {
|
||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FiveU16s {
|
||||||
|
uint16_t one;
|
||||||
|
uint16_t two;
|
||||||
|
uint16_t three;
|
||||||
|
uint16_t four;
|
||||||
|
uint16_t five;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FiveU16s
|
||||||
|
rust_dbg_extern_return_FiveU16s() {
|
||||||
|
struct FiveU16s s;
|
||||||
|
s.one = 10;
|
||||||
|
s.two = 20;
|
||||||
|
s.three = 30;
|
||||||
|
s.four = 40;
|
||||||
|
s.five = 50;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FiveU16s
|
||||||
|
rust_dbg_extern_identity_FiveU16s(struct FiveU16s u) {
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
struct ManyInts {
|
struct ManyInts {
|
||||||
int8_t arg1;
|
int8_t arg1;
|
||||||
int16_t arg2;
|
int16_t arg2;
|
||||||
|
|
280
tests/codegen/cast-target-abi.rs
Normal file
280
tests/codegen/cast-target-abi.rs
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
// ignore-tidy-linelength
|
||||||
|
//@ revisions:aarch64 loongarch64 powerpc64 sparc64
|
||||||
|
//@ compile-flags: -O -C no-prepopulate-passes
|
||||||
|
|
||||||
|
//@[aarch64] compile-flags: --target aarch64-unknown-linux-gnu
|
||||||
|
//@[aarch64] needs-llvm-components: arm
|
||||||
|
//@[loongarch64] compile-flags: --target loongarch64-unknown-linux-gnu
|
||||||
|
//@[loongarch64] needs-llvm-components: loongarch
|
||||||
|
//@[powerpc64] compile-flags: --target powerpc64-unknown-linux-gnu
|
||||||
|
//@[powerpc64] needs-llvm-components: powerpc
|
||||||
|
//@[sparc64] compile-flags: --target sparc64-unknown-linux-gnu
|
||||||
|
//@[sparc64] needs-llvm-components: sparc
|
||||||
|
|
||||||
|
// Tests that arguments with `PassMode::Cast` are handled correctly.
|
||||||
|
|
||||||
|
#![feature(no_core, lang_items)]
|
||||||
|
#![crate_type = "lib"]
|
||||||
|
#![no_std]
|
||||||
|
#![no_core]
|
||||||
|
|
||||||
|
#[lang="sized"] trait Sized { }
|
||||||
|
#[lang="freeze"] trait Freeze { }
|
||||||
|
#[lang="copy"] trait Copy { }
|
||||||
|
|
||||||
|
// This struct will be passed as a single `i64` or `i32`.
|
||||||
|
// This may be (if `i64)) larger than the Rust layout, which is just `{ i16, i16 }`.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct TwoU16s {
|
||||||
|
a: u16,
|
||||||
|
b: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This struct will be passed as `[2 x i64]`.
|
||||||
|
// This is larger than the Rust layout.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct FiveU16s {
|
||||||
|
a: u16,
|
||||||
|
b: u16,
|
||||||
|
c: u16,
|
||||||
|
d: u16,
|
||||||
|
e: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This struct will be passed as `[2 x double]`.
|
||||||
|
// This is the same as the Rust layout.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct DoubleDouble {
|
||||||
|
f: f64,
|
||||||
|
g: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
// On loongarch, this struct will be passed as `{ double, float }`.
|
||||||
|
// This is smaller than the Rust layout, which has trailing padding (`{ f64, f32, <f32 padding> }`)
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct DoubleFloat {
|
||||||
|
f: f64,
|
||||||
|
g: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
fn receives_twou16s(x: TwoU16s);
|
||||||
|
fn returns_twou16s() -> TwoU16s;
|
||||||
|
|
||||||
|
fn receives_fiveu16s(x: FiveU16s);
|
||||||
|
fn returns_fiveu16s() -> FiveU16s;
|
||||||
|
|
||||||
|
fn receives_doubledouble(x: DoubleDouble);
|
||||||
|
fn returns_doubledouble() -> DoubleDouble;
|
||||||
|
|
||||||
|
// These functions cause an ICE in sparc64 ABI code (https://github.com/rust-lang/rust/issues/122620)
|
||||||
|
#[cfg(not(target_arch = "sparc64"))]
|
||||||
|
fn receives_doublefloat(x: DoubleFloat);
|
||||||
|
#[cfg(not(target_arch = "sparc64"))]
|
||||||
|
fn returns_doublefloat() -> DoubleFloat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: @call_twou16s
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn call_twou16s() {
|
||||||
|
// aarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:i64]], align [[ABI_ALIGN:8]]
|
||||||
|
// loongarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:i64]], align [[ABI_ALIGN:8]]
|
||||||
|
// powerpc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:i32]], align [[ABI_ALIGN:4]]
|
||||||
|
// sparc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:i64]], align [[ABI_ALIGN:8]]
|
||||||
|
|
||||||
|
// CHECK: [[RUST_ALLOCA:%.+]] = alloca %TwoU16s, align [[RUST_ALIGN:2]]
|
||||||
|
|
||||||
|
// CHECK: call void @llvm.memcpy.{{.+}}(ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], i64 4, i1 false)
|
||||||
|
// CHECK: [[ABI_VALUE:%.+]] = load [[ABI_TYPE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
// CHECK: call void @receives_twou16s([[ABI_TYPE]] [[ABI_VALUE]])
|
||||||
|
let x = TwoU16s { a: 1, b: 2 };
|
||||||
|
receives_twou16s(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: @return_twou16s
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn return_twou16s() -> TwoU16s {
|
||||||
|
// powerpc returns this struct via sret pointer, it doesn't use the cast ABI.
|
||||||
|
|
||||||
|
// powerpc64: [[RETVAL:%.+]] = alloca %TwoU16s, align 2
|
||||||
|
// powerpc64: call void @returns_twou16s(ptr {{.+}} [[RETVAL]])
|
||||||
|
|
||||||
|
|
||||||
|
// The other targets copy the cast ABI type to an alloca.
|
||||||
|
|
||||||
|
// aarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:i64]], align [[ABI_ALIGN:8]]
|
||||||
|
// loongarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:i64]], align [[ABI_ALIGN:8]]
|
||||||
|
// sparc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:i64]], align [[ABI_ALIGN:8]]
|
||||||
|
|
||||||
|
// aarch64: [[RUST_ALLOCA:%.+]] = alloca %TwoU16s, align [[RUST_ALIGN:2]]
|
||||||
|
// loongarch64: [[RUST_ALLOCA:%.+]] = alloca %TwoU16s, align [[RUST_ALIGN:2]]
|
||||||
|
// sparc64: [[RUST_ALLOCA:%.+]] = alloca %TwoU16s, align [[RUST_ALIGN:2]]
|
||||||
|
|
||||||
|
// aarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_twou16s()
|
||||||
|
// loongarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_twou16s()
|
||||||
|
// sparc64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_twou16s()
|
||||||
|
|
||||||
|
// aarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
// loongarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
// sparc64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
|
||||||
|
// aarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 4, i1 false)
|
||||||
|
// loongarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 4, i1 false)
|
||||||
|
// sparc64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 4, i1 false)
|
||||||
|
returns_twou16s()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: @call_fiveu16s
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn call_fiveu16s() {
|
||||||
|
// CHECK: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
|
||||||
|
|
||||||
|
// CHECK: [[RUST_ALLOCA:%.+]] = alloca %FiveU16s, align 2
|
||||||
|
|
||||||
|
// CHECK: call void @llvm.memcpy.{{.+}}(ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], i64 10, i1 false)
|
||||||
|
// CHECK: [[ABI_VALUE:%.+]] = load [[ABI_TYPE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
// CHECK: call void @receives_fiveu16s([[ABI_TYPE]] [[ABI_VALUE]])
|
||||||
|
let x = FiveU16s { a: 1, b: 2, c: 3, d: 4, e: 5 };
|
||||||
|
receives_fiveu16s(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: @return_fiveu16s
|
||||||
|
// CHECK-SAME: (ptr {{.+}} sret([10 x i8]) align [[RUST_ALIGN:2]] dereferenceable(10) [[RET_PTR:%.+]])
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn return_fiveu16s() -> FiveU16s {
|
||||||
|
// powerpc returns this struct via sret pointer, it doesn't use the cast ABI.
|
||||||
|
|
||||||
|
// powerpc64: call void @returns_fiveu16s(ptr {{.+}} [[RET_PTR]])
|
||||||
|
|
||||||
|
|
||||||
|
// The other targets copy the cast ABI type to the sret pointer.
|
||||||
|
|
||||||
|
// aarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
|
||||||
|
// loongarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
|
||||||
|
// sparc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
|
||||||
|
|
||||||
|
// aarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_fiveu16s()
|
||||||
|
// loongarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_fiveu16s()
|
||||||
|
// sparc64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_fiveu16s()
|
||||||
|
|
||||||
|
// aarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
// loongarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
// sparc64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
|
||||||
|
// aarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RET_PTR]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 10, i1 false)
|
||||||
|
// loongarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RET_PTR]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 10, i1 false)
|
||||||
|
// sparc64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RET_PTR]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 10, i1 false)
|
||||||
|
returns_fiveu16s()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: @call_doubledouble
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn call_doubledouble() {
|
||||||
|
// aarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x double\]]], align [[ABI_ALIGN:8]]
|
||||||
|
// loongarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:{ double, double }]], align [[ABI_ALIGN:8]]
|
||||||
|
// powerpc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
|
||||||
|
// sparc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:{ double, double }]], align [[ABI_ALIGN:8]]
|
||||||
|
|
||||||
|
// CHECK: [[RUST_ALLOCA:%.+]] = alloca %DoubleDouble, align [[RUST_ALIGN:8]]
|
||||||
|
|
||||||
|
// CHECK: call void @llvm.memcpy.{{.+}}(ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], i64 16, i1 false)
|
||||||
|
// CHECK: [[ABI_VALUE:%.+]] = load [[ABI_TYPE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
// CHECK: call void @receives_doubledouble([[ABI_TYPE]] [[ABI_VALUE]])
|
||||||
|
let x = DoubleDouble { f: 1., g: 2. };
|
||||||
|
receives_doubledouble(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: @return_doubledouble
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn return_doubledouble() -> DoubleDouble {
|
||||||
|
// powerpc returns this struct via sret pointer, it doesn't use the cast ABI.
|
||||||
|
|
||||||
|
// powerpc64: [[RETVAL:%.+]] = alloca %DoubleDouble, align 8
|
||||||
|
// powerpc64: call void @returns_doubledouble(ptr {{.+}} [[RETVAL]])
|
||||||
|
|
||||||
|
|
||||||
|
// The other targets copy the cast ABI type to an alloca.
|
||||||
|
|
||||||
|
// aarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x double\]]], align [[ABI_ALIGN:8]]
|
||||||
|
// loongarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:{ double, double }]], align [[ABI_ALIGN:8]]
|
||||||
|
// sparc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:{ double, double }]], align [[ABI_ALIGN:8]]
|
||||||
|
|
||||||
|
// aarch64: [[RUST_ALLOCA:%.+]] = alloca %DoubleDouble, align [[RUST_ALIGN:8]]
|
||||||
|
// loongarch64: [[RUST_ALLOCA:%.+]] = alloca %DoubleDouble, align [[RUST_ALIGN:8]]
|
||||||
|
// sparc64: [[RUST_ALLOCA:%.+]] = alloca %DoubleDouble, align [[RUST_ALIGN:8]]
|
||||||
|
|
||||||
|
// aarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_doubledouble()
|
||||||
|
// loongarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_doubledouble()
|
||||||
|
// sparc64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_doubledouble()
|
||||||
|
|
||||||
|
// aarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
// loongarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
// sparc64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
|
||||||
|
// aarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 16, i1 false)
|
||||||
|
// loongarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 16, i1 false)
|
||||||
|
// sparc64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 16, i1 false)
|
||||||
|
returns_doubledouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test causes an ICE in sparc64 ABI code (https://github.com/rust-lang/rust/issues/122620)
|
||||||
|
#[cfg(not(target_arch = "sparc64"))]
|
||||||
|
// aarch64-LABEL: @call_doublefloat
|
||||||
|
// loongarch64-LABEL: @call_doublefloat
|
||||||
|
// powerpc64-LABEL: @call_doublefloat
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn call_doublefloat() {
|
||||||
|
// aarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
|
||||||
|
// loongarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:{ double, float }]], align [[ABI_ALIGN:8]]
|
||||||
|
// powerpc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
|
||||||
|
|
||||||
|
// aarch64: [[RUST_ALLOCA:%.+]] = alloca %DoubleFloat, align [[RUST_ALIGN:8]]
|
||||||
|
// loongarch64: [[RUST_ALLOCA:%.+]] = alloca %DoubleFloat, align [[RUST_ALIGN:8]]
|
||||||
|
// powerpc64: [[RUST_ALLOCA:%.+]] = alloca %DoubleFloat, align [[RUST_ALIGN:8]]
|
||||||
|
|
||||||
|
// aarch64: call void @llvm.memcpy.{{.+}}(ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], i64 16, i1 false)
|
||||||
|
// loongarch64: call void @llvm.memcpy.{{.+}}(ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], i64 12, i1 false)
|
||||||
|
// powerpc64: call void @llvm.memcpy.{{.+}}(ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], i64 16, i1 false)
|
||||||
|
|
||||||
|
// aarch64: [[ABI_VALUE:%.+]] = load [[ABI_TYPE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
// loongarch64: [[ABI_VALUE:%.+]] = load [[ABI_TYPE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
// powerpc64: [[ABI_VALUE:%.+]] = load [[ABI_TYPE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
|
||||||
|
// aarch64: call void @receives_doublefloat([[ABI_TYPE]] {{(inreg )?}}[[ABI_VALUE]])
|
||||||
|
// loongarch64: call void @receives_doublefloat([[ABI_TYPE]] {{(inreg )?}}[[ABI_VALUE]])
|
||||||
|
// powerpc64: call void @receives_doublefloat([[ABI_TYPE]] {{(inreg )?}}[[ABI_VALUE]])
|
||||||
|
let x = DoubleFloat { f: 1., g: 2. };
|
||||||
|
receives_doublefloat(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test causes an ICE in sparc64 ABI code (https://github.com/rust-lang/rust/issues/122620)
|
||||||
|
#[cfg(not(target_arch = "sparc64"))]
|
||||||
|
// aarch64-LABEL: @return_doublefloat
|
||||||
|
// loongarch64-LABEL: @return_doublefloat
|
||||||
|
// powerpc64-LABEL: @return_doublefloat
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe fn return_doublefloat() -> DoubleFloat {
|
||||||
|
// powerpc returns this struct via sret pointer, it doesn't use the cast ABI.
|
||||||
|
|
||||||
|
// powerpc64: [[RETVAL:%.+]] = alloca %DoubleFloat, align 8
|
||||||
|
// powerpc64: call void @returns_doublefloat(ptr {{.+}} [[RETVAL]])
|
||||||
|
|
||||||
|
|
||||||
|
// The other targets copy the cast ABI type to an alloca.
|
||||||
|
|
||||||
|
// aarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
|
||||||
|
// loongarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:{ double, float }]], align [[ABI_ALIGN:8]]
|
||||||
|
|
||||||
|
// aarch64: [[RUST_ALLOCA:%.+]] = alloca %DoubleFloat, align [[RUST_ALIGN:8]]
|
||||||
|
// loongarch64: [[RUST_ALLOCA:%.+]] = alloca %DoubleFloat, align [[RUST_ALIGN:8]]
|
||||||
|
|
||||||
|
// aarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_doublefloat()
|
||||||
|
// loongarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_doublefloat()
|
||||||
|
|
||||||
|
// aarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
// loongarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
|
||||||
|
|
||||||
|
// aarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 16, i1 false)
|
||||||
|
// loongarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 12, i1 false)
|
||||||
|
returns_doublefloat()
|
||||||
|
}
|
|
@ -1,8 +1,21 @@
|
||||||
|
//@ revisions: linux apple
|
||||||
|
//@ compile-flags: -C opt-level=0 -C no-prepopulate-passes
|
||||||
|
|
||||||
|
//@[linux] compile-flags: --target x86_64-unknown-linux-gnu
|
||||||
|
//@[linux] needs-llvm-components: x86
|
||||||
|
//@[apple] compile-flags: --target x86_64-apple-darwin
|
||||||
|
//@[apple] needs-llvm-components: x86
|
||||||
|
|
||||||
// Regression test for #29988
|
// Regression test for #29988
|
||||||
|
|
||||||
//@ compile-flags: -C no-prepopulate-passes
|
#![feature(no_core, lang_items)]
|
||||||
//@ only-x86_64
|
#![crate_type = "lib"]
|
||||||
//@ ignore-windows
|
#![no_std]
|
||||||
|
#![no_core]
|
||||||
|
|
||||||
|
#[lang="sized"] trait Sized { }
|
||||||
|
#[lang="freeze"] trait Freeze { }
|
||||||
|
#[lang="copy"] trait Copy { }
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct S {
|
struct S {
|
||||||
|
@ -15,11 +28,14 @@ extern "C" {
|
||||||
fn foo(s: S);
|
fn foo(s: S);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
// CHECK-LABEL: @test
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn test() {
|
||||||
let s = S { f1: 1, f2: 2, f3: 3 };
|
let s = S { f1: 1, f2: 2, f3: 3 };
|
||||||
unsafe {
|
unsafe {
|
||||||
// CHECK: load { i64, i32 }, {{.*}}, align 4
|
// CHECK: [[ALLOCA:%.+]] = alloca { i64, i32 }, align 8
|
||||||
// CHECK: call void @foo({ i64, i32 } {{.*}})
|
// CHECK: [[LOAD:%.+]] = load { i64, i32 }, ptr [[ALLOCA]], align 8
|
||||||
|
// CHECK: call void @foo({ i64, i32 } [[LOAD]])
|
||||||
foo(s);
|
foo(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
30
tests/ui/abi/extern/extern-pass-FiveU16s.rs
vendored
Normal file
30
tests/ui/abi/extern/extern-pass-FiveU16s.rs
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
//@ run-pass
|
||||||
|
#![allow(improper_ctypes)]
|
||||||
|
|
||||||
|
// Test a foreign function that accepts and returns a struct by value.
|
||||||
|
|
||||||
|
// FiveU16s in particular is interesting because it is larger than a single 64 bit or 32 bit
|
||||||
|
// register, which are used as cast destinations on some targets, but does not evenly divide those
|
||||||
|
// sizes, causing there to be padding in the last element.
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub struct FiveU16s {
|
||||||
|
one: u16,
|
||||||
|
two: u16,
|
||||||
|
three: u16,
|
||||||
|
four: u16,
|
||||||
|
five: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[link(name = "rust_test_helpers", kind = "static")]
|
||||||
|
extern "C" {
|
||||||
|
pub fn rust_dbg_extern_identity_FiveU16s(v: FiveU16s) -> FiveU16s;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
unsafe {
|
||||||
|
let x = FiveU16s { one: 22, two: 23, three: 24, four: 25, five: 26 };
|
||||||
|
let y = rust_dbg_extern_identity_FiveU16s(x);
|
||||||
|
assert_eq!(x, y);
|
||||||
|
}
|
||||||
|
}
|
26
tests/ui/abi/extern/extern-return-FiveU16s.rs
vendored
Normal file
26
tests/ui/abi/extern/extern-return-FiveU16s.rs
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
//@ run-pass
|
||||||
|
#![allow(improper_ctypes)]
|
||||||
|
|
||||||
|
pub struct FiveU16s {
|
||||||
|
one: u16,
|
||||||
|
two: u16,
|
||||||
|
three: u16,
|
||||||
|
four: u16,
|
||||||
|
five: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[link(name = "rust_test_helpers", kind = "static")]
|
||||||
|
extern "C" {
|
||||||
|
pub fn rust_dbg_extern_return_FiveU16s() -> FiveU16s;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
unsafe {
|
||||||
|
let y = rust_dbg_extern_return_FiveU16s();
|
||||||
|
assert_eq!(y.one, 10);
|
||||||
|
assert_eq!(y.two, 20);
|
||||||
|
assert_eq!(y.three, 30);
|
||||||
|
assert_eq!(y.four, 40);
|
||||||
|
assert_eq!(y.five, 50);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue