diff --git a/compiler/rustc_target/src/callconv/mips64.rs b/compiler/rustc_target/src/callconv/mips64.rs index f17e59c12e57e..a9d5ec958889f 100644 --- a/compiler/rustc_target/src/callconv/mips64.rs +++ b/compiler/rustc_target/src/callconv/mips64.rs @@ -3,7 +3,7 @@ use rustc_abi::{ BackendRepr, FieldsShape, Float, HasDataLayout, Primitive, Reg, Size, TyAbiInterface, }; -use crate::callconv::{ArgAbi, ArgExtension, CastTarget, FnAbi, PassMode, Uniform}; +use crate::callconv::{ArgAbi, ArgAttribute, ArgExtension, CastTarget, FnAbi, PassMode, Uniform}; fn extend_integer_width_mips(arg: &mut ArgAbi<'_, Ty>, bits: u64) { // Always sign extend u32 values on 64-bit mips @@ -27,8 +27,15 @@ where { match ret.layout.field(cx, i).backend_repr { BackendRepr::Scalar(scalar) => match scalar.primitive() { - Primitive::Float(Float::F32) => Some(Reg::f32()), - Primitive::Float(Float::F64) => Some(Reg::f64()), + Primitive::Float(float) => { + match float { + // C does not have the f16 type + Float::F16 => None, + Float::F32 => Some(Reg::f32()), + Float::F64 => Some(Reg::f64()), + Float::F128 => Some(Reg::f128()), + } + } _ => None, }, _ => None, @@ -55,14 +62,17 @@ where if let FieldsShape::Arbitrary { .. } = ret.layout.fields { if ret.layout.fields.count() == 1 { if let Some(reg) = float_reg(cx, ret, 0) { - ret.cast_to(reg); + // The inreg attribute forces LLVM to return a struct containing a f128 in + // $f0 and $f1 rather than $f0 and $f2, see: + // https://github.com/llvm/llvm-project/blob/a81db64570f94c2ca8ac0f598c0b5bba1a7ae59e/llvm/lib/Target/Mips/MipsCallingConv.td#L48-L51 + ret.cast_to_with_attrs(reg, ArgAttribute::InReg.into()); return; } } else if ret.layout.fields.count() == 2 && let Some(reg0) = float_reg(cx, ret, 0) && let Some(reg1) = float_reg(cx, ret, 1) { - ret.cast_to(CastTarget::pair(reg0, reg1)); + ret.cast_to_with_attrs(CastTarget::pair(reg0, reg1), ArgAttribute::InReg.into()); return; } } diff --git a/tests/assembly-llvm/mips-struct-float.rs b/tests/assembly-llvm/mips-struct-float.rs new file mode 100644 index 0000000000000..92f10267e01d2 --- /dev/null +++ b/tests/assembly-llvm/mips-struct-float.rs @@ -0,0 +1,151 @@ +// Tests that MIPS targets return structs that are up to 128 bits large, only +// consist of 1 or 2 floating point fields and have a first field with offset 0 +// in FPRs rather than GPRs. +// +//@ add-minicore +//@ assembly-output: emit-asm +//@ compile-flags: -Copt-level=3 +// +//@ revisions: LE +//@[LE] compile-flags: --target=mips64el-unknown-linux-gnuabi64 +//@[LE] needs-llvm-components: mips +//@ revisions: BE +//@[BE] compile-flags: --target=mips64-unknown-linux-gnuabi64 +//@[BE] needs-llvm-components: mips + +#![crate_type = "lib"] +#![feature(no_core, f128)] +#![no_core] + +extern crate minicore; + +// 128-bit large struct with one floating point field, should be returned in +// $f0 and $f1 to match the de-facto GCC ABI. +#[repr(C)] +struct SingleF128 { + x: f128, +} + +// 64-bit large struct with one floating point field, should be returned in $f0. +#[repr(C)] +struct SingleF64 { + x: f64, +} + +// 32-bit large struct with one floating point field, should be returned in $f0. +#[repr(C)] +struct SingleF32 { + x: f32, +} + +// 128-bit large struct with two floating point fields, should be returned in +// $f0 and $f2. +#[repr(C)] +struct TwoF64 { + x: f64, + y: f64, +} + +// 64-bit large struct with two floating point fields, should be returned in +// $f0 and $f2. +#[repr(C)] +struct TwoF32 { + x: f32, + y: f32, +} + +// 96-bit large struct with two floating point fields, should be returned in +// $f0 and $f2. +#[repr(C)] +struct F32AndF64 { + x: f32, + y: f64, +} + +// 96-bit large struct with two floating-point fields, should be returned in +// $f0 and $f2. +#[repr(C)] +struct F64AndF32 { + x: f64, + y: f32, +} + +// CHECK-LABEL: single_f128 +// CHECK: dmtc1 $4, $f0 +// CHECK-NEXT: jr $ra +// CHECK-NEXT: dmtc1 $5, $f1 +#[unsafe(no_mangle)] +pub extern "C" fn single_f128(a: SingleF128) -> SingleF128 { + a +} + +// CHECK-LABEL: single_f64 +// CHECK: jr $ra +// CHECK-NEXT: mov.d $f0, $f12 +#[unsafe(no_mangle)] +pub extern "C" fn single_f64(a: SingleF64) -> SingleF64 { + a +} + +// CHECK-LABEL: single_f32 +// BE: dsrl $1, $4, 32 +// LE: sll $1, $4, 0 +// BE-NEXT: sll $1, $1, 0 +// CHECK-NEXT: jr $ra +// CHECK-NEXT: mtc1 $1, $f0 +#[unsafe(no_mangle)] +pub extern "C" fn single_f32(a: SingleF32) -> SingleF32 { + a +} + +// CHECK-LABEL: two_f64 +// CHECK: mov.d $f2, $f13 +// CHECK-NEXT: jr $ra +// CHECK-NEXT: mov.d $f0, $f12 +#[unsafe(no_mangle)] +pub extern "C" fn two_f64(a: TwoF64) -> TwoF64 { + a +} + +// CHECK-LABEL: two_f32 +// CHECK: sll $1, $4, 0 +// LE-NEXT: mtc1 $1, $f0 +// BE-NEXT: mtc1 $1, $f2 +// CHECK-NEXT: dsrl $1, $4, 32 +// CHECK-NEXT: sll $1, $1, 0 +// CHECK-NEXT: jr $ra +// LE-NEXT: mtc1 $1, $f2 +// BE-NEXT: mtc1 $1, $f0 +#[unsafe(no_mangle)] +pub extern "C" fn two_f32(a: TwoF32) -> TwoF32 { + a +} + +// We deviate from Clang in the order of instructions in the next two functions +// but that's okay + +// CHECK-LABEL: f32_and_f64 +// BE: dsrl $1, $4, 32 +// LE: mov.d $f2, $f13 +// BE-NEXT: mov.d $f2, $f13 +// LE-NEXT: sll $1, $4, 0 +// BE-NEXT: sll $1, $1, 0 +// CHECK-NEXT: jr $ra +// CHECK-NEXT: mtc1 $1, $f0 +#[unsafe(no_mangle)] +pub extern "C" fn f32_and_f64(a: F32AndF64) -> F32AndF64 { + a +} + +// CHECK-LABEL: f64_and_f32 +// BE: dsrl $1, $5, 32 +// LE: mov.d $f0, $f12 +// BE-NEXT: mov.d $f0, $f12 +// LE-NEXT: sll $1, $5, 0 +// BE-NEXT: sll $1, $1, 0 +// CHECK-NEXT: jr $ra +// CHECK-NEXT: mtc1 $1, $f2 +#[unsafe(no_mangle)] +pub extern "C" fn f64_and_f32(a: F64AndF32) -> F64AndF32 { + a +}