Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c0c80fb

Browse files
authoredApr 3, 2024
Unrolled build for rust-lang#122619
Rollup merge of rust-lang#122619 - erikdesjardins:cast, r=compiler-errors Fix some unsoundness with PassMode::Cast ABI Fixes rust-lang#122617 Reviewable commit-by-commit. More info in each commit message.
2 parents 98efd80 + dec81ac commit c0c80fb

File tree

8 files changed

+476
-89
lines changed

8 files changed

+476
-89
lines changed
 

‎compiler/rustc_codegen_llvm/src/abi.rs

Lines changed: 51 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ pub use rustc_middle::ty::layout::{FAT_PTR_ADDR, FAT_PTR_EXTRA};
1616
use rustc_middle::ty::Ty;
1717
use rustc_session::config;
1818
pub use rustc_target::abi::call::*;
19-
use rustc_target::abi::{self, HasDataLayout, Int};
19+
use rustc_target::abi::{self, HasDataLayout, Int, Size};
2020
pub use rustc_target::spec::abi::Abi;
2121
use rustc_target::spec::SanitizerSet;
2222

2323
use libc::c_uint;
2424
use smallvec::SmallVec;
2525

26+
use std::cmp;
27+
2628
pub trait ArgAttributesExt {
2729
fn apply_attrs_to_llfn(&self, idx: AttributePlace, cx: &CodegenCx<'_, '_>, llfn: &Value);
2830
fn apply_attrs_to_callsite(
@@ -130,42 +132,36 @@ impl LlvmType for Reg {
130132
impl LlvmType for CastTarget {
131133
fn llvm_type<'ll>(&self, cx: &CodegenCx<'ll, '_>) -> &'ll Type {
132134
let rest_ll_unit = self.rest.unit.llvm_type(cx);
133-
let (rest_count, rem_bytes) = if self.rest.unit.size.bytes() == 0 {
134-
(0, 0)
135+
let rest_count = if self.rest.total == Size::ZERO {
136+
0
135137
} else {
136-
(
137-
self.rest.total.bytes() / self.rest.unit.size.bytes(),
138-
self.rest.total.bytes() % self.rest.unit.size.bytes(),
139-
)
138+
assert_ne!(
139+
self.rest.unit.size,
140+
Size::ZERO,
141+
"total size {:?} cannot be divided into units of zero size",
142+
self.rest.total
143+
);
144+
if self.rest.total.bytes() % self.rest.unit.size.bytes() != 0 {
145+
assert_eq!(self.rest.unit.kind, RegKind::Integer, "only int regs can be split");
146+
}
147+
self.rest.total.bytes().div_ceil(self.rest.unit.size.bytes())
140148
};
141149

150+
// Simplify to a single unit or an array if there's no prefix.
151+
// This produces the same layout, but using a simpler type.
142152
if self.prefix.iter().all(|x| x.is_none()) {
143-
// Simplify to a single unit when there is no prefix and size <= unit size
144-
if self.rest.total <= self.rest.unit.size {
153+
if rest_count == 1 {
145154
return rest_ll_unit;
146155
}
147156

148-
// Simplify to array when all chunks are the same size and type
149-
if rem_bytes == 0 {
150-
return cx.type_array(rest_ll_unit, rest_count);
151-
}
152-
}
153-
154-
// Create list of fields in the main structure
155-
let mut args: Vec<_> = self
156-
.prefix
157-
.iter()
158-
.flat_map(|option_reg| option_reg.map(|reg| reg.llvm_type(cx)))
159-
.chain((0..rest_count).map(|_| rest_ll_unit))
160-
.collect();
161-
162-
// Append final integer
163-
if rem_bytes != 0 {
164-
// Only integers can be really split further.
165-
assert_eq!(self.rest.unit.kind, RegKind::Integer);
166-
args.push(cx.type_ix(rem_bytes * 8));
157+
return cx.type_array(rest_ll_unit, rest_count);
167158
}
168159

160+
// Generate a struct type with the prefix and the "rest" arguments.
161+
let prefix_args =
162+
self.prefix.iter().flat_map(|option_reg| option_reg.map(|reg| reg.llvm_type(cx)));
163+
let rest_args = (0..rest_count).map(|_| rest_ll_unit);
164+
let args: Vec<_> = prefix_args.chain(rest_args).collect();
169165
cx.type_struct(&args, false)
170166
}
171167
}
@@ -215,47 +211,33 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> {
215211
bug!("unsized `ArgAbi` must be handled through `store_fn_arg`");
216212
}
217213
PassMode::Cast { cast, pad_i32: _ } => {
218-
// FIXME(eddyb): Figure out when the simpler Store is safe, clang
219-
// uses it for i16 -> {i8, i8}, but not for i24 -> {i8, i8, i8}.
220-
let can_store_through_cast_ptr = false;
221-
if can_store_through_cast_ptr {
222-
bx.store(val, dst.llval, self.layout.align.abi);
223-
} else {
224-
// The actual return type is a struct, but the ABI
225-
// adaptation code has cast it into some scalar type. The
226-
// code that follows is the only reliable way I have
227-
// found to do a transform like i64 -> {i32,i32}.
228-
// Basically we dump the data onto the stack then memcpy it.
229-
//
230-
// Other approaches I tried:
231-
// - Casting rust ret pointer to the foreign type and using Store
232-
// is (a) unsafe if size of foreign type > size of rust type and
233-
// (b) runs afoul of strict aliasing rules, yielding invalid
234-
// assembly under -O (specifically, the store gets removed).
235-
// - Truncating foreign type to correct integral type and then
236-
// bitcasting to the struct type yields invalid cast errors.
237-
238-
// We instead thus allocate some scratch space...
239-
let scratch_size = cast.size(bx);
240-
let scratch_align = cast.align(bx);
241-
let llscratch = bx.alloca(cast.llvm_type(bx), scratch_align);
242-
bx.lifetime_start(llscratch, scratch_size);
243-
244-
// ... where we first store the value...
245-
bx.store(val, llscratch, scratch_align);
246-
247-
// ... and then memcpy it to the intended destination.
248-
bx.memcpy(
249-
dst.llval,
250-
self.layout.align.abi,
251-
llscratch,
252-
scratch_align,
253-
bx.const_usize(self.layout.size.bytes()),
254-
MemFlags::empty(),
255-
);
256-
257-
bx.lifetime_end(llscratch, scratch_size);
258-
}
214+
// The ABI mandates that the value is passed as a different struct representation.
215+
// Spill and reload it from the stack to convert from the ABI representation to
216+
// the Rust representation.
217+
let scratch_size = cast.size(bx);
218+
let scratch_align = cast.align(bx);
219+
// Note that the ABI type may be either larger or smaller than the Rust type,
220+
// due to the presence or absence of trailing padding. For example:
221+
// - On some ABIs, the Rust layout { f64, f32, <f32 padding> } may omit padding
222+
// when passed by value, making it smaller.
223+
// - On some ABIs, the Rust layout { u16, u16, u16 } may be padded up to 8 bytes
224+
// when passed by value, making it larger.
225+
let copy_bytes = cmp::min(scratch_size.bytes(), self.layout.size.bytes());
226+
// Allocate some scratch space...
227+
let llscratch = bx.alloca(cast.llvm_type(bx), scratch_align);
228+
bx.lifetime_start(llscratch, scratch_size);
229+
// ...store the value...
230+
bx.store(val, llscratch, scratch_align);
231+
// ... and then memcpy it to the intended destination.
232+
bx.memcpy(
233+
dst.llval,
234+
self.layout.align.abi,
235+
llscratch,
236+
scratch_align,
237+
bx.const_usize(copy_bytes),
238+
MemFlags::empty(),
239+
);
240+
bx.lifetime_end(llscratch, scratch_size);
259241
}
260242
_ => {
261243
OperandRef::from_immediate_or_packed_pair(bx, val, self.layout).val.store(bx, dst);

‎compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,9 +1505,35 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
15051505

15061506
if by_ref && !arg.is_indirect() {
15071507
// Have to load the argument, maybe while casting it.
1508-
if let PassMode::Cast { cast: ty, .. } = &arg.mode {
1509-
let llty = bx.cast_backend_type(ty);
1510-
llval = bx.load(llty, llval, align.min(arg.layout.align.abi));
1508+
if let PassMode::Cast { cast, pad_i32: _ } = &arg.mode {
1509+
// The ABI mandates that the value is passed as a different struct representation.
1510+
// Spill and reload it from the stack to convert from the Rust representation to
1511+
// the ABI representation.
1512+
let scratch_size = cast.size(bx);
1513+
let scratch_align = cast.align(bx);
1514+
// Note that the ABI type may be either larger or smaller than the Rust type,
1515+
// due to the presence or absence of trailing padding. For example:
1516+
// - On some ABIs, the Rust layout { f64, f32, <f32 padding> } may omit padding
1517+
// when passed by value, making it smaller.
1518+
// - On some ABIs, the Rust layout { u16, u16, u16 } may be padded up to 8 bytes
1519+
// when passed by value, making it larger.
1520+
let copy_bytes = cmp::min(scratch_size.bytes(), arg.layout.size.bytes());
1521+
// Allocate some scratch space...
1522+
let llscratch = bx.alloca(bx.cast_backend_type(cast), scratch_align);
1523+
bx.lifetime_start(llscratch, scratch_size);
1524+
// ...memcpy the value...
1525+
bx.memcpy(
1526+
llscratch,
1527+
scratch_align,
1528+
llval,
1529+
align,
1530+
bx.const_usize(copy_bytes),
1531+
MemFlags::empty(),
1532+
);
1533+
// ...and then load it with the ABI type.
1534+
let cast_ty = bx.cast_backend_type(cast);
1535+
llval = bx.load(cast_ty, llscratch, scratch_align);
1536+
bx.lifetime_end(llscratch, scratch_size);
15111537
} else {
15121538
// We can't use `PlaceRef::load` here because the argument
15131539
// may have a type we don't treat as immediate, but the ABI

‎compiler/rustc_target/src/abi/call/mod.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,9 @@ pub struct Uniform {
251251
/// The total size of the argument, which can be:
252252
/// * equal to `unit.size` (one scalar/vector),
253253
/// * a multiple of `unit.size` (an array of scalar/vectors),
254-
/// * if `unit.kind` is `Integer`, the last element
255-
/// can be shorter, i.e., `{ i64, i64, i32 }` for
256-
/// 64-bit integers with a total size of 20 bytes.
254+
/// * if `unit.kind` is `Integer`, the last element can be shorter, i.e., `{ i64, i64, i32 }`
255+
/// for 64-bit integers with a total size of 20 bytes. When the argument is actually passed,
256+
/// this size will be rounded up to the nearest multiple of `unit.size`.
257257
pub total: Size,
258258
}
259259

@@ -319,14 +319,17 @@ impl CastTarget {
319319
}
320320

321321
pub fn size<C: HasDataLayout>(&self, _cx: &C) -> Size {
322-
let mut size = self.rest.total;
323-
for i in 0..self.prefix.iter().count() {
324-
match self.prefix[i] {
325-
Some(v) => size += v.size,
326-
None => {}
327-
}
328-
}
329-
return size;
322+
// Prefix arguments are passed in specific designated registers
323+
let prefix_size = self
324+
.prefix
325+
.iter()
326+
.filter_map(|x| x.map(|reg| reg.size))
327+
.fold(Size::ZERO, |acc, size| acc + size);
328+
// Remaining arguments are passed in chunks of the unit size
329+
let rest_size =
330+
self.rest.unit.size * self.rest.total.bytes().div_ceil(self.rest.unit.size.bytes());
331+
332+
prefix_size + rest_size
330333
}
331334

332335
pub fn align<C: HasDataLayout>(&self, cx: &C) -> Align {

‎tests/auxiliary/rust_test_helpers.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,30 @@ rust_dbg_extern_identity_TwoDoubles(struct TwoDoubles u) {
118118
return u;
119119
}
120120

121+
struct FiveU16s {
122+
uint16_t one;
123+
uint16_t two;
124+
uint16_t three;
125+
uint16_t four;
126+
uint16_t five;
127+
};
128+
129+
struct FiveU16s
130+
rust_dbg_extern_return_FiveU16s() {
131+
struct FiveU16s s;
132+
s.one = 10;
133+
s.two = 20;
134+
s.three = 30;
135+
s.four = 40;
136+
s.five = 50;
137+
return s;
138+
}
139+
140+
struct FiveU16s
141+
rust_dbg_extern_identity_FiveU16s(struct FiveU16s u) {
142+
return u;
143+
}
144+
121145
struct ManyInts {
122146
int8_t arg1;
123147
int16_t arg2;

‎tests/codegen/cast-target-abi.rs

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
// ignore-tidy-linelength
2+
//@ revisions:aarch64 loongarch64 powerpc64 sparc64
3+
//@ compile-flags: -O -C no-prepopulate-passes
4+
5+
//@[aarch64] compile-flags: --target aarch64-unknown-linux-gnu
6+
//@[aarch64] needs-llvm-components: arm
7+
//@[loongarch64] compile-flags: --target loongarch64-unknown-linux-gnu
8+
//@[loongarch64] needs-llvm-components: loongarch
9+
//@[powerpc64] compile-flags: --target powerpc64-unknown-linux-gnu
10+
//@[powerpc64] needs-llvm-components: powerpc
11+
//@[sparc64] compile-flags: --target sparc64-unknown-linux-gnu
12+
//@[sparc64] needs-llvm-components: sparc
13+
14+
// Tests that arguments with `PassMode::Cast` are handled correctly.
15+
16+
#![feature(no_core, lang_items)]
17+
#![crate_type = "lib"]
18+
#![no_std]
19+
#![no_core]
20+
21+
#[lang="sized"] trait Sized { }
22+
#[lang="freeze"] trait Freeze { }
23+
#[lang="copy"] trait Copy { }
24+
25+
// This struct will be passed as a single `i64` or `i32`.
26+
// This may be (if `i64)) larger than the Rust layout, which is just `{ i16, i16 }`.
27+
#[repr(C)]
28+
pub struct TwoU16s {
29+
a: u16,
30+
b: u16,
31+
}
32+
33+
// This struct will be passed as `[2 x i64]`.
34+
// This is larger than the Rust layout.
35+
#[repr(C)]
36+
pub struct FiveU16s {
37+
a: u16,
38+
b: u16,
39+
c: u16,
40+
d: u16,
41+
e: u16,
42+
}
43+
44+
// This struct will be passed as `[2 x double]`.
45+
// This is the same as the Rust layout.
46+
#[repr(C)]
47+
pub struct DoubleDouble {
48+
f: f64,
49+
g: f64,
50+
}
51+
52+
// On loongarch, this struct will be passed as `{ double, float }`.
53+
// This is smaller than the Rust layout, which has trailing padding (`{ f64, f32, <f32 padding> }`)
54+
#[repr(C)]
55+
pub struct DoubleFloat {
56+
f: f64,
57+
g: f32,
58+
}
59+
60+
extern "C" {
61+
fn receives_twou16s(x: TwoU16s);
62+
fn returns_twou16s() -> TwoU16s;
63+
64+
fn receives_fiveu16s(x: FiveU16s);
65+
fn returns_fiveu16s() -> FiveU16s;
66+
67+
fn receives_doubledouble(x: DoubleDouble);
68+
fn returns_doubledouble() -> DoubleDouble;
69+
70+
// These functions cause an ICE in sparc64 ABI code (https://github.com/rust-lang/rust/issues/122620)
71+
#[cfg(not(target_arch = "sparc64"))]
72+
fn receives_doublefloat(x: DoubleFloat);
73+
#[cfg(not(target_arch = "sparc64"))]
74+
fn returns_doublefloat() -> DoubleFloat;
75+
}
76+
77+
// CHECK-LABEL: @call_twou16s
78+
#[no_mangle]
79+
pub unsafe fn call_twou16s() {
80+
// aarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:i64]], align [[ABI_ALIGN:8]]
81+
// loongarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:i64]], align [[ABI_ALIGN:8]]
82+
// powerpc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:i32]], align [[ABI_ALIGN:4]]
83+
// sparc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:i64]], align [[ABI_ALIGN:8]]
84+
85+
// CHECK: [[RUST_ALLOCA:%.+]] = alloca %TwoU16s, align [[RUST_ALIGN:2]]
86+
87+
// CHECK: call void @llvm.memcpy.{{.+}}(ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], i64 4, i1 false)
88+
// CHECK: [[ABI_VALUE:%.+]] = load [[ABI_TYPE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
89+
// CHECK: call void @receives_twou16s([[ABI_TYPE]] [[ABI_VALUE]])
90+
let x = TwoU16s { a: 1, b: 2 };
91+
receives_twou16s(x);
92+
}
93+
94+
// CHECK-LABEL: @return_twou16s
95+
#[no_mangle]
96+
pub unsafe fn return_twou16s() -> TwoU16s {
97+
// powerpc returns this struct via sret pointer, it doesn't use the cast ABI.
98+
99+
// powerpc64: [[RETVAL:%.+]] = alloca %TwoU16s, align 2
100+
// powerpc64: call void @returns_twou16s(ptr {{.+}} [[RETVAL]])
101+
102+
103+
// The other targets copy the cast ABI type to an alloca.
104+
105+
// aarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:i64]], align [[ABI_ALIGN:8]]
106+
// loongarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:i64]], align [[ABI_ALIGN:8]]
107+
// sparc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:i64]], align [[ABI_ALIGN:8]]
108+
109+
// aarch64: [[RUST_ALLOCA:%.+]] = alloca %TwoU16s, align [[RUST_ALIGN:2]]
110+
// loongarch64: [[RUST_ALLOCA:%.+]] = alloca %TwoU16s, align [[RUST_ALIGN:2]]
111+
// sparc64: [[RUST_ALLOCA:%.+]] = alloca %TwoU16s, align [[RUST_ALIGN:2]]
112+
113+
// aarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_twou16s()
114+
// loongarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_twou16s()
115+
// sparc64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_twou16s()
116+
117+
// aarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
118+
// loongarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
119+
// sparc64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
120+
121+
// aarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 4, i1 false)
122+
// loongarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 4, i1 false)
123+
// sparc64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 4, i1 false)
124+
returns_twou16s()
125+
}
126+
127+
// CHECK-LABEL: @call_fiveu16s
128+
#[no_mangle]
129+
pub unsafe fn call_fiveu16s() {
130+
// CHECK: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
131+
132+
// CHECK: [[RUST_ALLOCA:%.+]] = alloca %FiveU16s, align 2
133+
134+
// CHECK: call void @llvm.memcpy.{{.+}}(ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], i64 10, i1 false)
135+
// CHECK: [[ABI_VALUE:%.+]] = load [[ABI_TYPE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
136+
// CHECK: call void @receives_fiveu16s([[ABI_TYPE]] [[ABI_VALUE]])
137+
let x = FiveU16s { a: 1, b: 2, c: 3, d: 4, e: 5 };
138+
receives_fiveu16s(x);
139+
}
140+
141+
// CHECK-LABEL: @return_fiveu16s
142+
// CHECK-SAME: (ptr {{.+}} sret([10 x i8]) align [[RUST_ALIGN:2]] dereferenceable(10) [[RET_PTR:%.+]])
143+
#[no_mangle]
144+
pub unsafe fn return_fiveu16s() -> FiveU16s {
145+
// powerpc returns this struct via sret pointer, it doesn't use the cast ABI.
146+
147+
// powerpc64: call void @returns_fiveu16s(ptr {{.+}} [[RET_PTR]])
148+
149+
150+
// The other targets copy the cast ABI type to the sret pointer.
151+
152+
// aarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
153+
// loongarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
154+
// sparc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
155+
156+
// aarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_fiveu16s()
157+
// loongarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_fiveu16s()
158+
// sparc64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_fiveu16s()
159+
160+
// aarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
161+
// loongarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
162+
// sparc64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
163+
164+
// aarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RET_PTR]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 10, i1 false)
165+
// loongarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RET_PTR]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 10, i1 false)
166+
// sparc64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RET_PTR]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 10, i1 false)
167+
returns_fiveu16s()
168+
}
169+
170+
// CHECK-LABEL: @call_doubledouble
171+
#[no_mangle]
172+
pub unsafe fn call_doubledouble() {
173+
// aarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x double\]]], align [[ABI_ALIGN:8]]
174+
// loongarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:{ double, double }]], align [[ABI_ALIGN:8]]
175+
// powerpc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
176+
// sparc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:{ double, double }]], align [[ABI_ALIGN:8]]
177+
178+
// CHECK: [[RUST_ALLOCA:%.+]] = alloca %DoubleDouble, align [[RUST_ALIGN:8]]
179+
180+
// CHECK: call void @llvm.memcpy.{{.+}}(ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], i64 16, i1 false)
181+
// CHECK: [[ABI_VALUE:%.+]] = load [[ABI_TYPE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
182+
// CHECK: call void @receives_doubledouble([[ABI_TYPE]] [[ABI_VALUE]])
183+
let x = DoubleDouble { f: 1., g: 2. };
184+
receives_doubledouble(x);
185+
}
186+
187+
// CHECK-LABEL: @return_doubledouble
188+
#[no_mangle]
189+
pub unsafe fn return_doubledouble() -> DoubleDouble {
190+
// powerpc returns this struct via sret pointer, it doesn't use the cast ABI.
191+
192+
// powerpc64: [[RETVAL:%.+]] = alloca %DoubleDouble, align 8
193+
// powerpc64: call void @returns_doubledouble(ptr {{.+}} [[RETVAL]])
194+
195+
196+
// The other targets copy the cast ABI type to an alloca.
197+
198+
// aarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x double\]]], align [[ABI_ALIGN:8]]
199+
// loongarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:{ double, double }]], align [[ABI_ALIGN:8]]
200+
// sparc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:{ double, double }]], align [[ABI_ALIGN:8]]
201+
202+
// aarch64: [[RUST_ALLOCA:%.+]] = alloca %DoubleDouble, align [[RUST_ALIGN:8]]
203+
// loongarch64: [[RUST_ALLOCA:%.+]] = alloca %DoubleDouble, align [[RUST_ALIGN:8]]
204+
// sparc64: [[RUST_ALLOCA:%.+]] = alloca %DoubleDouble, align [[RUST_ALIGN:8]]
205+
206+
// aarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_doubledouble()
207+
// loongarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_doubledouble()
208+
// sparc64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_doubledouble()
209+
210+
// aarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
211+
// loongarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
212+
// sparc64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
213+
214+
// aarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 16, i1 false)
215+
// loongarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 16, i1 false)
216+
// sparc64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 16, i1 false)
217+
returns_doubledouble()
218+
}
219+
220+
// This test causes an ICE in sparc64 ABI code (https://github.com/rust-lang/rust/issues/122620)
221+
#[cfg(not(target_arch = "sparc64"))]
222+
// aarch64-LABEL: @call_doublefloat
223+
// loongarch64-LABEL: @call_doublefloat
224+
// powerpc64-LABEL: @call_doublefloat
225+
#[no_mangle]
226+
pub unsafe fn call_doublefloat() {
227+
// aarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
228+
// loongarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:{ double, float }]], align [[ABI_ALIGN:8]]
229+
// powerpc64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
230+
231+
// aarch64: [[RUST_ALLOCA:%.+]] = alloca %DoubleFloat, align [[RUST_ALIGN:8]]
232+
// loongarch64: [[RUST_ALLOCA:%.+]] = alloca %DoubleFloat, align [[RUST_ALIGN:8]]
233+
// powerpc64: [[RUST_ALLOCA:%.+]] = alloca %DoubleFloat, align [[RUST_ALIGN:8]]
234+
235+
// aarch64: call void @llvm.memcpy.{{.+}}(ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], i64 16, i1 false)
236+
// loongarch64: call void @llvm.memcpy.{{.+}}(ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], i64 12, i1 false)
237+
// powerpc64: call void @llvm.memcpy.{{.+}}(ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], i64 16, i1 false)
238+
239+
// aarch64: [[ABI_VALUE:%.+]] = load [[ABI_TYPE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
240+
// loongarch64: [[ABI_VALUE:%.+]] = load [[ABI_TYPE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
241+
// powerpc64: [[ABI_VALUE:%.+]] = load [[ABI_TYPE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
242+
243+
// aarch64: call void @receives_doublefloat([[ABI_TYPE]] {{(inreg )?}}[[ABI_VALUE]])
244+
// loongarch64: call void @receives_doublefloat([[ABI_TYPE]] {{(inreg )?}}[[ABI_VALUE]])
245+
// powerpc64: call void @receives_doublefloat([[ABI_TYPE]] {{(inreg )?}}[[ABI_VALUE]])
246+
let x = DoubleFloat { f: 1., g: 2. };
247+
receives_doublefloat(x);
248+
}
249+
250+
// This test causes an ICE in sparc64 ABI code (https://github.com/rust-lang/rust/issues/122620)
251+
#[cfg(not(target_arch = "sparc64"))]
252+
// aarch64-LABEL: @return_doublefloat
253+
// loongarch64-LABEL: @return_doublefloat
254+
// powerpc64-LABEL: @return_doublefloat
255+
#[no_mangle]
256+
pub unsafe fn return_doublefloat() -> DoubleFloat {
257+
// powerpc returns this struct via sret pointer, it doesn't use the cast ABI.
258+
259+
// powerpc64: [[RETVAL:%.+]] = alloca %DoubleFloat, align 8
260+
// powerpc64: call void @returns_doublefloat(ptr {{.+}} [[RETVAL]])
261+
262+
263+
// The other targets copy the cast ABI type to an alloca.
264+
265+
// aarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:\[2 x i64\]]], align [[ABI_ALIGN:8]]
266+
// loongarch64: [[ABI_ALLOCA:%.+]] = alloca [[ABI_TYPE:{ double, float }]], align [[ABI_ALIGN:8]]
267+
268+
// aarch64: [[RUST_ALLOCA:%.+]] = alloca %DoubleFloat, align [[RUST_ALIGN:8]]
269+
// loongarch64: [[RUST_ALLOCA:%.+]] = alloca %DoubleFloat, align [[RUST_ALIGN:8]]
270+
271+
// aarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_doublefloat()
272+
// loongarch64: [[ABI_VALUE:%.+]] = call [[ABI_TYPE]] @returns_doublefloat()
273+
274+
// aarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
275+
// loongarch64: store [[ABI_TYPE]] [[ABI_VALUE]], ptr [[ABI_ALLOCA]], align [[ABI_ALIGN]]
276+
277+
// aarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 16, i1 false)
278+
// loongarch64: call void @llvm.memcpy.{{.+}}(ptr align [[RUST_ALIGN]] [[RUST_ALLOCA]], ptr align [[ABI_ALIGN]] [[ABI_ALLOCA]], i64 12, i1 false)
279+
returns_doublefloat()
280+
}
Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
1+
//@ revisions: linux apple
2+
//@ compile-flags: -C opt-level=0 -C no-prepopulate-passes
3+
4+
//@[linux] compile-flags: --target x86_64-unknown-linux-gnu
5+
//@[linux] needs-llvm-components: x86
6+
//@[apple] compile-flags: --target x86_64-apple-darwin
7+
//@[apple] needs-llvm-components: x86
8+
19
// Regression test for #29988
210

3-
//@ compile-flags: -C no-prepopulate-passes
4-
//@ only-x86_64
5-
//@ ignore-windows
11+
#![feature(no_core, lang_items)]
12+
#![crate_type = "lib"]
13+
#![no_std]
14+
#![no_core]
15+
16+
#[lang="sized"] trait Sized { }
17+
#[lang="freeze"] trait Freeze { }
18+
#[lang="copy"] trait Copy { }
619

720
#[repr(C)]
821
struct S {
@@ -15,11 +28,14 @@ extern "C" {
1528
fn foo(s: S);
1629
}
1730

18-
fn main() {
31+
// CHECK-LABEL: @test
32+
#[no_mangle]
33+
pub fn test() {
1934
let s = S { f1: 1, f2: 2, f3: 3 };
2035
unsafe {
21-
// CHECK: load { i64, i32 }, {{.*}}, align 4
22-
// CHECK: call void @foo({ i64, i32 } {{.*}})
36+
// CHECK: [[ALLOCA:%.+]] = alloca { i64, i32 }, align 8
37+
// CHECK: [[LOAD:%.+]] = load { i64, i32 }, ptr [[ALLOCA]], align 8
38+
// CHECK: call void @foo({ i64, i32 } [[LOAD]])
2339
foo(s);
2440
}
2541
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//@ run-pass
2+
#![allow(improper_ctypes)]
3+
4+
// Test a foreign function that accepts and returns a struct by value.
5+
6+
// FiveU16s in particular is interesting because it is larger than a single 64 bit or 32 bit
7+
// register, which are used as cast destinations on some targets, but does not evenly divide those
8+
// sizes, causing there to be padding in the last element.
9+
10+
#[derive(Copy, Clone, PartialEq, Debug)]
11+
pub struct FiveU16s {
12+
one: u16,
13+
two: u16,
14+
three: u16,
15+
four: u16,
16+
five: u16,
17+
}
18+
19+
#[link(name = "rust_test_helpers", kind = "static")]
20+
extern "C" {
21+
pub fn rust_dbg_extern_identity_FiveU16s(v: FiveU16s) -> FiveU16s;
22+
}
23+
24+
pub fn main() {
25+
unsafe {
26+
let x = FiveU16s { one: 22, two: 23, three: 24, four: 25, five: 26 };
27+
let y = rust_dbg_extern_identity_FiveU16s(x);
28+
assert_eq!(x, y);
29+
}
30+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//@ run-pass
2+
#![allow(improper_ctypes)]
3+
4+
pub struct FiveU16s {
5+
one: u16,
6+
two: u16,
7+
three: u16,
8+
four: u16,
9+
five: u16,
10+
}
11+
12+
#[link(name = "rust_test_helpers", kind = "static")]
13+
extern "C" {
14+
pub fn rust_dbg_extern_return_FiveU16s() -> FiveU16s;
15+
}
16+
17+
pub fn main() {
18+
unsafe {
19+
let y = rust_dbg_extern_return_FiveU16s();
20+
assert_eq!(y.one, 10);
21+
assert_eq!(y.two, 20);
22+
assert_eq!(y.three, 30);
23+
assert_eq!(y.four, 40);
24+
assert_eq!(y.five, 50);
25+
}
26+
}

0 commit comments

Comments
 (0)
This repository has been archived.