Skip to content

Commit 3b8d411

Browse files
authored
Rollup merge of #145709 - heiher:issue-145692-1, r=jackh726
Fix LoongArch C function ABI when passing/returning structs containing floats Similar to RISC-V, LoongArch passes structs containing only one or two floats (or a float–integer pair) in registers, as long as each element fits into a single corresponding register. Before this PR, Rust did not check the actual offset of the second float or integer; instead, it assumed the standard offset based on the default alignment. However, since the offset can be affected by `#[repr(align(N))]` and `#[repr(packed)]`, this led to miscompilations (see #145692). This PR fixes the issue by explicitly specifying the offset for the remainder of the cast.
2 parents 1a6cfac + b65a177 commit 3b8d411

File tree

4 files changed

+327
-40
lines changed

4 files changed

+327
-40
lines changed

compiler/rustc_target/src/callconv/loongarch.rs

Lines changed: 105 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ use crate::spec::HasTargetSpec;
88

99
#[derive(Copy, Clone)]
1010
enum RegPassKind {
11-
Float(Reg),
12-
Integer(Reg),
11+
Float { offset_from_start: Size, ty: Reg },
12+
Integer { offset_from_start: Size, ty: Reg },
1313
Unknown,
1414
}
1515

1616
#[derive(Copy, Clone)]
1717
enum FloatConv {
18-
FloatPair(Reg, Reg),
18+
FloatPair { first_ty: Reg, second_ty_offset_from_start: Size, second_ty: Reg },
1919
Float(Reg),
20-
MixedPair(Reg, Reg),
20+
MixedPair { first_ty: Reg, second_ty_offset_from_start: Size, second_ty: Reg },
2121
}
2222

2323
#[derive(Copy, Clone)]
@@ -37,6 +37,7 @@ fn should_use_fp_conv_helper<'a, Ty, C>(
3737
flen: u64,
3838
field1_kind: &mut RegPassKind,
3939
field2_kind: &mut RegPassKind,
40+
offset_from_start: Size,
4041
) -> Result<(), CannotUseFpConv>
4142
where
4243
Ty: TyAbiInterface<'a, C> + Copy,
@@ -49,16 +50,16 @@ where
4950
}
5051
match (*field1_kind, *field2_kind) {
5152
(RegPassKind::Unknown, _) => {
52-
*field1_kind = RegPassKind::Integer(Reg {
53-
kind: RegKind::Integer,
54-
size: arg_layout.size,
55-
});
53+
*field1_kind = RegPassKind::Integer {
54+
offset_from_start,
55+
ty: Reg { kind: RegKind::Integer, size: arg_layout.size },
56+
};
5657
}
57-
(RegPassKind::Float(_), RegPassKind::Unknown) => {
58-
*field2_kind = RegPassKind::Integer(Reg {
59-
kind: RegKind::Integer,
60-
size: arg_layout.size,
61-
});
58+
(RegPassKind::Float { .. }, RegPassKind::Unknown) => {
59+
*field2_kind = RegPassKind::Integer {
60+
offset_from_start,
61+
ty: Reg { kind: RegKind::Integer, size: arg_layout.size },
62+
};
6263
}
6364
_ => return Err(CannotUseFpConv),
6465
}
@@ -69,12 +70,16 @@ where
6970
}
7071
match (*field1_kind, *field2_kind) {
7172
(RegPassKind::Unknown, _) => {
72-
*field1_kind =
73-
RegPassKind::Float(Reg { kind: RegKind::Float, size: arg_layout.size });
73+
*field1_kind = RegPassKind::Float {
74+
offset_from_start,
75+
ty: Reg { kind: RegKind::Float, size: arg_layout.size },
76+
};
7477
}
7578
(_, RegPassKind::Unknown) => {
76-
*field2_kind =
77-
RegPassKind::Float(Reg { kind: RegKind::Float, size: arg_layout.size });
79+
*field2_kind = RegPassKind::Float {
80+
offset_from_start,
81+
ty: Reg { kind: RegKind::Float, size: arg_layout.size },
82+
};
7883
}
7984
_ => return Err(CannotUseFpConv),
8085
}
@@ -96,13 +101,14 @@ where
96101
flen,
97102
field1_kind,
98103
field2_kind,
104+
offset_from_start,
99105
);
100106
}
101107
return Err(CannotUseFpConv);
102108
}
103109
}
104110
FieldsShape::Array { count, .. } => {
105-
for _ in 0..count {
111+
for i in 0..count {
106112
let elem_layout = arg_layout.field(cx, 0);
107113
should_use_fp_conv_helper(
108114
cx,
@@ -111,6 +117,7 @@ where
111117
flen,
112118
field1_kind,
113119
field2_kind,
120+
offset_from_start + elem_layout.size * i,
114121
)?;
115122
}
116123
}
@@ -121,7 +128,15 @@ where
121128
}
122129
for i in arg_layout.fields.index_by_increasing_offset() {
123130
let field = arg_layout.field(cx, i);
124-
should_use_fp_conv_helper(cx, &field, xlen, flen, field1_kind, field2_kind)?;
131+
should_use_fp_conv_helper(
132+
cx,
133+
&field,
134+
xlen,
135+
flen,
136+
field1_kind,
137+
field2_kind,
138+
offset_from_start + arg_layout.fields.offset(i),
139+
)?;
125140
}
126141
}
127142
},
@@ -140,14 +155,52 @@ where
140155
{
141156
let mut field1_kind = RegPassKind::Unknown;
142157
let mut field2_kind = RegPassKind::Unknown;
143-
if should_use_fp_conv_helper(cx, arg, xlen, flen, &mut field1_kind, &mut field2_kind).is_err() {
158+
if should_use_fp_conv_helper(
159+
cx,
160+
arg,
161+
xlen,
162+
flen,
163+
&mut field1_kind,
164+
&mut field2_kind,
165+
Size::ZERO,
166+
)
167+
.is_err()
168+
{
144169
return None;
145170
}
146171
match (field1_kind, field2_kind) {
147-
(RegPassKind::Integer(l), RegPassKind::Float(r)) => Some(FloatConv::MixedPair(l, r)),
148-
(RegPassKind::Float(l), RegPassKind::Integer(r)) => Some(FloatConv::MixedPair(l, r)),
149-
(RegPassKind::Float(l), RegPassKind::Float(r)) => Some(FloatConv::FloatPair(l, r)),
150-
(RegPassKind::Float(f), RegPassKind::Unknown) => Some(FloatConv::Float(f)),
172+
(
173+
RegPassKind::Integer { offset_from_start, .. }
174+
| RegPassKind::Float { offset_from_start, .. },
175+
_,
176+
) if offset_from_start != Size::ZERO => {
177+
panic!("type {:?} has a first field with non-zero offset {offset_from_start:?}", arg.ty)
178+
}
179+
(
180+
RegPassKind::Integer { ty: first_ty, .. },
181+
RegPassKind::Float { offset_from_start, ty: second_ty },
182+
) => Some(FloatConv::MixedPair {
183+
first_ty,
184+
second_ty_offset_from_start: offset_from_start,
185+
second_ty,
186+
}),
187+
(
188+
RegPassKind::Float { ty: first_ty, .. },
189+
RegPassKind::Integer { offset_from_start, ty: second_ty },
190+
) => Some(FloatConv::MixedPair {
191+
first_ty,
192+
second_ty_offset_from_start: offset_from_start,
193+
second_ty,
194+
}),
195+
(
196+
RegPassKind::Float { ty: first_ty, .. },
197+
RegPassKind::Float { offset_from_start, ty: second_ty },
198+
) => Some(FloatConv::FloatPair {
199+
first_ty,
200+
second_ty_offset_from_start: offset_from_start,
201+
second_ty,
202+
}),
203+
(RegPassKind::Float { ty, .. }, RegPassKind::Unknown) => Some(FloatConv::Float(ty)),
151204
_ => None,
152205
}
153206
}
@@ -165,11 +218,19 @@ where
165218
FloatConv::Float(f) => {
166219
arg.cast_to(f);
167220
}
168-
FloatConv::FloatPair(l, r) => {
169-
arg.cast_to(CastTarget::pair(l, r));
221+
FloatConv::FloatPair { first_ty, second_ty_offset_from_start, second_ty } => {
222+
arg.cast_to(CastTarget::offset_pair(
223+
first_ty,
224+
second_ty_offset_from_start,
225+
second_ty,
226+
));
170227
}
171-
FloatConv::MixedPair(l, r) => {
172-
arg.cast_to(CastTarget::pair(l, r));
228+
FloatConv::MixedPair { first_ty, second_ty_offset_from_start, second_ty } => {
229+
arg.cast_to(CastTarget::offset_pair(
230+
first_ty,
231+
second_ty_offset_from_start,
232+
second_ty,
233+
));
173234
}
174235
}
175236
return false;
@@ -233,15 +294,27 @@ fn classify_arg<'a, Ty, C>(
233294
arg.cast_to(f);
234295
return;
235296
}
236-
Some(FloatConv::FloatPair(l, r)) if *avail_fprs >= 2 => {
297+
Some(FloatConv::FloatPair { first_ty, second_ty_offset_from_start, second_ty })
298+
if *avail_fprs >= 2 =>
299+
{
237300
*avail_fprs -= 2;
238-
arg.cast_to(CastTarget::pair(l, r));
301+
arg.cast_to(CastTarget::offset_pair(
302+
first_ty,
303+
second_ty_offset_from_start,
304+
second_ty,
305+
));
239306
return;
240307
}
241-
Some(FloatConv::MixedPair(l, r)) if *avail_fprs >= 1 && *avail_gprs >= 1 => {
308+
Some(FloatConv::MixedPair { first_ty, second_ty_offset_from_start, second_ty })
309+
if *avail_fprs >= 1 && *avail_gprs >= 1 =>
310+
{
242311
*avail_gprs -= 1;
243312
*avail_fprs -= 1;
244-
arg.cast_to(CastTarget::pair(l, r));
313+
arg.cast_to(CastTarget::offset_pair(
314+
first_ty,
315+
second_ty_offset_from_start,
316+
second_ty,
317+
));
245318
return;
246319
}
247320
_ => (),
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
//@ add-core-stubs
2+
//@ assembly-output: emit-asm
3+
//@ compile-flags: -Copt-level=3 --target loongarch64-unknown-linux-gnu
4+
//@ needs-llvm-components: loongarch
5+
6+
#![feature(no_core, lang_items)]
7+
#![no_std]
8+
#![no_core]
9+
#![crate_type = "lib"]
10+
11+
extern crate minicore;
12+
use minicore::*;
13+
14+
#[repr(C, align(64))]
15+
struct Aligned(f64);
16+
17+
#[repr(C)]
18+
struct Padded(u8, Aligned);
19+
20+
#[repr(C, packed)]
21+
struct Packed(u8, f32);
22+
23+
impl Copy for Aligned {}
24+
impl Copy for Padded {}
25+
impl Copy for Packed {}
26+
27+
extern "C" {
28+
fn take_padded(x: Padded);
29+
fn get_padded() -> Padded;
30+
fn take_packed(x: Packed);
31+
fn get_packed() -> Packed;
32+
}
33+
34+
// CHECK-LABEL: pass_padded
35+
#[unsafe(no_mangle)]
36+
extern "C" fn pass_padded(out: &mut Padded, x: Padded) {
37+
// CHECK: st.b $a1, $a0, 0
38+
// CHECK-NEXT: fst.d $fa0, $a0, 64
39+
// CHECK-NEXT: ret
40+
*out = x;
41+
}
42+
43+
// CHECK-LABEL: ret_padded
44+
#[unsafe(no_mangle)]
45+
extern "C" fn ret_padded(x: &Padded) -> Padded {
46+
// CHECK: fld.d $fa0, $a0, 64
47+
// CHECK-NEXT: ld.b $a0, $a0, 0
48+
// CHECK-NEXT: ret
49+
*x
50+
}
51+
52+
#[unsafe(no_mangle)]
53+
extern "C" fn call_padded(x: &Padded) {
54+
// CHECK: fld.d $fa0, $a0, 64
55+
// CHECK-NEXT: ld.b $a0, $a0, 0
56+
// CHECK-NEXT: pcaddu18i $t8, %call36(take_padded)
57+
// CHECK-NEXT: jr $t8
58+
unsafe {
59+
take_padded(*x);
60+
}
61+
}
62+
63+
#[unsafe(no_mangle)]
64+
extern "C" fn receive_padded(out: &mut Padded) {
65+
// CHECK: addi.d $sp, $sp, -16
66+
// CHECK-NEXT: .cfi_def_cfa_offset 16
67+
// CHECK-NEXT: st.d $ra, $sp, [[#%d,RA_SPILL:]]
68+
// CHECK-NEXT: st.d [[TEMP:.*]], $sp, [[#%d,TEMP_SPILL:]]
69+
// CHECK-NEXT: .cfi_offset 1, [[#%d,RA_SPILL - 16]]
70+
// CHECK-NEXT: .cfi_offset [[#%d,TEMP_NUM:]], [[#%d,TEMP_SPILL - 16]]
71+
// CHECK-NEXT: move [[TEMP]], $a0
72+
// CHECK-NEXT: pcaddu18i $ra, %call36(get_padded)
73+
// CHECK-NEXT: jirl $ra, $ra, 0
74+
// CHECK-NEXT: st.b $a0, [[TEMP]], 0
75+
// CHECK-NEXT: fst.d $fa0, [[TEMP]], 64
76+
// CHECK-NEXT: ld.d [[TEMP]], $sp, [[#%d,TEMP_SPILL]]
77+
// CHECK-NEXT: ld.d $ra, $sp, [[#%d,RA_SPILL]]
78+
// CHECK: addi.d $sp, $sp, 16
79+
// CHECK: ret
80+
unsafe {
81+
*out = get_padded();
82+
}
83+
}
84+
85+
// CHECK-LABEL: pass_packed
86+
#[unsafe(no_mangle)]
87+
extern "C" fn pass_packed(out: &mut Packed, x: Packed) {
88+
// CHECK: st.b $a1, $a0, 0
89+
// CHECK-NEXT: fst.s $fa0, $a0, 1
90+
// CHECK-NEXT: ret
91+
*out = x;
92+
}
93+
94+
// CHECK-LABEL: ret_packed
95+
#[unsafe(no_mangle)]
96+
extern "C" fn ret_packed(x: &Packed) -> Packed {
97+
// CHECK: fld.s $fa0, $a0, 1
98+
// CHECK-NEXT: ld.b $a0, $a0, 0
99+
// CHECK-NEXT: ret
100+
*x
101+
}
102+
103+
#[unsafe(no_mangle)]
104+
extern "C" fn call_packed(x: &Packed) {
105+
// CHECK: fld.s $fa0, $a0, 1
106+
// CHECK-NEXT: ld.b $a0, $a0, 0
107+
// CHECK-NEXT: pcaddu18i $t8, %call36(take_packed)
108+
// CHECK-NEXT: jr $t8
109+
unsafe {
110+
take_packed(*x);
111+
}
112+
}
113+
114+
#[unsafe(no_mangle)]
115+
extern "C" fn receive_packed(out: &mut Packed) {
116+
// CHECK: addi.d $sp, $sp, -16
117+
// CHECK-NEXT: .cfi_def_cfa_offset 16
118+
// CHECK-NEXT: st.d $ra, $sp, [[#%d,RA_SPILL:]]
119+
// CHECK-NEXT: st.d [[TEMP:.*]], $sp, [[#%d,TEMP_SPILL:]]
120+
// CHECK-NEXT: .cfi_offset 1, [[#%d,RA_SPILL - 16]]
121+
// CHECK-NEXT: .cfi_offset [[#%d,TEMP_NUM:]], [[#%d,TEMP_SPILL - 16]]
122+
// CHECK-NEXT: move [[TEMP]], $a0
123+
// CHECK-NEXT: pcaddu18i $ra, %call36(get_packed)
124+
// CHECK-NEXT: jirl $ra, $ra, 0
125+
// CHECK-NEXT: st.b $a0, [[TEMP]], 0
126+
// CHECK-NEXT: fst.s $fa0, [[TEMP]], 1
127+
// CHECK-NEXT: ld.d [[TEMP]], $sp, [[#%d,TEMP_SPILL]]
128+
// CHECK-NEXT: ld.d $ra, $sp, [[#%d,RA_SPILL]]
129+
// CHECK: addi.d $sp, $sp, 16
130+
// CHECK: ret
131+
unsafe {
132+
*out = get_packed();
133+
}
134+
}

0 commit comments

Comments
 (0)