Skip to content

Commit 7cba46f

Browse files
committed
Fix passing/returning structs with the 64-bit SPARC ABI
1 parent 052114f commit 7cba46f

File tree

4 files changed

+309
-179
lines changed

4 files changed

+309
-179
lines changed

compiler/rustc_abi/src/callconv/reg.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ impl Reg {
3535

3636
reg_ctor!(f32, Float, 32);
3737
reg_ctor!(f64, Float, 64);
38+
reg_ctor!(f128, Float, 128);
3839
}
3940

4041
impl Reg {
Lines changed: 145 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -1,210 +1,185 @@
1-
// FIXME: This needs an audit for correctness and completeness.
2-
31
use rustc_abi::{
4-
BackendRepr, FieldsShape, Float, HasDataLayout, Primitive, Reg, Scalar, Size, TyAbiInterface,
5-
TyAndLayout,
2+
Align, BackendRepr, FieldsShape, Float, HasDataLayout, Primitive, Reg, Size, TyAbiInterface,
3+
TyAndLayout, Variants,
64
};
75

86
use crate::callconv::{ArgAbi, ArgAttribute, CastTarget, FnAbi, Uniform};
97
use crate::spec::HasTargetSpec;
108

11-
#[derive(Clone, Debug)]
12-
struct Sdata {
13-
pub prefix: [Option<Reg>; 8],
14-
pub prefix_index: usize,
15-
pub last_offset: Size,
16-
pub has_float: bool,
17-
pub arg_attribute: ArgAttribute,
9+
#[derive(Copy, Clone)]
10+
enum DoubleWord {
11+
F64,
12+
F128Start,
13+
F128End,
14+
Words([Word; 2]),
1815
}
1916

20-
fn arg_scalar<C>(cx: &C, scalar: &Scalar, offset: Size, mut data: Sdata) -> Sdata
21-
where
22-
C: HasDataLayout,
23-
{
24-
let dl = cx.data_layout();
25-
26-
if !matches!(scalar.primitive(), Primitive::Float(Float::F32 | Float::F64)) {
27-
return data;
28-
}
29-
30-
data.has_float = true;
31-
32-
if !data.last_offset.is_aligned(dl.f64_align.abi) && data.last_offset < offset {
33-
if data.prefix_index == data.prefix.len() {
34-
return data;
35-
}
36-
data.prefix[data.prefix_index] = Some(Reg::i32());
37-
data.prefix_index += 1;
38-
data.last_offset = data.last_offset + Reg::i32().size;
39-
}
40-
41-
for _ in 0..((offset - data.last_offset).bits() / 64)
42-
.min((data.prefix.len() - data.prefix_index) as u64)
43-
{
44-
data.prefix[data.prefix_index] = Some(Reg::i64());
45-
data.prefix_index += 1;
46-
data.last_offset = data.last_offset + Reg::i64().size;
47-
}
48-
49-
if data.last_offset < offset {
50-
if data.prefix_index == data.prefix.len() {
51-
return data;
52-
}
53-
data.prefix[data.prefix_index] = Some(Reg::i32());
54-
data.prefix_index += 1;
55-
data.last_offset = data.last_offset + Reg::i32().size;
56-
}
57-
58-
if data.prefix_index == data.prefix.len() {
59-
return data;
60-
}
61-
62-
if scalar.primitive() == Primitive::Float(Float::F32) {
63-
data.arg_attribute = ArgAttribute::InReg;
64-
data.prefix[data.prefix_index] = Some(Reg::f32());
65-
data.last_offset = offset + Reg::f32().size;
66-
} else {
67-
data.prefix[data.prefix_index] = Some(Reg::f64());
68-
data.last_offset = offset + Reg::f64().size;
69-
}
70-
data.prefix_index += 1;
71-
data
17+
#[derive(Copy, Clone)]
18+
enum Word {
19+
F32,
20+
Integer,
7221
}
7322

74-
fn arg_scalar_pair<C>(
23+
fn classify<'a, Ty, C>(
7524
cx: &C,
76-
scalar1: &Scalar,
77-
scalar2: &Scalar,
78-
mut offset: Size,
79-
mut data: Sdata,
80-
) -> Sdata
81-
where
82-
C: HasDataLayout,
83-
{
84-
data = arg_scalar(cx, scalar1, offset, data);
85-
match (scalar1.primitive(), scalar2.primitive()) {
86-
(Primitive::Float(Float::F32), _) => offset += Reg::f32().size,
87-
(_, Primitive::Float(Float::F64)) => offset += Reg::f64().size,
88-
(Primitive::Int(i, _signed), _) => offset += i.size(),
89-
(Primitive::Pointer(_), _) => offset += Reg::i64().size,
90-
_ => {}
91-
}
92-
93-
if !offset.bytes().is_multiple_of(4)
94-
&& matches!(scalar2.primitive(), Primitive::Float(Float::F32 | Float::F64))
95-
{
96-
offset += Size::from_bytes(4 - (offset.bytes() % 4));
97-
}
98-
data = arg_scalar(cx, scalar2, offset, data);
99-
data
100-
}
101-
102-
fn parse_structure<'a, Ty, C>(
103-
cx: &C,
104-
layout: TyAndLayout<'a, Ty>,
105-
mut data: Sdata,
106-
mut offset: Size,
107-
) -> Sdata
108-
where
25+
arg_layout: &TyAndLayout<'a, Ty>,
26+
offset: Size,
27+
double_words: &mut [DoubleWord; 4],
28+
) where
10929
Ty: TyAbiInterface<'a, C> + Copy,
11030
C: HasDataLayout,
11131
{
112-
if let FieldsShape::Union(_) = layout.fields {
113-
return data;
114-
}
115-
116-
match layout.backend_repr {
117-
BackendRepr::Scalar(scalar) => {
118-
data = arg_scalar(cx, &scalar, offset, data);
119-
}
120-
BackendRepr::Memory { .. } => {
121-
for i in 0..layout.fields.count() {
122-
if offset < layout.fields.offset(i) {
123-
offset = layout.fields.offset(i);
32+
match arg_layout.backend_repr {
33+
BackendRepr::Scalar(scalar) => match scalar.primitive() {
34+
Primitive::Float(float)
35+
if offset.is_aligned(float.align(cx).abi.min(Align::from_bytes(8).unwrap())) =>
36+
{
37+
let index = offset.bytes_usize() / 8;
38+
match float {
39+
Float::F128 => {
40+
double_words[index] = DoubleWord::F128Start;
41+
double_words[index + 1] = DoubleWord::F128End;
42+
}
43+
Float::F64 => {
44+
double_words[index] = DoubleWord::F64;
45+
}
46+
Float::F32 => {
47+
if let DoubleWord::Words(words) = &mut double_words[index] {
48+
words[(offset.bytes_usize() % 8) / 4] = Word::F32;
49+
} else {
50+
unreachable!();
51+
}
52+
}
53+
Float::F16 => {
54+
// Match LLVM by passing `f16` in integer registers.
55+
}
12456
}
125-
data = parse_structure(cx, layout.field(cx, i), data.clone(), offset);
12657
}
127-
}
128-
_ => {
129-
if let BackendRepr::ScalarPair(scalar1, scalar2) = &layout.backend_repr {
130-
data = arg_scalar_pair(cx, scalar1, scalar2, offset, data);
58+
_ => {}
59+
},
60+
BackendRepr::SimdVector { .. } => {}
61+
BackendRepr::ScalarPair(..) | BackendRepr::Memory { .. } => match arg_layout.fields {
62+
FieldsShape::Primitive => {
63+
unreachable!("aggregates can't have `FieldsShape::Primitive`")
13164
}
132-
}
65+
FieldsShape::Union(_) => {
66+
if !arg_layout.is_zst() {
67+
if arg_layout.is_transparent() {
68+
let non_1zst_elem = arg_layout.non_1zst_field(cx).expect("not exactly one non-1-ZST field in non-ZST repr(transparent) union").1;
69+
classify(cx, &non_1zst_elem, offset, double_words);
70+
}
71+
}
72+
}
73+
FieldsShape::Array { .. } => {}
74+
FieldsShape::Arbitrary { .. } => match arg_layout.variants {
75+
Variants::Multiple { .. } => {}
76+
Variants::Single { .. } | Variants::Empty => {
77+
// Match Clang by ignoring whether a struct is packed and just considering
78+
// whether individual fields are aligned. GCC currently uses only integer
79+
// registers when passing packed structs.
80+
for i in arg_layout.fields.index_by_increasing_offset() {
81+
classify(
82+
cx,
83+
&arg_layout.field(cx, i),
84+
offset + arg_layout.fields.offset(i),
85+
double_words,
86+
);
87+
}
88+
}
89+
},
90+
},
13391
}
134-
135-
data
13692
}
13793

138-
fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>, in_registers_max: Size)
139-
where
94+
fn classify_arg<'a, Ty, C>(
95+
cx: &C,
96+
arg: &mut ArgAbi<'a, Ty>,
97+
in_registers_max: Size,
98+
total_double_word_count: &mut usize,
99+
) where
140100
Ty: TyAbiInterface<'a, C> + Copy,
141101
C: HasDataLayout,
142102
{
103+
// 64-bit SPARC allocates argument stack space in 64-bit chunks (double words), some of which
104+
// are promoted to registers based on their position on the stack.
105+
106+
// Keep track of the total number of double words used by arguments so far. This allows padding
107+
// arguments to be inserted where necessary to ensure that 16-aligned arguments are passed in an
108+
// aligned set of registers.
109+
110+
let pad = !total_double_word_count.is_multiple_of(2) && arg.layout.align.abi.bytes() == 16;
111+
// The number of double words used by this argument.
112+
let double_word_count = arg.layout.size.bytes_usize().div_ceil(8);
113+
// The number of double words before this argument, including any padding.
114+
let start_double_word_count = *total_double_word_count + usize::from(pad);
143115
if !arg.layout.is_aggregate() {
144116
arg.extend_integer_width_to(64);
117+
*total_double_word_count = start_double_word_count + double_word_count;
145118
return;
146119
}
147120

148121
let total = arg.layout.size;
149122
if total > in_registers_max {
150123
arg.make_indirect();
124+
*total_double_word_count += 1;
151125
return;
152126
}
153127

154-
match arg.layout.fields {
155-
FieldsShape::Primitive => unreachable!(),
156-
FieldsShape::Array { .. } => {
157-
// Arrays are passed indirectly
158-
arg.make_indirect();
159-
return;
160-
}
161-
FieldsShape::Union(_) => {
162-
// Unions and are always treated as a series of 64-bit integer chunks
163-
}
164-
FieldsShape::Arbitrary { .. } => {
165-
// Structures with floating point numbers need special care.
128+
*total_double_word_count = start_double_word_count + double_word_count;
166129

167-
let mut data = parse_structure(
168-
cx,
169-
arg.layout,
170-
Sdata {
171-
prefix: [None; 8],
172-
prefix_index: 0,
173-
last_offset: Size::ZERO,
174-
has_float: false,
175-
arg_attribute: ArgAttribute::default(),
176-
},
177-
Size::ZERO,
178-
);
130+
let mut double_words = [DoubleWord::Words([Word::Integer; 2]); 4];
131+
classify(cx, &arg.layout, Size::ZERO, &mut double_words);
179132

180-
if data.has_float {
181-
// Structure { float, int, int } doesn't like to be handled like
182-
// { float, long int }. Other way around it doesn't mind.
183-
if data.last_offset < arg.layout.size
184-
&& !data.last_offset.bytes().is_multiple_of(8)
185-
&& data.prefix_index < data.prefix.len()
186-
{
187-
data.prefix[data.prefix_index] = Some(Reg::i32());
188-
data.prefix_index += 1;
189-
data.last_offset += Reg::i32().size;
190-
}
133+
let mut regs = [None; 8];
134+
let mut i = 0;
135+
let mut push = |reg| {
136+
regs[i] = Some(reg);
137+
i += 1;
138+
};
139+
let mut attrs = ArgAttribute::empty();
191140

192-
let mut rest_size = arg.layout.size - data.last_offset;
193-
if !rest_size.bytes().is_multiple_of(8) && data.prefix_index < data.prefix.len() {
194-
data.prefix[data.prefix_index] = Some(Reg::i32());
195-
rest_size = rest_size - Reg::i32().size;
141+
for (index, double_word) in double_words.into_iter().enumerate() {
142+
if arg.layout.size.bytes_usize() <= index * 8 {
143+
break;
144+
}
145+
match double_word {
146+
// `f128` must be aligned to be assigned a float register.
147+
DoubleWord::F128Start if (start_double_word_count + index).is_multiple_of(2) => {
148+
push(Reg::f128());
149+
}
150+
DoubleWord::F128Start => {
151+
// Clang currently handles this case nonsensically, always returning a packed
152+
// `struct { long double x; }` in an aligned quad floating-point register even when
153+
// the `long double` isn't aligned on the stack, which also makes all future
154+
// arguments get passed in the wrong registers. This passes the `f128` in integer
155+
// registers when it is unaligned, same as with `f32` and `f64`.
156+
push(Reg::i64());
157+
push(Reg::i64());
158+
}
159+
DoubleWord::F128End => {} // Already handled by `F128Start`
160+
DoubleWord::F64 => push(Reg::f64()),
161+
DoubleWord::Words([Word::Integer, Word::Integer]) => push(Reg::i64()),
162+
DoubleWord::Words(words) => {
163+
attrs |= ArgAttribute::InReg;
164+
for word in words {
165+
match word {
166+
Word::F32 => push(Reg::f32()),
167+
Word::Integer => push(Reg::i32()),
168+
}
196169
}
197-
198-
arg.cast_to(
199-
CastTarget::prefixed(data.prefix, Uniform::new(Reg::i64(), rest_size))
200-
.with_attrs(data.arg_attribute.into()),
201-
);
202-
return;
203170
}
204171
}
205172
}
206173

207-
arg.cast_to(Uniform::new(Reg::i64(), total));
174+
if let [Some(reg), None, ..] = regs {
175+
arg.cast_to_and_pad_i32(CastTarget::from(reg).with_attrs(attrs.into()), pad);
176+
} else {
177+
arg.cast_to_and_pad_i32(
178+
CastTarget::prefixed(regs, Uniform::new(Reg::i8(), Size::ZERO))
179+
.with_attrs(attrs.into()),
180+
pad,
181+
);
182+
}
208183
}
209184

210185
pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>)
@@ -213,9 +188,10 @@ where
213188
C: HasDataLayout + HasTargetSpec,
214189
{
215190
if !fn_abi.ret.is_ignore() {
216-
classify_arg(cx, &mut fn_abi.ret, Size::from_bytes(32));
191+
classify_arg(cx, &mut fn_abi.ret, Size::from_bytes(32), &mut 0);
217192
}
218193

194+
let mut double_word_count = 0;
219195
for arg in fn_abi.args.iter_mut() {
220196
if arg.is_ignore() {
221197
// sparc64-unknown-linux-{gnu,musl,uclibc} doesn't ignore ZSTs.
@@ -224,9 +200,10 @@ where
224200
&& arg.layout.is_zst()
225201
{
226202
arg.make_indirect_from_ignore();
203+
double_word_count += 1;
227204
}
228-
return;
205+
continue;
229206
}
230-
classify_arg(cx, arg, Size::from_bytes(16));
207+
classify_arg(cx, arg, Size::from_bytes(16), &mut double_word_count);
231208
}
232209
}

0 commit comments

Comments
 (0)