Rollup merge of #131955 - SpriteOvO:riscv-int-arg-attr, r=workingjubilee

Set `signext` or `zeroext` for integer arguments on RISC-V and LoongArch64

This PR contains 3 commits:

- the first one introduces a new function `adjust_for_rust_abi` in `rustc_target`, and moves the x86 specific adjustment code into it;
- the second one adds RISC-V specific adjustment code into it, which sets `signext` or `zeroext` attribute for integer arguments.
- **UPDATE**: added the 3rd commit to apply the same adjustment for LoongArch64.
This commit is contained in:
León Orell Valerian Liehr 2024-10-23 22:11:03 +02:00 committed by GitHub
commit a144561608
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 468 additions and 168 deletions

View File

@ -1,6 +1,7 @@
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;
use crate::spec::abi::Abi as SpecAbi;
#[derive(Copy, Clone)]
enum RegPassKind {
@ -359,3 +360,30 @@ where
);
}
}
pub(crate) fn compute_rust_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>, abi: SpecAbi)
where
Ty: TyAbiInterface<'a, C> + Copy,
C: HasDataLayout + HasTargetSpec,
{
if abi == SpecAbi::RustIntrinsic {
return;
}
let grlen = 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 LoongArch instructions do not have a `.w` suffix version, they use all the
// GRLEN bits. By explicitly setting the `signext` or `zeroext` attribute
// according to signedness to avoid unnecessary integer extending instructions.
//
// This is similar to the RISC-V case, see
// https://github.com/rust-lang/rust/issues/114508 for details.
extend_integer_width(arg, grlen);
}
}

View File

@ -1,11 +1,14 @@
use std::fmt;
use std::str::FromStr;
use std::{fmt, iter};
pub use rustc_abi::{Reg, RegKind};
use rustc_macros::HashStable_Generic;
use rustc_span::Symbol;
use crate::abi::{self, Abi, Align, HasDataLayout, Size, TyAbiInterface, TyAndLayout};
use crate::abi::{
self, Abi, AddressSpace, Align, HasDataLayout, Pointer, Size, TyAbiInterface, TyAndLayout,
};
use crate::spec::abi::Abi as SpecAbi;
use crate::spec::{self, HasTargetSpec, HasWasmCAbiOpt, HasX86AbiOpt, WasmCAbi};
mod aarch64;
@ -720,6 +723,118 @@ impl<'a, Ty> FnAbi<'a, Ty> {
Ok(())
}
pub fn adjust_for_rust_abi<C>(&mut self, cx: &C, abi: SpecAbi)
where
Ty: TyAbiInterface<'a, C> + Copy,
C: HasDataLayout + HasTargetSpec,
{
let spec = cx.target_spec();
match &spec.arch[..] {
"x86" => x86::compute_rust_abi_info(cx, self, abi),
"riscv32" | "riscv64" => riscv::compute_rust_abi_info(cx, self, abi),
"loongarch64" => loongarch::compute_rust_abi_info(cx, self, abi),
_ => {}
};
for (arg_idx, arg) in self
.args
.iter_mut()
.enumerate()
.map(|(idx, arg)| (Some(idx), arg))
.chain(iter::once((None, &mut self.ret)))
{
if arg.is_ignore() {
continue;
}
if arg_idx.is_none() && arg.layout.size > Pointer(AddressSpace::DATA).size(cx) * 2 {
// Return values larger than 2 registers using a return area
// pointer. LLVM and Cranelift disagree about how to return
// values that don't fit in the registers designated for return
// values. LLVM will force the entire return value to be passed
// by return area pointer, while Cranelift will look at each IR level
// return value independently and decide to pass it in a
// register or not, which would result in the return value
// being passed partially in registers and partially through a
// return area pointer.
//
// While Cranelift may need to be fixed as the LLVM behavior is
// generally more correct with respect to the surface language,
// forcing this behavior in rustc itself makes it easier for
// other backends to conform to the Rust ABI and for the C ABI
// rustc already handles this behavior anyway.
//
// In addition LLVM's decision to pass the return value in
// registers or using a return area pointer depends on how
// exactly the return type is lowered to an LLVM IR type. For
// example `Option<u128>` can be lowered as `{ i128, i128 }`
// in which case the x86_64 backend would use a return area
// pointer, or it could be passed as `{ i32, i128 }` in which
// case the x86_64 backend would pass it in registers by taking
// advantage of an LLVM ABI extension that allows using 3
// registers for the x86_64 sysv call conv rather than the
// officially specified 2 registers.
//
// FIXME: Technically we should look at the amount of available
// return registers rather than guessing that there are 2
// registers for return values. In practice only a couple of
// architectures have less than 2 return registers. None of
// which supported by Cranelift.
//
// NOTE: This adjustment is only necessary for the Rust ABI as
// for other ABI's the calling convention implementations in
// rustc_target already ensure any return value which doesn't
// fit in the available amount of return registers is passed in
// the right way for the current target.
arg.make_indirect();
continue;
}
match arg.layout.abi {
Abi::Aggregate { .. } => {}
// This is a fun case! The gist of what this is doing is
// that we want callers and callees to always agree on the
// ABI of how they pass SIMD arguments. If we were to *not*
// make these arguments indirect then they'd be immediates
// in LLVM, which means that they'd used whatever the
// appropriate ABI is for the callee and the caller. That
// means, for example, if the caller doesn't have AVX
// enabled but the callee does, then passing an AVX argument
// across this boundary would cause corrupt data to show up.
//
// This problem is fixed by unconditionally passing SIMD
// arguments through memory between callers and callees
// which should get them all to agree on ABI regardless of
// target feature sets. Some more information about this
// issue can be found in #44367.
//
// Note that the intrinsic ABI is exempt here as
// that's how we connect up to LLVM and it's unstable
// anyway, we control all calls to it in libstd.
Abi::Vector { .. } if abi != SpecAbi::RustIntrinsic && spec.simd_types_indirect => {
arg.make_indirect();
continue;
}
_ => continue,
}
// Compute `Aggregate` ABI.
let is_indirect_not_on_stack =
matches!(arg.mode, PassMode::Indirect { on_stack: false, .. });
assert!(is_indirect_not_on_stack);
let size = arg.layout.size;
if !arg.layout.is_unsized() && size <= Pointer(AddressSpace::DATA).size(cx) {
// We want to pass small aggregates as immediates, but using
// an LLVM aggregate type for this leads to bad optimizations,
// so we pick an appropriately sized integer type instead.
arg.cast_to(Reg { kind: RegKind::Integer, size });
}
}
}
}
impl FromStr for Conv {

View File

@ -7,6 +7,7 @@
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;
use crate::spec::abi::Abi as SpecAbi;
#[derive(Copy, Clone)]
enum RegPassKind {
@ -365,3 +366,29 @@ where
);
}
}
pub(crate) fn compute_rust_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>, abi: SpecAbi)
where
Ty: TyAbiInterface<'a, C> + Copy,
C: HasDataLayout + HasTargetSpec,
{
if abi == SpecAbi::RustIntrinsic {
return;
}
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);
}
}

View File

@ -1,6 +1,9 @@
use crate::abi::call::{ArgAttribute, FnAbi, PassMode, Reg, RegKind};
use crate::abi::{Abi, Align, HasDataLayout, TyAbiInterface, TyAndLayout};
use crate::abi::{
Abi, AddressSpace, Align, Float, HasDataLayout, Pointer, TyAbiInterface, TyAndLayout,
};
use crate::spec::HasTargetSpec;
use crate::spec::abi::Abi as SpecAbi;
#[derive(PartialEq)]
pub(crate) enum Flavor {
@ -207,3 +210,35 @@ pub(crate) fn fill_inregs<'a, Ty, C>(
}
}
}
pub(crate) fn compute_rust_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>, abi: SpecAbi)
where
Ty: TyAbiInterface<'a, C> + Copy,
C: HasDataLayout + HasTargetSpec,
{
// Avoid returning floats in x87 registers on x86 as loading and storing from x87
// registers will quiet signalling NaNs. Also avoid using SSE registers since they
// are not always available (depending on target features).
if !fn_abi.ret.is_ignore()
// Intrinsics themselves are not actual "real" functions, so theres no need to change their ABIs.
&& abi != SpecAbi::RustIntrinsic
{
let has_float = match fn_abi.ret.layout.abi {
Abi::Scalar(s) => matches!(s.primitive(), Float(_)),
Abi::ScalarPair(s1, s2) => {
matches!(s1.primitive(), Float(_)) || matches!(s2.primitive(), Float(_))
}
_ => false, // anyway not passed via registers on x86
};
if has_float {
if fn_abi.ret.layout.size <= Pointer(AddressSpace::DATA).size(cx) {
// Same size or smaller than pointer, return in a register.
fn_abi.ret.cast_to(Reg { kind: RegKind::Integer, size: fn_abi.ret.layout.size });
} else {
// Larger than a pointer, return indirectly.
fn_abi.ret.make_indirect();
}
return;
}
}
}

View File

@ -1,7 +1,7 @@
use std::iter;
use rustc_abi::Primitive::{Float, Pointer};
use rustc_abi::{Abi, AddressSpace, PointerKind, Scalar, Size};
use rustc_abi::Primitive::Pointer;
use rustc_abi::{Abi, PointerKind, Scalar, Size};
use rustc_hir as hir;
use rustc_hir::lang_items::LangItem;
use rustc_middle::bug;
@ -13,8 +13,7 @@ use rustc_middle::ty::{self, InstanceKind, Ty, TyCtxt};
use rustc_session::config::OptLevel;
use rustc_span::def_id::DefId;
use rustc_target::abi::call::{
ArgAbi, ArgAttribute, ArgAttributes, ArgExtension, Conv, FnAbi, PassMode, Reg, RegKind,
RiscvInterruptKind,
ArgAbi, ArgAttribute, ArgAttributes, ArgExtension, Conv, FnAbi, PassMode, RiscvInterruptKind,
};
use rustc_target::spec::abi::Abi as SpecAbi;
use tracing::debug;
@ -678,6 +677,8 @@ fn fn_abi_adjust_for_abi<'tcx>(
let tcx = cx.tcx();
if abi == SpecAbi::Rust || abi == SpecAbi::RustCall || abi == SpecAbi::RustIntrinsic {
fn_abi.adjust_for_rust_abi(cx, abi);
// Look up the deduced parameter attributes for this function, if we have its def ID and
// we're optimizing in non-incremental mode. We'll tag its parameters with those attributes
// as appropriate.
@ -688,125 +689,9 @@ fn fn_abi_adjust_for_abi<'tcx>(
&[]
};
let fixup = |arg: &mut ArgAbi<'tcx, Ty<'tcx>>, arg_idx: Option<usize>| {
for (arg_idx, arg) in fn_abi.args.iter_mut().enumerate() {
if arg.is_ignore() {
return;
}
// Avoid returning floats in x87 registers on x86 as loading and storing from x87
// registers will quiet signalling NaNs. Also avoid using SSE registers since they
// are not always available (depending on target features).
if tcx.sess.target.arch == "x86"
&& arg_idx.is_none()
// Intrinsics themselves are not actual "real" functions, so theres no need to
// change their ABIs.
&& abi != SpecAbi::RustIntrinsic
{
let has_float = match arg.layout.abi {
Abi::Scalar(s) => matches!(s.primitive(), Float(_)),
Abi::ScalarPair(s1, s2) => {
matches!(s1.primitive(), Float(_)) || matches!(s2.primitive(), Float(_))
}
_ => false, // anyway not passed via registers on x86
};
if has_float {
if arg.layout.size <= Pointer(AddressSpace::DATA).size(cx) {
// Same size or smaller than pointer, return in a register.
arg.cast_to(Reg { kind: RegKind::Integer, size: arg.layout.size });
} else {
// Larger than a pointer, return indirectly.
arg.make_indirect();
}
return;
}
}
if arg_idx.is_none() && arg.layout.size > Pointer(AddressSpace::DATA).size(cx) * 2 {
// Return values larger than 2 registers using a return area
// pointer. LLVM and Cranelift disagree about how to return
// values that don't fit in the registers designated for return
// values. LLVM will force the entire return value to be passed
// by return area pointer, while Cranelift will look at each IR level
// return value independently and decide to pass it in a
// register or not, which would result in the return value
// being passed partially in registers and partially through a
// return area pointer.
//
// While Cranelift may need to be fixed as the LLVM behavior is
// generally more correct with respect to the surface language,
// forcing this behavior in rustc itself makes it easier for
// other backends to conform to the Rust ABI and for the C ABI
// rustc already handles this behavior anyway.
//
// In addition LLVM's decision to pass the return value in
// registers or using a return area pointer depends on how
// exactly the return type is lowered to an LLVM IR type. For
// example `Option<u128>` can be lowered as `{ i128, i128 }`
// in which case the x86_64 backend would use a return area
// pointer, or it could be passed as `{ i32, i128 }` in which
// case the x86_64 backend would pass it in registers by taking
// advantage of an LLVM ABI extension that allows using 3
// registers for the x86_64 sysv call conv rather than the
// officially specified 2 registers.
//
// FIXME: Technically we should look at the amount of available
// return registers rather than guessing that there are 2
// registers for return values. In practice only a couple of
// architectures have less than 2 return registers. None of
// which supported by Cranelift.
//
// NOTE: This adjustment is only necessary for the Rust ABI as
// for other ABI's the calling convention implementations in
// rustc_target already ensure any return value which doesn't
// fit in the available amount of return registers is passed in
// the right way for the current target.
arg.make_indirect();
return;
}
match arg.layout.abi {
Abi::Aggregate { .. } => {}
// This is a fun case! The gist of what this is doing is
// that we want callers and callees to always agree on the
// ABI of how they pass SIMD arguments. If we were to *not*
// make these arguments indirect then they'd be immediates
// in LLVM, which means that they'd used whatever the
// appropriate ABI is for the callee and the caller. That
// means, for example, if the caller doesn't have AVX
// enabled but the callee does, then passing an AVX argument
// across this boundary would cause corrupt data to show up.
//
// This problem is fixed by unconditionally passing SIMD
// arguments through memory between callers and callees
// which should get them all to agree on ABI regardless of
// target feature sets. Some more information about this
// issue can be found in #44367.
//
// Note that the intrinsic ABI is exempt here as
// that's how we connect up to LLVM and it's unstable
// anyway, we control all calls to it in libstd.
Abi::Vector { .. }
if abi != SpecAbi::RustIntrinsic && tcx.sess.target.simd_types_indirect =>
{
arg.make_indirect();
return;
}
_ => return,
}
// Compute `Aggregate` ABI.
let is_indirect_not_on_stack =
matches!(arg.mode, PassMode::Indirect { on_stack: false, .. });
assert!(is_indirect_not_on_stack, "{:?}", arg);
let size = arg.layout.size;
if !arg.layout.is_unsized() && size <= Pointer(AddressSpace::DATA).size(cx) {
// We want to pass small aggregates as immediates, but using
// an LLVM aggregate type for this leads to bad optimizations,
// so we pick an appropriately sized integer type instead.
arg.cast_to(Reg { kind: RegKind::Integer, size });
continue;
}
// If we deduced that this parameter was read-only, add that to the attribute list now.
@ -814,9 +699,7 @@ fn fn_abi_adjust_for_abi<'tcx>(
// The `readonly` parameter only applies to pointers, so we can only do this if the
// argument was passed indirectly. (If the argument is passed directly, it's an SSA
// value, so it's implicitly immutable.)
if let (Some(arg_idx), &mut PassMode::Indirect { ref mut attrs, .. }) =
(arg_idx, &mut arg.mode)
{
if let &mut PassMode::Indirect { ref mut attrs, .. } = &mut arg.mode {
// The `deduced_param_attrs` list could be empty if this is a type of function
// we can't deduce any parameters for, so make sure the argument index is in
// bounds.
@ -827,11 +710,6 @@ fn fn_abi_adjust_for_abi<'tcx>(
}
}
}
};
fixup(&mut fn_abi.ret, None);
for (arg_idx, arg) in fn_abi.args.iter_mut().enumerate() {
fixup(arg, Some(arg_idx));
}
} else {
fn_abi

View File

@ -0,0 +1,108 @@
//@ assembly-output: emit-asm
//@ revisions: riscv64 riscv64-zbb loongarch64
//@ compile-flags: -C opt-level=3
//@ [riscv64] compile-flags: --target riscv64gc-unknown-linux-gnu
//@ [riscv64] needs-llvm-components: riscv
//@ [riscv64-zbb] compile-flags: --target riscv64gc-unknown-linux-gnu
//@ [riscv64-zbb] compile-flags: -C target-feature=+zbb
//@ [riscv64-zbb] needs-llvm-components: riscv
//@ [loongarch64] compile-flags: --target loongarch64-unknown-linux-gnu
//@ [loongarch64] needs-llvm-components: loongarch
#![feature(no_core, lang_items, intrinsics, rustc_attrs)]
#![crate_type = "lib"]
#![no_std]
#![no_core]
// FIXME: Migrate these code after PR #130693 is landed.
// vvvvv core
#[lang = "sized"]
trait Sized {}
#[lang = "copy"]
trait Copy {}
impl Copy for i8 {}
impl Copy for u32 {}
impl Copy for i32 {}
#[lang = "neg"]
trait Neg {
type Output;
fn neg(self) -> Self::Output;
}
impl Neg for i8 {
type Output = i8;
fn neg(self) -> Self::Output {
-self
}
}
#[lang = "Ordering"]
#[repr(i8)]
enum Ordering {
Less = -1,
Equal = 0,
Greater = 1,
}
extern "rust-intrinsic" {
#[rustc_safe_intrinsic]
fn three_way_compare<T: Copy>(lhs: T, rhs: T) -> Ordering;
}
// ^^^^^ core
// Reimplementation of function `{integer}::max`.
macro_rules! max {
($a:expr, $b:expr) => {
match three_way_compare($a, $b) {
Ordering::Less | Ordering::Equal => $b,
Ordering::Greater => $a,
}
};
}
#[no_mangle]
// CHECK-LABEL: issue_114508_u32:
pub fn issue_114508_u32(a: u32, b: u32) -> u32 {
// CHECK-NEXT: .cfi_startproc
// riscv64-NEXT: bltu a1, a0, .[[RET:.+]]
// riscv64-NEXT: mv a0, a1
// riscv64-NEXT: .[[RET]]:
// riscv64-zbb-NEXT: maxu a0, a0, a1
// loongarch64-NEXT: sltu $a2, $a1, $a0
// loongarch64-NEXT: masknez $a1, $a1, $a2
// loongarch64-NEXT: maskeqz $a0, $a0, $a2
// loongarch64-NEXT: or $a0, $a0, $a1
// CHECK-NEXT: ret
max!(a, b)
}
#[no_mangle]
// CHECK-LABEL: issue_114508_i32:
pub fn issue_114508_i32(a: i32, b: i32) -> i32 {
// CHECK-NEXT: .cfi_startproc
// riscv64-NEXT: blt a1, a0, .[[RET:.+]]
// riscv64-NEXT: mv a0, a1
// riscv64-NEXT: .[[RET]]:
// riscv64-zbb-NEXT: max a0, a0, a1
// loongarch64-NEXT: slt $a2, $a1, $a0
// loongarch64-NEXT: masknez $a1, $a1, $a2
// loongarch64-NEXT: maskeqz $a0, $a0, $a2
// loongarch64-NEXT: or $a0, $a0, $a1
// CHECK-NEXT: ret
max!(a, b)
}

View File

@ -5,7 +5,7 @@
// Ensure that when val < base, we do not divide or multiply.
// CHECK-LABEL: @checked_ilog
// CHECK-SAME: (i16 noundef %val, i16 noundef %base)
// CHECK-SAME: (i16{{.*}} %val, i16{{.*}} %base)
#[no_mangle]
pub fn checked_ilog(val: u16, base: u16) -> Option<u32> {
// CHECK-NOT: udiv

View File

@ -8,7 +8,7 @@
// Thanks to poison semantics, this doesn't even need branches.
// CHECK-LABEL: @checked_sub_unsigned
// CHECK-SAME: (i16 noundef %a, i16 noundef %b)
// CHECK-SAME: (i16{{.*}} %a, i16{{.*}} %b)
#[no_mangle]
pub fn checked_sub_unsigned(a: u16, b: u16) -> Option<u16> {
// CHECK-DAG: %[[IS_SOME:.+]] = icmp uge i16 %a, %b
@ -26,7 +26,7 @@ pub fn checked_sub_unsigned(a: u16, b: u16) -> Option<u16> {
// looking for no-wrap flags, we just need there to not be any masking.
// CHECK-LABEL: @checked_shl_unsigned
// CHECK-SAME: (i32 noundef %a, i32 noundef %b)
// CHECK-SAME: (i32{{.*}} %a, i32{{.*}} %b)
#[no_mangle]
pub fn checked_shl_unsigned(a: u32, b: u32) -> Option<u32> {
// CHECK-DAG: %[[IS_SOME:.+]] = icmp ult i32 %b, 32
@ -41,7 +41,7 @@ pub fn checked_shl_unsigned(a: u32, b: u32) -> Option<u32> {
}
// CHECK-LABEL: @checked_shr_unsigned
// CHECK-SAME: (i32 noundef %a, i32 noundef %b)
// CHECK-SAME: (i32{{.*}} %a, i32{{.*}} %b)
#[no_mangle]
pub fn checked_shr_unsigned(a: u32, b: u32) -> Option<u32> {
// CHECK-DAG: %[[IS_SOME:.+]] = icmp ult i32 %b, 32
@ -56,7 +56,7 @@ pub fn checked_shr_unsigned(a: u32, b: u32) -> Option<u32> {
}
// CHECK-LABEL: @checked_shl_signed
// CHECK-SAME: (i32 noundef %a, i32 noundef %b)
// CHECK-SAME: (i32{{.*}} %a, i32{{.*}} %b)
#[no_mangle]
pub fn checked_shl_signed(a: i32, b: u32) -> Option<i32> {
// CHECK-DAG: %[[IS_SOME:.+]] = icmp ult i32 %b, 32
@ -71,7 +71,7 @@ pub fn checked_shl_signed(a: i32, b: u32) -> Option<i32> {
}
// CHECK-LABEL: @checked_shr_signed
// CHECK-SAME: (i32 noundef %a, i32 noundef %b)
// CHECK-SAME: (i32{{.*}} %a, i32{{.*}} %b)
#[no_mangle]
pub fn checked_shr_signed(a: i32, b: u32) -> Option<i32> {
// CHECK-DAG: %[[IS_SOME:.+]] = icmp ult i32 %b, 32
@ -86,7 +86,7 @@ pub fn checked_shr_signed(a: i32, b: u32) -> Option<i32> {
}
// CHECK-LABEL: @checked_add_one_unwrap_unsigned
// CHECK-SAME: (i32 noundef %x)
// CHECK-SAME: (i32{{.*}} %x)
#[no_mangle]
pub fn checked_add_one_unwrap_unsigned(x: u32) -> u32 {
// CHECK: %[[IS_MAX:.+]] = icmp eq i32 %x, -1

View File

@ -12,7 +12,7 @@ use std::cmp::Ordering;
pub struct Foo(u16);
// CHECK-LABEL: @check_lt
// CHECK-SAME: (i16 noundef %[[A:.+]], i16 noundef %[[B:.+]])
// CHECK-SAME: (i16{{.*}} %[[A:.+]], i16{{.*}} %[[B:.+]])
#[no_mangle]
pub fn check_lt(a: Foo, b: Foo) -> bool {
// CHECK: %[[R:.+]] = icmp ult i16 %[[A]], %[[B]]
@ -21,7 +21,7 @@ pub fn check_lt(a: Foo, b: Foo) -> bool {
}
// CHECK-LABEL: @check_le
// CHECK-SAME: (i16 noundef %[[A:.+]], i16 noundef %[[B:.+]])
// CHECK-SAME: (i16{{.*}} %[[A:.+]], i16{{.*}} %[[B:.+]])
#[no_mangle]
pub fn check_le(a: Foo, b: Foo) -> bool {
// CHECK: %[[R:.+]] = icmp ule i16 %[[A]], %[[B]]
@ -30,7 +30,7 @@ pub fn check_le(a: Foo, b: Foo) -> bool {
}
// CHECK-LABEL: @check_gt
// CHECK-SAME: (i16 noundef %[[A:.+]], i16 noundef %[[B:.+]])
// CHECK-SAME: (i16{{.*}} %[[A:.+]], i16{{.*}} %[[B:.+]])
#[no_mangle]
pub fn check_gt(a: Foo, b: Foo) -> bool {
// CHECK: %[[R:.+]] = icmp ugt i16 %[[A]], %[[B]]
@ -39,7 +39,7 @@ pub fn check_gt(a: Foo, b: Foo) -> bool {
}
// CHECK-LABEL: @check_ge
// CHECK-SAME: (i16 noundef %[[A:.+]], i16 noundef %[[B:.+]])
// CHECK-SAME: (i16{{.*}} %[[A:.+]], i16{{.*}} %[[B:.+]])
#[no_mangle]
pub fn check_ge(a: Foo, b: Foo) -> bool {
// CHECK: %[[R:.+]] = icmp uge i16 %[[A]], %[[B]]

View File

@ -6,11 +6,11 @@
#[no_mangle]
pub fn sum(x: u32, y: u32) -> u32 {
// YES-LABEL: define{{.*}}i32 @sum(i32 noundef %0, i32 noundef %1)
// YES-LABEL: define{{.*}}i32 @sum(i32{{.*}} %0, i32{{.*}} %1)
// YES-NEXT: %3 = add i32 %1, %0
// YES-NEXT: ret i32 %3
// NO-LABEL: define{{.*}}i32 @sum(i32 noundef %x, i32 noundef %y)
// NO-LABEL: define{{.*}}i32 @sum(i32{{.*}} %x, i32{{.*}} %y)
// NO-NEXT: start:
// NO-NEXT: %z = add i32 %y, %x
// NO-NEXT: ret i32 %z

View File

@ -32,7 +32,7 @@ pub fn boolean(x: bool) -> bool {
x
}
// CHECK: i8 @maybeuninit_boolean(i8 %x)
// CHECK: i8 @maybeuninit_boolean(i8{{.*}} %x)
#[no_mangle]
pub fn maybeuninit_boolean(x: MaybeUninit<bool>) -> MaybeUninit<bool> {
x
@ -44,19 +44,19 @@ pub fn enum_bool(x: MyBool) -> MyBool {
x
}
// CHECK: i8 @maybeuninit_enum_bool(i8 %x)
// CHECK: i8 @maybeuninit_enum_bool(i8{{.*}} %x)
#[no_mangle]
pub fn maybeuninit_enum_bool(x: MaybeUninit<MyBool>) -> MaybeUninit<MyBool> {
x
}
// CHECK: noundef{{( range\(i32 0, 1114112\))?}} i32 @char(i32 noundef{{( range\(i32 0, 1114112\))?}} %x)
// CHECK: noundef{{( range\(i32 0, 1114112\))?}} i32 @char(i32{{.*}}{{( range\(i32 0, 1114112\))?}} %x)
#[no_mangle]
pub fn char(x: char) -> char {
x
}
// CHECK: i32 @maybeuninit_char(i32 %x)
// CHECK: i32 @maybeuninit_char(i32{{.*}} %x)
#[no_mangle]
pub fn maybeuninit_char(x: MaybeUninit<char>) -> MaybeUninit<char> {
x

View File

@ -10,8 +10,7 @@ use std::intrinsics::three_way_compare;
#[no_mangle]
// CHECK-LABEL: @signed_cmp
// DEBUG-SAME: (i16 %a, i16 %b)
// OPTIM-SAME: (i16 noundef %a, i16 noundef %b)
// CHECK-SAME: (i16{{.*}} %a, i16{{.*}} %b)
pub fn signed_cmp(a: i16, b: i16) -> std::cmp::Ordering {
// DEBUG: %[[GT:.+]] = icmp sgt i16 %a, %b
// DEBUG: %[[ZGT:.+]] = zext i1 %[[GT]] to i8
@ -29,8 +28,7 @@ pub fn signed_cmp(a: i16, b: i16) -> std::cmp::Ordering {
#[no_mangle]
// CHECK-LABEL: @unsigned_cmp
// DEBUG-SAME: (i16 %a, i16 %b)
// OPTIM-SAME: (i16 noundef %a, i16 noundef %b)
// CHECK-SAME: (i16{{.*}} %a, i16{{.*}} %b)
pub fn unsigned_cmp(a: u16, b: u16) -> std::cmp::Ordering {
// DEBUG: %[[GT:.+]] = icmp ugt i16 %a, %b
// DEBUG: %[[ZGT:.+]] = zext i1 %[[GT]] to i8

View File

@ -9,7 +9,7 @@
#[repr(transparent)]
pub struct Transparent32(u32);
// CHECK: i32 @make_transparent(i32 noundef %x)
// CHECK: i32 @make_transparent(i32{{.*}} %x)
#[no_mangle]
pub fn make_transparent(x: u32) -> Transparent32 {
// CHECK-NOT: alloca
@ -18,7 +18,7 @@ pub fn make_transparent(x: u32) -> Transparent32 {
a
}
// CHECK: i32 @make_closure(i32 noundef %x)
// CHECK: i32 @make_closure(i32{{.*}} %x)
#[no_mangle]
pub fn make_closure(x: i32) -> impl Fn(i32) -> i32 {
// CHECK-NOT: alloca
@ -40,7 +40,7 @@ pub fn make_transparent_pair(x: (u16, u16)) -> TransparentPair {
a
}
// CHECK-LABEL: { i32, i32 } @make_2_tuple(i32 noundef %x)
// CHECK-LABEL: { i32, i32 } @make_2_tuple(i32{{.*}} %x)
#[no_mangle]
pub fn make_2_tuple(x: u32) -> (u32, u32) {
// CHECK-NOT: alloca
@ -59,7 +59,7 @@ pub fn make_cell_of_bool(b: bool) -> std::cell::Cell<bool> {
std::cell::Cell::new(b)
}
// CHECK-LABEL: { i8, i16 } @make_cell_of_bool_and_short(i1 noundef zeroext %b, i16 noundef %s)
// CHECK-LABEL: { i8, i16 } @make_cell_of_bool_and_short(i1 noundef zeroext %b, i16{{.*}} %s)
#[no_mangle]
pub fn make_cell_of_bool_and_short(b: bool, s: u16) -> std::cell::Cell<(bool, u16)> {
// CHECK-NOT: alloca
@ -92,7 +92,7 @@ pub fn make_struct_0() -> Struct0 {
pub struct Struct1(i32);
// CHECK-LABEL: i32 @make_struct_1(i32 noundef %a)
// CHECK-LABEL: i32 @make_struct_1(i32{{.*}} %a)
#[no_mangle]
pub fn make_struct_1(a: i32) -> Struct1 {
// CHECK: ret i32 %a
@ -104,7 +104,7 @@ pub struct Struct2Asc(i16, i64);
// bit32-LABEL: void @make_struct_2_asc({{.*}} sret({{[^,]*}}) {{.*}} %s,
// bit64-LABEL: { i64, i16 } @make_struct_2_asc(
// CHECK-SAME: i16 noundef %a, i64 noundef %b)
// CHECK-SAME: i16{{.*}} %a, i64 noundef %b)
#[no_mangle]
pub fn make_struct_2_asc(a: i16, b: i64) -> Struct2Asc {
// CHECK-NOT: alloca
@ -122,7 +122,7 @@ pub struct Struct2Desc(i64, i16);
// bit32-LABEL: void @make_struct_2_desc({{.*}} sret({{[^,]*}}) {{.*}} %s,
// bit64-LABEL: { i64, i16 } @make_struct_2_desc(
// CHECK-SAME: i64 noundef %a, i16 noundef %b)
// CHECK-SAME: i64 noundef %a, i16{{.*}} %b)
#[no_mangle]
pub fn make_struct_2_desc(a: i64, b: i16) -> Struct2Desc {
// CHECK-NOT: alloca

View File

@ -24,7 +24,7 @@ pub fn nonzero_int(x: NonZero<u128>) -> NonZero<u128> {
x
}
// CHECK: noundef range(i8 0, 3) i8 @optional_bool(i8 noundef range(i8 0, 3) %x)
// CHECK: noundef range(i8 0, 3) i8 @optional_bool(i8{{.*}} range(i8 0, 3) %x)
#[no_mangle]
pub fn optional_bool(x: Option<bool>) -> Option<bool> {
x
@ -36,7 +36,7 @@ pub enum Enum0 {
C,
}
// CHECK: noundef range(i8 0, 4) i8 @enum0_value(i8 noundef range(i8 0, 4) %x)
// CHECK: noundef range(i8 0, 4) i8 @enum0_value(i8{{.*}} range(i8 0, 4) %x)
#[no_mangle]
pub fn enum0_value(x: Enum0) -> Enum0 {
x

View File

@ -0,0 +1,111 @@
//@ compile-flags: -O -C no-prepopulate-passes
//@ revisions: riscv64 loongarch64
//@[riscv64] only-riscv64
//@[riscv64] compile-flags: --target riscv64gc-unknown-linux-gnu
//@[riscv64] needs-llvm-components: riscv
//@[loongarch64] only-loongarch64
//@[loongarch64] compile-flags: --target loongarch64-unknown-linux-gnu
//@[loongarch64] needs-llvm-components: loongarch
#![crate_type = "lib"]
#[no_mangle]
// riscv64: define noundef i8 @arg_attr_u8(i8 noundef zeroext %x)
// loongarch64: define noundef i8 @arg_attr_u8(i8 noundef zeroext %x)
pub fn arg_attr_u8(x: u8) -> u8 {
x
}
#[no_mangle]
// riscv64: define noundef i16 @arg_attr_u16(i16 noundef zeroext %x)
// loongarch64: define noundef i16 @arg_attr_u16(i16 noundef zeroext %x)
pub fn arg_attr_u16(x: u16) -> u16 {
x
}
#[no_mangle]
// riscv64: define noundef i32 @arg_attr_u32(i32 noundef signext %x)
// loongarch64: define noundef i32 @arg_attr_u32(i32 noundef signext %x)
pub fn arg_attr_u32(x: u32) -> u32 {
x
}
#[no_mangle]
// riscv64: define noundef i64 @arg_attr_u64(i64 noundef %x)
// loongarch64: define noundef i64 @arg_attr_u64(i64 noundef %x)
pub fn arg_attr_u64(x: u64) -> u64 {
x
}
#[no_mangle]
// riscv64: define noundef i128 @arg_attr_u128(i128 noundef %x)
// loongarch64: define noundef i128 @arg_attr_u128(i128 noundef %x)
pub fn arg_attr_u128(x: u128) -> u128 {
x
}
#[no_mangle]
// riscv64: define noundef i8 @arg_attr_i8(i8 noundef signext %x)
// loongarch64: define noundef i8 @arg_attr_i8(i8 noundef signext %x)
pub fn arg_attr_i8(x: i8) -> i8 {
x
}
#[no_mangle]
// riscv64: define noundef i16 @arg_attr_i16(i16 noundef signext %x)
// loongarch64: define noundef i16 @arg_attr_i16(i16 noundef signext %x)
pub fn arg_attr_i16(x: i16) -> i16 {
x
}
#[no_mangle]
// riscv64: define noundef i32 @arg_attr_i32(i32 noundef signext %x)
// loongarch64: define noundef i32 @arg_attr_i32(i32 noundef signext %x)
pub fn arg_attr_i32(x: i32) -> i32 {
x
}
#[no_mangle]
// riscv64: define noundef i64 @arg_attr_i64(i64 noundef %x)
// loongarch64: define noundef i64 @arg_attr_i64(i64 noundef %x)
pub fn arg_attr_i64(x: i64) -> i64 {
x
}
#[no_mangle]
// riscv64: define noundef i128 @arg_attr_i128(i128 noundef %x)
// loongarch64: define noundef i128 @arg_attr_i128(i128 noundef %x)
pub fn arg_attr_i128(x: i128) -> i128 {
x
}
#[no_mangle]
// riscv64: define noundef zeroext i1 @arg_attr_bool(i1 noundef zeroext %x)
// loongarch64: define noundef zeroext i1 @arg_attr_bool(i1 noundef zeroext %x)
pub fn arg_attr_bool(x: bool) -> bool {
x
}
#[no_mangle]
// ignore-tidy-linelength
// riscv64: define noundef{{( range\(i32 0, 1114112\))?}} i32 @arg_attr_char(i32 noundef signext{{( range\(i32 0, 1114112\))?}} %x)
// loongarch64: define noundef{{( range\(i32 0, 1114112\))?}} i32 @arg_attr_char(i32 noundef signext{{( range\(i32 0, 1114112\))?}} %x)
pub fn arg_attr_char(x: char) -> char {
x
}
#[no_mangle]
// riscv64: define noundef float @arg_attr_f32(float noundef %x)
// loongarch64: define noundef float @arg_attr_f32(float noundef %x)
pub fn arg_attr_f32(x: f32) -> f32 {
x
}
#[no_mangle]
// riscv64: define noundef double @arg_attr_f64(double noundef %x)
// loongarch64: define noundef double @arg_attr_f64(double noundef %x)
pub fn arg_attr_f64(x: f64) -> f64 {
x
}

View File

@ -12,7 +12,7 @@ pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 {
// CHECK: Function Attrs: {{.*}}
// CHECK-LABEL: define{{.*}}foo{{.*}}!type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}}
// CHECK: start:
// CHECK-NEXT: {{%.+}} = call i32 %f(i32 %arg)
// CHECK-NEXT: {{%.+}} = call i32 %f(i32{{.*}} %arg)
// CHECK-NEXT: ret i32 {{%.+}}
f(arg)
}

View File

@ -11,7 +11,7 @@ pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 {
// CHECK: [[TT:%.+]] = call i1 @llvm.type.test(ptr {{%f|%0}}, metadata !"{{[[:print:]]+}}")
// CHECK-NEXT: br i1 [[TT]], label %type_test.pass, label %type_test.fail
// CHECK: type_test.pass:
// CHECK-NEXT: {{%.+}} = call i32 %f(i32 %arg)
// CHECK-NEXT: {{%.+}} = call i32 %f(i32{{.*}} %arg)
// CHECK: type_test.fail:
// CHECK-NEXT: call void @llvm.trap()
// CHECK-NEXT: unreachable

View File

@ -25,7 +25,7 @@ pub fn bool_to_byte(b: bool) -> u8 {
unsafe { std::mem::transmute(b) }
}
// CHECK-LABEL: define{{.*}}zeroext i1 @byte_to_bool(i8 %byte)
// CHECK-LABEL: define{{.*}}zeroext i1 @byte_to_bool(i8{{.*}} %byte)
// CHECK: %_0 = trunc i8 %byte to i1
// CHECK-NEXT: ret i1 %_0
#[no_mangle]

View File

@ -131,7 +131,7 @@ pub fn test_CUnionU128(_: CUnionU128) {
pub union UnionBool {
b: bool,
}
// CHECK: define {{(dso_local )?}}noundef zeroext i1 @test_UnionBool(i8 %b)
// CHECK: define {{(dso_local )?}}noundef zeroext i1 @test_UnionBool(i8{{.*}} %b)
#[no_mangle]
pub fn test_UnionBool(b: UnionBool) -> bool {
unsafe { b.b }

View File

@ -2,7 +2,7 @@
#![crate_type = "lib"]
// CHECK-LABEL: define{{.*}}i32 @test(i32 noundef %a, i32 noundef %b)
// CHECK-LABEL: define{{.*}}i32 @test(i32{{.*}} %a, i32{{.*}} %b)
#[no_mangle]
pub fn test(a: u32, b: u32) -> u32 {
let c = a + b;