| // Reference: RISC-V ELF psABI specification | |
| // https://github.com/riscv/riscv-elf-psabi-doc | |
| // | |
| // Reference: Clang RISC-V ELF psABI lowering code | |
| // https://github.com/llvm/llvm-project/blob/8e780252a7284be45cf1ba224cabd884847e8e92/clang/lib/CodeGen/TargetInfo.cpp#L9311-L9773 | |
| use rustc_abi::{ | |
| BackendRepr, FieldsShape, HasDataLayout, Primitive, Reg, RegKind, Size, TyAbiInterface, | |
| TyAndLayout, Variants, | |
| }; | |
| use crate::callconv::{ArgAbi, ArgExtension, CastTarget, FnAbi, PassMode, Uniform}; | |
| use crate::spec::{HasTargetSpec, LlvmAbi}; | |
| #[derive(Copy, Clone)] | |
| enum RegPassKind { | |
| Float { offset_from_start: Size, ty: Reg }, | |
| Integer { offset_from_start: Size, ty: Reg }, | |
| Unknown, | |
| } | |
| #[derive(Copy, Clone)] | |
| enum FloatConv { | |
| FloatPair { first_ty: Reg, second_ty_offset_from_start: Size, second_ty: Reg }, | |
| Float(Reg), | |
| MixedPair { first_ty: Reg, second_ty_offset_from_start: Size, second_ty: Reg }, | |
| } | |
| #[derive(Copy, Clone)] | |
| struct CannotUseFpConv; | |
| fn is_riscv_aggregate<Ty>(arg: &ArgAbi<'_, Ty>) -> bool { | |
| match arg.layout.backend_repr { | |
| BackendRepr::SimdVector { .. } => true, | |
| _ => arg.layout.is_aggregate(), | |
| } | |
| } | |
| fn should_use_fp_conv_helper<'a, Ty, C>( | |
| cx: &C, | |
| arg_layout: &TyAndLayout<'a, Ty>, | |
| xlen: u64, | |
| flen: u64, | |
| field1_kind: &mut RegPassKind, | |
| field2_kind: &mut RegPassKind, | |
| offset_from_start: Size, | |
| ) -> Result<(), CannotUseFpConv> | |
| where | |
| Ty: TyAbiInterface<'a, C> + Copy, | |
| { | |
| match arg_layout.backend_repr { | |
| BackendRepr::Scalar(scalar) => match scalar.primitive() { | |
| Primitive::Int(..) | Primitive::Pointer(_) => { | |
| if arg_layout.size.bits() > xlen { | |
| return Err(CannotUseFpConv); | |
| } | |
| match (*field1_kind, *field2_kind) { | |
| (RegPassKind::Unknown, _) => { | |
| *field1_kind = RegPassKind::Integer { | |
| offset_from_start, | |
| ty: Reg { kind: RegKind::Integer, size: arg_layout.size }, | |
| }; | |
| } | |
| (RegPassKind::Float { .. }, RegPassKind::Unknown) => { | |
| *field2_kind = RegPassKind::Integer { | |
| offset_from_start, | |
| ty: Reg { kind: RegKind::Integer, size: arg_layout.size }, | |
| }; | |
| } | |
| _ => return Err(CannotUseFpConv), | |
| } | |
| } | |
| Primitive::Float(_) => { | |
| if arg_layout.size.bits() > flen { | |
| return Err(CannotUseFpConv); | |
| } | |
| match (*field1_kind, *field2_kind) { | |
| (RegPassKind::Unknown, _) => { | |
| *field1_kind = RegPassKind::Float { | |
| offset_from_start, | |
| ty: Reg { kind: RegKind::Float, size: arg_layout.size }, | |
| }; | |
| } | |
| (_, RegPassKind::Unknown) => { | |
| *field2_kind = RegPassKind::Float { | |
| offset_from_start, | |
| ty: Reg { kind: RegKind::Float, size: arg_layout.size }, | |
| }; | |
| } | |
| _ => return Err(CannotUseFpConv), | |
| } | |
| } | |
| }, | |
| BackendRepr::SimdVector { .. } | BackendRepr::SimdScalableVector { .. } => { | |
| return Err(CannotUseFpConv); | |
| } | |
| BackendRepr::ScalarPair(..) | BackendRepr::Memory { .. } => match arg_layout.fields { | |
| FieldsShape::Primitive => { | |
| unreachable!("aggregates can't have `FieldsShape::Primitive`") | |
| } | |
| FieldsShape::Union(_) => { | |
| if !arg_layout.is_zst() { | |
| if arg_layout.is_transparent() { | |
| 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; | |
| return should_use_fp_conv_helper( | |
| cx, | |
| &non_1zst_elem, | |
| xlen, | |
| flen, | |
| field1_kind, | |
| field2_kind, | |
| offset_from_start, | |
| ); | |
| } | |
| return Err(CannotUseFpConv); | |
| } | |
| } | |
| FieldsShape::Array { count, .. } => { | |
| for i in 0..count { | |
| let elem_layout = arg_layout.field(cx, 0); | |
| should_use_fp_conv_helper( | |
| cx, | |
| &elem_layout, | |
| xlen, | |
| flen, | |
| field1_kind, | |
| field2_kind, | |
| offset_from_start + elem_layout.size * i, | |
| )?; | |
| } | |
| } | |
| FieldsShape::Arbitrary { .. } => { | |
| match arg_layout.variants { | |
| Variants::Multiple { .. } => return Err(CannotUseFpConv), | |
| Variants::Single { .. } | Variants::Empty => (), | |
| } | |
| for i in arg_layout.fields.index_by_increasing_offset() { | |
| let field = arg_layout.field(cx, i); | |
| should_use_fp_conv_helper( | |
| cx, | |
| &field, | |
| xlen, | |
| flen, | |
| field1_kind, | |
| field2_kind, | |
| offset_from_start + arg_layout.fields.offset(i), | |
| )?; | |
| } | |
| } | |
| }, | |
| } | |
| Ok(()) | |
| } | |
| fn should_use_fp_conv<'a, Ty, C>( | |
| cx: &C, | |
| arg: &TyAndLayout<'a, Ty>, | |
| xlen: u64, | |
| flen: u64, | |
| ) -> Option<FloatConv> | |
| where | |
| Ty: TyAbiInterface<'a, C> + Copy, | |
| { | |
| let mut field1_kind = RegPassKind::Unknown; | |
| let mut field2_kind = RegPassKind::Unknown; | |
| if should_use_fp_conv_helper( | |
| cx, | |
| arg, | |
| xlen, | |
| flen, | |
| &mut field1_kind, | |
| &mut field2_kind, | |
| Size::ZERO, | |
| ) | |
| .is_err() | |
| { | |
| return None; | |
| } | |
| match (field1_kind, field2_kind) { | |
| ( | |
| RegPassKind::Integer { offset_from_start, .. } | |
| | RegPassKind::Float { offset_from_start, .. }, | |
| _, | |
| ) if offset_from_start != Size::ZERO => { | |
| panic!("type {:?} has a first field with non-zero offset {offset_from_start:?}", arg.ty) | |
| } | |
| ( | |
| RegPassKind::Integer { ty: first_ty, .. }, | |
| RegPassKind::Float { offset_from_start, ty: second_ty }, | |
| ) => Some(FloatConv::MixedPair { | |
| first_ty, | |
| second_ty_offset_from_start: offset_from_start, | |
| second_ty, | |
| }), | |
| ( | |
| RegPassKind::Float { ty: first_ty, .. }, | |
| RegPassKind::Integer { offset_from_start, ty: second_ty }, | |
| ) => Some(FloatConv::MixedPair { | |
| first_ty, | |
| second_ty_offset_from_start: offset_from_start, | |
| second_ty, | |
| }), | |
| ( | |
| RegPassKind::Float { ty: first_ty, .. }, | |
| RegPassKind::Float { offset_from_start, ty: second_ty }, | |
| ) => Some(FloatConv::FloatPair { | |
| first_ty, | |
| second_ty_offset_from_start: offset_from_start, | |
| second_ty, | |
| }), | |
| (RegPassKind::Float { ty, .. }, RegPassKind::Unknown) => Some(FloatConv::Float(ty)), | |
| _ => None, | |
| } | |
| } | |
| fn classify_ret<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>, xlen: u64, flen: u64) -> bool | |
| where | |
| Ty: TyAbiInterface<'a, C> + Copy, | |
| { | |
| if !arg.layout.is_sized() { | |
| // Not touching this... | |
| return false; // I guess? return value of this function is not documented | |
| } | |
| if let Some(conv) = should_use_fp_conv(cx, &arg.layout, xlen, flen) { | |
| match conv { | |
| FloatConv::Float(f) => { | |
| arg.cast_to(f); | |
| } | |
| FloatConv::FloatPair { first_ty, second_ty_offset_from_start, second_ty } => { | |
| arg.cast_to(CastTarget::offset_pair( | |
| first_ty, | |
| second_ty_offset_from_start, | |
| second_ty, | |
| )); | |
| } | |
| FloatConv::MixedPair { first_ty, second_ty_offset_from_start, second_ty } => { | |
| arg.cast_to(CastTarget::offset_pair( | |
| first_ty, | |
| second_ty_offset_from_start, | |
| second_ty, | |
| )); | |
| } | |
| } | |
| return false; | |
| } | |
| let total = arg.layout.size; | |
| // "Scalars wider than 2✕XLEN are passed by reference and are replaced in | |
| // the argument list with the address." | |
| // "Aggregates larger than 2✕XLEN bits are passed by reference and are | |
| // replaced in the argument list with the address, as are C++ aggregates | |
| // with nontrivial copy constructors, destructors, or vtables." | |
| if total.bits() > 2 * xlen { | |
| // We rely on the LLVM backend lowering code to lower passing a scalar larger than 2*XLEN. | |
| if is_riscv_aggregate(arg) { | |
| arg.make_indirect(); | |
| } | |
| return true; | |
| } | |
| let xlen_reg = match xlen { | |
| 32 => Reg::i32(), | |
| 64 => Reg::i64(), | |
| _ => unreachable!("Unsupported XLEN: {}", xlen), | |
| }; | |
| if is_riscv_aggregate(arg) { | |
| if total.bits() <= xlen { | |
| arg.cast_to(xlen_reg); | |
| } else { | |
| arg.cast_to(Uniform::new(xlen_reg, Size::from_bits(xlen * 2))); | |
| } | |
| return false; | |
| } | |
| // "When passed in registers, scalars narrower than XLEN bits are widened | |
| // according to the sign of their type up to 32 bits, then sign-extended to | |
| // XLEN bits." | |
| extend_integer_width(arg, xlen); | |
| false | |
| } | |
| fn classify_arg<'a, Ty, C>( | |
| cx: &C, | |
| arg: &mut ArgAbi<'a, Ty>, | |
| xlen: u64, | |
| flen: u64, | |
| is_vararg: bool, | |
| avail_gprs: &mut u64, | |
| avail_fprs: &mut u64, | |
| ) where | |
| Ty: TyAbiInterface<'a, C> + Copy, | |
| { | |
| if !arg.layout.is_sized() { | |
| // FIXME: Update avail_gprs? | |
| // Not touching this... | |
| return; | |
| } | |
| if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { | |
| arg.make_indirect(); | |
| *avail_gprs = (*avail_gprs).saturating_sub(1); | |
| return; | |
| } | |
| if !is_vararg { | |
| match should_use_fp_conv(cx, &arg.layout, xlen, flen) { | |
| Some(FloatConv::Float(f)) if *avail_fprs >= 1 => { | |
| *avail_fprs -= 1; | |
| arg.cast_to(f); | |
| return; | |
| } | |
| Some(FloatConv::FloatPair { first_ty, second_ty_offset_from_start, second_ty }) | |
| if *avail_fprs >= 2 => | |
| { | |
| *avail_fprs -= 2; | |
| arg.cast_to(CastTarget::offset_pair( | |
| first_ty, | |
| second_ty_offset_from_start, | |
| second_ty, | |
| )); | |
| return; | |
| } | |
| Some(FloatConv::MixedPair { first_ty, second_ty_offset_from_start, second_ty }) | |
| if *avail_fprs >= 1 && *avail_gprs >= 1 => | |
| { | |
| *avail_gprs -= 1; | |
| *avail_fprs -= 1; | |
| arg.cast_to(CastTarget::offset_pair( | |
| first_ty, | |
| second_ty_offset_from_start, | |
| second_ty, | |
| )); | |
| return; | |
| } | |
| _ => (), | |
| } | |
| } | |
| let total = arg.layout.size; | |
| let align = arg.layout.align.bits(); | |
| // "Scalars wider than 2✕XLEN are passed by reference and are replaced in | |
| // the argument list with the address." | |
| // "Aggregates larger than 2✕XLEN bits are passed by reference and are | |
| // replaced in the argument list with the address, as are C++ aggregates | |
| // with nontrivial copy constructors, destructors, or vtables." | |
| if total.bits() > 2 * xlen { | |
| // We rely on the LLVM backend lowering code to lower passing a scalar larger than 2*XLEN. | |
| if is_riscv_aggregate(arg) { | |
| arg.make_indirect(); | |
| } | |
| if *avail_gprs >= 1 { | |
| *avail_gprs -= 1; | |
| } | |
| return; | |
| } | |
| let double_xlen_reg = match xlen { | |
| 32 => Reg::i64(), | |
| 64 => Reg::i128(), | |
| _ => unreachable!("Unsupported XLEN: {}", xlen), | |
| }; | |
| let xlen_reg = match xlen { | |
| 32 => Reg::i32(), | |
| 64 => Reg::i64(), | |
| _ => unreachable!("Unsupported XLEN: {}", xlen), | |
| }; | |
| if total.bits() > xlen { | |
| let align_regs = align > xlen; | |
| if is_riscv_aggregate(arg) { | |
| arg.cast_to(Uniform::new( | |
| if align_regs { double_xlen_reg } else { xlen_reg }, | |
| Size::from_bits(xlen * 2), | |
| )); | |
| } | |
| if align_regs && is_vararg { | |
| *avail_gprs -= *avail_gprs % 2; | |
| } | |
| if *avail_gprs >= 2 { | |
| *avail_gprs -= 2; | |
| } else { | |
| *avail_gprs = 0; | |
| } | |
| return; | |
| } else if is_riscv_aggregate(arg) { | |
| arg.cast_to(xlen_reg); | |
| if *avail_gprs >= 1 { | |
| *avail_gprs -= 1; | |
| } | |
| return; | |
| } | |
| // "When passed in registers, scalars narrower than XLEN bits are widened | |
| // according to the sign of their type up to 32 bits, then sign-extended to | |
| // XLEN bits." | |
| if *avail_gprs >= 1 { | |
| extend_integer_width(arg, xlen); | |
| *avail_gprs -= 1; | |
| } | |
| } | |
| fn extend_integer_width<Ty>(arg: &mut ArgAbi<'_, Ty>, xlen: u64) { | |
| if let BackendRepr::Scalar(scalar) = arg.layout.backend_repr | |
| && let Primitive::Int(i, _) = scalar.primitive() | |
| && i.size().bits() == 32 | |
| && xlen > 32 | |
| && let PassMode::Direct(ref mut attrs) = arg.mode | |
| { | |
| attrs.ext(ArgExtension::Sext); | |
| return; | |
| } | |
| arg.extend_integer_width_to(xlen); | |
| } | |
| pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) | |
| where | |
| Ty: TyAbiInterface<'a, C> + Copy, | |
| C: HasDataLayout + HasTargetSpec, | |
| { | |
| let flen = match &cx.target_spec().llvm_abiname { | |
| LlvmAbi::Ilp32f | LlvmAbi::Lp64f => 32, | |
| LlvmAbi::Ilp32d | LlvmAbi::Lp64d => 64, | |
| _ => 0, | |
| }; | |
| let xlen = cx.data_layout().pointer_size().bits(); | |
| let mut avail_gprs = 8; | |
| let mut avail_fprs = 8; | |
| if !fn_abi.ret.is_ignore() && classify_ret(cx, &mut fn_abi.ret, xlen, flen) { | |
| avail_gprs -= 1; | |
| } | |
| for (i, arg) in fn_abi.args.iter_mut().enumerate() { | |
| if arg.is_ignore() { | |
| continue; | |
| } | |
| classify_arg( | |
| cx, | |
| arg, | |
| xlen, | |
| flen, | |
| i >= fn_abi.fixed_count as usize, | |
| &mut avail_gprs, | |
| &mut avail_fprs, | |
| ); | |
| } | |
| } | |
| pub(crate) fn compute_rust_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) | |
| where | |
| Ty: TyAbiInterface<'a, C> + Copy, | |
| C: HasDataLayout + HasTargetSpec, | |
| { | |
| let xlen = cx.data_layout().pointer_size().bits(); | |
| for arg in fn_abi.args.iter_mut() { | |
| if arg.is_ignore() { | |
| continue; | |
| } | |
| // LLVM integers types do not differentiate between signed or unsigned integers. | |
| // Some RISC-V instructions do not have a `.w` suffix version, they use all the | |
| // XLEN bits. By explicitly setting the `signext` or `zeroext` attribute | |
| // according to signedness to avoid unnecessary integer extending instructions. | |
| // | |
| // See https://github.com/rust-lang/rust/issues/114508 for details. | |
| extend_integer_width(arg, xlen); | |
| } | |
| } |