Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add loongarch64 abi support #101939

Merged
merged 1 commit into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
342 changes: 342 additions & 0 deletions compiler/rustc_target/src/abi/call/loongarch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
use crate::abi::call::{ArgAbi, ArgExtension, CastTarget, FnAbi, PassMode, Reg, RegKind, Uniform};
use crate::abi::{self, Abi, FieldsShape, HasDataLayout, Size, TyAbiInterface, TyAndLayout};
use crate::spec::HasTargetSpec;

#[derive(Copy, Clone)]
enum RegPassKind {
Float(Reg),
Integer(Reg),
Unknown,
}

#[derive(Copy, Clone)]
enum FloatConv {
FloatPair(Reg, Reg),
Float(Reg),
MixedPair(Reg, Reg),
}

#[derive(Copy, Clone)]
struct CannotUseFpConv;

fn is_loongarch_aggregate<'a, Ty>(arg: &ArgAbi<'a, Ty>) -> bool {
match arg.layout.abi {
Abi::Vector { .. } => 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,
) -> Result<(), CannotUseFpConv>
where
Ty: TyAbiInterface<'a, C> + Copy,
{
match arg_layout.abi {
Abi::Scalar(scalar) => match scalar.primitive() {
abi::Int(..) | abi::Pointer => {
if arg_layout.size.bits() > xlen {
return Err(CannotUseFpConv);
}
match (*field1_kind, *field2_kind) {
(RegPassKind::Unknown, _) => {
*field1_kind = RegPassKind::Integer(Reg {
kind: RegKind::Integer,
size: arg_layout.size,
});
}
(RegPassKind::Float(_), RegPassKind::Unknown) => {
*field2_kind = RegPassKind::Integer(Reg {
kind: RegKind::Integer,
size: arg_layout.size,
});
}
_ => return Err(CannotUseFpConv),
}
}
abi::F32 | abi::F64 => {
if arg_layout.size.bits() > flen {
return Err(CannotUseFpConv);
}
match (*field1_kind, *field2_kind) {
(RegPassKind::Unknown, _) => {
*field1_kind =
RegPassKind::Float(Reg { kind: RegKind::Float, size: arg_layout.size });
}
(_, RegPassKind::Unknown) => {
*field2_kind =
RegPassKind::Float(Reg { kind: RegKind::Float, size: arg_layout.size });
}
_ => return Err(CannotUseFpConv),
}
}
},
Abi::Vector { .. } | Abi::Uninhabited => return Err(CannotUseFpConv),
Abi::ScalarPair(..) | Abi::Aggregate { .. } => match arg_layout.fields {
FieldsShape::Primitive => {
unreachable!("aggregates can't have `FieldsShape::Primitive`")
}
FieldsShape::Union(_) => {
if !arg_layout.is_zst() {
return Err(CannotUseFpConv);
}
}
FieldsShape::Array { count, .. } => {
for _ 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,
)?;
}
}
FieldsShape::Arbitrary { .. } => {
match arg_layout.variants {
abi::Variants::Multiple { .. } => return Err(CannotUseFpConv),
abi::Variants::Single { .. } => (),
}
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)?;
}
}
},
}
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).is_err() {
return None;
}
match (field1_kind, field2_kind) {
(RegPassKind::Integer(l), RegPassKind::Float(r)) => Some(FloatConv::MixedPair(l, r)),
(RegPassKind::Float(l), RegPassKind::Integer(r)) => Some(FloatConv::MixedPair(l, r)),
(RegPassKind::Float(l), RegPassKind::Float(r)) => Some(FloatConv::FloatPair(l, r)),
(RegPassKind::Float(f), RegPassKind::Unknown) => Some(FloatConv::Float(f)),
_ => 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 let Some(conv) = should_use_fp_conv(cx, &arg.layout, xlen, flen) {
match conv {
FloatConv::Float(f) => {
arg.cast_to(f);
}
FloatConv::FloatPair(l, r) => {
arg.cast_to(CastTarget::pair(l, r));
}
FloatConv::MixedPair(l, r) => {
arg.cast_to(CastTarget::pair(l, r));
}
}
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_loongarch_aggregate(arg) {
arg.make_indirect();
}
return true;
}

let xlen_reg = match xlen {
32 => Reg::i32(),
64 => Reg::i64(),
_ => unreachable!("Unsupported XLEN: {}", xlen),
};
if is_loongarch_aggregate(arg) {
if total.bits() <= xlen {
arg.cast_to(xlen_reg);
} else {
arg.cast_to(Uniform { unit: xlen_reg, total: 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 !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(l, r)) if *avail_fprs >= 2 => {
*avail_fprs -= 2;
arg.cast_to(CastTarget::pair(l, r));
return;
}
Some(FloatConv::MixedPair(l, r)) if *avail_fprs >= 1 && *avail_gprs >= 1 => {
*avail_gprs -= 1;
*avail_fprs -= 1;
arg.cast_to(CastTarget::pair(l, r));
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_loongarch_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_loongarch_aggregate(arg) {
arg.cast_to(Uniform {
unit: if align_regs { double_xlen_reg } else { xlen_reg },
total: 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_loongarch_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<'a, Ty>(arg: &mut ArgAbi<'a, Ty>, xlen: u64) {
if let Abi::Scalar(scalar) = arg.layout.abi {
if let abi::Int(i, _) = scalar.primitive() {
// 32-bit integers are always sign-extended
if i.size().bits() == 32 && xlen > 32 {
if let PassMode::Direct(ref mut attrs) = arg.mode {
attrs.ext(ArgExtension::Sext);
return;
}
}
}
}

arg.extend_integer_width_to(xlen);
}

pub 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 xlen = cx.data_layout().pointer_size.bits();
let flen = match &cx.target_spec().llvm_abiname[..] {
"ilp32f" | "lp64f" => 32,
"ilp32d" | "lp64d" => 64,
_ => 0,
};

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,
);
}
}
2 changes: 2 additions & 0 deletions compiler/rustc_target/src/abi/call/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod arm;
mod avr;
mod bpf;
mod hexagon;
mod loongarch;
mod m68k;
mod mips;
mod mips64;
Expand Down Expand Up @@ -696,6 +697,7 @@ impl<'a, Ty> FnAbi<'a, Ty> {
"amdgpu" => amdgpu::compute_abi_info(cx, self),
"arm" => arm::compute_abi_info(cx, self),
"avr" => avr::compute_abi_info(self),
"loongarch64" => loongarch::compute_abi_info(cx, self),
"m68k" => m68k::compute_abi_info(self),
"mips" => mips::compute_abi_info(cx, self),
"mips64" => mips64::compute_abi_info(cx, self),
Expand Down