rustc_target/callconv/
loongarch.rs

1use rustc_abi::{
2    BackendRepr, FieldsShape, HasDataLayout, Primitive, Reg, RegKind, Size, TyAbiInterface,
3    TyAndLayout, Variants,
4};
5
6use crate::callconv::{ArgAbi, ArgExtension, CastTarget, FnAbi, PassMode, Uniform};
7use crate::spec::HasTargetSpec;
8
9#[derive(Copy, Clone)]
10enum RegPassKind {
11    Float { offset_from_start: Size, ty: Reg },
12    Integer { offset_from_start: Size, ty: Reg },
13    Unknown,
14}
15
16#[derive(Copy, Clone)]
17enum FloatConv {
18    FloatPair { first_ty: Reg, second_ty_offset_from_start: Size, second_ty: Reg },
19    Float(Reg),
20    MixedPair { first_ty: Reg, second_ty_offset_from_start: Size, second_ty: Reg },
21}
22
23#[derive(Copy, Clone)]
24struct CannotUseFpConv;
25
26fn is_loongarch_aggregate<Ty>(arg: &ArgAbi<'_, Ty>) -> bool {
27    match arg.layout.backend_repr {
28        BackendRepr::SimdVector { .. } => true,
29        _ => arg.layout.is_aggregate(),
30    }
31}
32
33fn should_use_fp_conv_helper<'a, Ty, C>(
34    cx: &C,
35    arg_layout: &TyAndLayout<'a, Ty>,
36    xlen: u64,
37    flen: u64,
38    field1_kind: &mut RegPassKind,
39    field2_kind: &mut RegPassKind,
40    offset_from_start: Size,
41) -> Result<(), CannotUseFpConv>
42where
43    Ty: TyAbiInterface<'a, C> + Copy,
44{
45    match arg_layout.backend_repr {
46        BackendRepr::Scalar(scalar) => match scalar.primitive() {
47            Primitive::Int(..) | Primitive::Pointer(_) => {
48                if arg_layout.size.bits() > xlen {
49                    return Err(CannotUseFpConv);
50                }
51                match (*field1_kind, *field2_kind) {
52                    (RegPassKind::Unknown, _) => {
53                        *field1_kind = RegPassKind::Integer {
54                            offset_from_start,
55                            ty: Reg { kind: RegKind::Integer, size: arg_layout.size },
56                        };
57                    }
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                        };
63                    }
64                    _ => return Err(CannotUseFpConv),
65                }
66            }
67            Primitive::Float(_) => {
68                if arg_layout.size.bits() > flen {
69                    return Err(CannotUseFpConv);
70                }
71                match (*field1_kind, *field2_kind) {
72                    (RegPassKind::Unknown, _) => {
73                        *field1_kind = RegPassKind::Float {
74                            offset_from_start,
75                            ty: Reg { kind: RegKind::Float, size: arg_layout.size },
76                        };
77                    }
78                    (_, RegPassKind::Unknown) => {
79                        *field2_kind = RegPassKind::Float {
80                            offset_from_start,
81                            ty: Reg { kind: RegKind::Float, size: arg_layout.size },
82                        };
83                    }
84                    _ => return Err(CannotUseFpConv),
85                }
86            }
87        },
88        BackendRepr::SimdVector { .. } => return Err(CannotUseFpConv),
89        BackendRepr::ScalarPair(..) | BackendRepr::Memory { .. } => match arg_layout.fields {
90            FieldsShape::Primitive => {
91                unreachable!("aggregates can't have `FieldsShape::Primitive`")
92            }
93            FieldsShape::Union(_) => {
94                if !arg_layout.is_zst() {
95                    if arg_layout.is_transparent() {
96                        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;
97                        return should_use_fp_conv_helper(
98                            cx,
99                            &non_1zst_elem,
100                            xlen,
101                            flen,
102                            field1_kind,
103                            field2_kind,
104                            offset_from_start,
105                        );
106                    }
107                    return Err(CannotUseFpConv);
108                }
109            }
110            FieldsShape::Array { count, .. } => {
111                for i in 0..count {
112                    let elem_layout = arg_layout.field(cx, 0);
113                    should_use_fp_conv_helper(
114                        cx,
115                        &elem_layout,
116                        xlen,
117                        flen,
118                        field1_kind,
119                        field2_kind,
120                        offset_from_start + elem_layout.size * i,
121                    )?;
122                }
123            }
124            FieldsShape::Arbitrary { .. } => {
125                match arg_layout.variants {
126                    Variants::Multiple { .. } => return Err(CannotUseFpConv),
127                    Variants::Single { .. } | Variants::Empty => (),
128                }
129                for i in arg_layout.fields.index_by_increasing_offset() {
130                    let field = arg_layout.field(cx, i);
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                    )?;
140                }
141            }
142        },
143    }
144    Ok(())
145}
146
147fn should_use_fp_conv<'a, Ty, C>(
148    cx: &C,
149    arg: &TyAndLayout<'a, Ty>,
150    xlen: u64,
151    flen: u64,
152) -> Option<FloatConv>
153where
154    Ty: TyAbiInterface<'a, C> + Copy,
155{
156    let mut field1_kind = RegPassKind::Unknown;
157    let mut field2_kind = RegPassKind::Unknown;
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    {
169        return None;
170    }
171    match (field1_kind, field2_kind) {
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)),
204        _ => None,
205    }
206}
207
208fn classify_ret<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>, xlen: u64, flen: u64) -> bool
209where
210    Ty: TyAbiInterface<'a, C> + Copy,
211{
212    if !arg.layout.is_sized() {
213        // Not touching this...
214        return false; // I guess? return value of this function is not documented
215    }
216    if let Some(conv) = should_use_fp_conv(cx, &arg.layout, xlen, flen) {
217        match conv {
218            FloatConv::Float(f) => {
219                arg.cast_to(f);
220            }
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                ));
227            }
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                ));
234            }
235        }
236        return false;
237    }
238
239    let total = arg.layout.size;
240
241    // "Scalars wider than 2✕XLEN are passed by reference and are replaced in
242    // the argument list with the address."
243    // "Aggregates larger than 2✕XLEN bits are passed by reference and are
244    // replaced in the argument list with the address, as are C++ aggregates
245    // with nontrivial copy constructors, destructors, or vtables."
246    if total.bits() > 2 * xlen {
247        // We rely on the LLVM backend lowering code to lower passing a scalar larger than 2*XLEN.
248        if is_loongarch_aggregate(arg) {
249            arg.make_indirect();
250        }
251        return true;
252    }
253
254    let xlen_reg = match xlen {
255        32 => Reg::i32(),
256        64 => Reg::i64(),
257        _ => unreachable!("Unsupported XLEN: {}", xlen),
258    };
259    if is_loongarch_aggregate(arg) {
260        if total.bits() <= xlen {
261            arg.cast_to(xlen_reg);
262        } else {
263            arg.cast_to(Uniform::new(xlen_reg, Size::from_bits(xlen * 2)));
264        }
265        return false;
266    }
267
268    // "When passed in registers, scalars narrower than XLEN bits are widened
269    // according to the sign of their type up to 32 bits, then sign-extended to
270    // XLEN bits."
271    extend_integer_width(arg, xlen);
272    false
273}
274
275fn classify_arg<'a, Ty, C>(
276    cx: &C,
277    arg: &mut ArgAbi<'a, Ty>,
278    xlen: u64,
279    flen: u64,
280    is_vararg: bool,
281    avail_gprs: &mut u64,
282    avail_fprs: &mut u64,
283) where
284    Ty: TyAbiInterface<'a, C> + Copy,
285{
286    if !arg.layout.is_sized() {
287        // Not touching this...
288        return;
289    }
290    if !is_vararg {
291        match should_use_fp_conv(cx, &arg.layout, xlen, flen) {
292            Some(FloatConv::Float(f)) if *avail_fprs >= 1 => {
293                *avail_fprs -= 1;
294                arg.cast_to(f);
295                return;
296            }
297            Some(FloatConv::FloatPair { first_ty, second_ty_offset_from_start, second_ty })
298                if *avail_fprs >= 2 =>
299            {
300                *avail_fprs -= 2;
301                arg.cast_to(CastTarget::offset_pair(
302                    first_ty,
303                    second_ty_offset_from_start,
304                    second_ty,
305                ));
306                return;
307            }
308            Some(FloatConv::MixedPair { first_ty, second_ty_offset_from_start, second_ty })
309                if *avail_fprs >= 1 && *avail_gprs >= 1 =>
310            {
311                *avail_gprs -= 1;
312                *avail_fprs -= 1;
313                arg.cast_to(CastTarget::offset_pair(
314                    first_ty,
315                    second_ty_offset_from_start,
316                    second_ty,
317                ));
318                return;
319            }
320            _ => (),
321        }
322    }
323
324    let total = arg.layout.size;
325    let align = arg.layout.align.abi.bits();
326
327    // "Scalars wider than 2✕XLEN are passed by reference and are replaced in
328    // the argument list with the address."
329    // "Aggregates larger than 2✕XLEN bits are passed by reference and are
330    // replaced in the argument list with the address, as are C++ aggregates
331    // with nontrivial copy constructors, destructors, or vtables."
332    if total.bits() > 2 * xlen {
333        // We rely on the LLVM backend lowering code to lower passing a scalar larger than 2*XLEN.
334        if is_loongarch_aggregate(arg) {
335            arg.make_indirect();
336        }
337        if *avail_gprs >= 1 {
338            *avail_gprs -= 1;
339        }
340        return;
341    }
342
343    let double_xlen_reg = match xlen {
344        32 => Reg::i64(),
345        64 => Reg::i128(),
346        _ => unreachable!("Unsupported XLEN: {}", xlen),
347    };
348
349    let xlen_reg = match xlen {
350        32 => Reg::i32(),
351        64 => Reg::i64(),
352        _ => unreachable!("Unsupported XLEN: {}", xlen),
353    };
354
355    if total.bits() > xlen {
356        let align_regs = align > xlen;
357        if is_loongarch_aggregate(arg) {
358            arg.cast_to(Uniform::new(
359                if align_regs { double_xlen_reg } else { xlen_reg },
360                Size::from_bits(xlen * 2),
361            ));
362        }
363        if align_regs && is_vararg {
364            *avail_gprs -= *avail_gprs % 2;
365        }
366        if *avail_gprs >= 2 {
367            *avail_gprs -= 2;
368        } else {
369            *avail_gprs = 0;
370        }
371        return;
372    } else if is_loongarch_aggregate(arg) {
373        arg.cast_to(xlen_reg);
374        if *avail_gprs >= 1 {
375            *avail_gprs -= 1;
376        }
377        return;
378    }
379
380    // "When passed in registers, scalars narrower than XLEN bits are widened
381    // according to the sign of their type up to 32 bits, then sign-extended to
382    // XLEN bits."
383    if *avail_gprs >= 1 {
384        extend_integer_width(arg, xlen);
385        *avail_gprs -= 1;
386    }
387}
388
389fn extend_integer_width<Ty>(arg: &mut ArgAbi<'_, Ty>, xlen: u64) {
390    if let BackendRepr::Scalar(scalar) = arg.layout.backend_repr
391        && let Primitive::Int(i, _) = scalar.primitive()
392        && i.size().bits() == 32
393        && xlen > 32
394        && let PassMode::Direct(ref mut attrs) = arg.mode
395    {
396        // 32-bit integers are always sign-extended
397        attrs.ext(ArgExtension::Sext);
398        return;
399    }
400
401    arg.extend_integer_width_to(xlen);
402}
403
404pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>)
405where
406    Ty: TyAbiInterface<'a, C> + Copy,
407    C: HasDataLayout + HasTargetSpec,
408{
409    let xlen = cx.data_layout().pointer_size().bits();
410    let flen = match &cx.target_spec().llvm_abiname[..] {
411        "ilp32f" | "lp64f" => 32,
412        "ilp32d" | "lp64d" => 64,
413        _ => 0,
414    };
415
416    let mut avail_gprs = 8;
417    let mut avail_fprs = 8;
418
419    if !fn_abi.ret.is_ignore() && classify_ret(cx, &mut fn_abi.ret, xlen, flen) {
420        avail_gprs -= 1;
421    }
422
423    for (i, arg) in fn_abi.args.iter_mut().enumerate() {
424        if arg.is_ignore() {
425            continue;
426        }
427        classify_arg(
428            cx,
429            arg,
430            xlen,
431            flen,
432            i >= fn_abi.fixed_count as usize,
433            &mut avail_gprs,
434            &mut avail_fprs,
435        );
436    }
437}
438
439pub(crate) fn compute_rust_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>)
440where
441    Ty: TyAbiInterface<'a, C> + Copy,
442    C: HasDataLayout + HasTargetSpec,
443{
444    let grlen = cx.data_layout().pointer_size().bits();
445
446    for arg in fn_abi.args.iter_mut() {
447        if arg.is_ignore() {
448            continue;
449        }
450
451        // LLVM integers types do not differentiate between signed or unsigned integers.
452        // Some LoongArch instructions do not have a `.w` suffix version, they use all the
453        // GRLEN bits. By explicitly setting the `signext` or `zeroext` attribute
454        // according to signedness to avoid unnecessary integer extending instructions.
455        //
456        // This is similar to the RISC-V case, see
457        // https://github.com/rust-lang/rust/issues/114508 for details.
458        extend_integer_width(arg, grlen);
459    }
460}