// 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; #[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(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 { .. } => 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 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() { // Not touching this... 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.abi.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(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[..] { "ilp32f" | "lp64f" => 32, "ilp32d" | "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); } }