bolt/deps/llvm-18.1.8/clang/test/CodeGen/LoongArch/abi-lp64d.c
2025-02-14 19:21:04 +01:00

488 lines
16 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// RUN: %clang_cc1 -triple loongarch64 -target-feature +f -target-feature +d -target-abi lp64d \
// RUN: -emit-llvm %s -o - | FileCheck %s
/// This test checks the calling convention of the lp64d ABI.
#include <stddef.h>
#include <stdint.h>
/// Part 0: C Data Types and Alignment.
/// `char` datatype is signed by default.
/// In most cases, the unsigned integer data types are zero-extended when stored
/// in general-purpose register, and the signed integer data types are
/// sign-extended. However, in the LP64D ABI, unsigned 32-bit types, such as
/// unsigned int, are stored in general-purpose registers as proper sign
/// extensions of their 32-bit values.
// CHECK-LABEL: define{{.*}} zeroext i1 @check_bool()
_Bool check_bool() { return 0; }
// CHECK-LABEL: define{{.*}} signext i8 @check_char()
char check_char() { return 0; }
// CHECK-LABEL: define{{.*}} signext i16 @check_short()
short check_short() { return 0; }
// CHECK-LABEL: define{{.*}} signext i32 @check_int()
int check_int() { return 0; }
// CHECK-LABEL: define{{.*}} i64 @check_long()
long check_long() { return 0; }
// CHECK-LABEL: define{{.*}} i64 @check_longlong()
long long check_longlong() { return 0; }
// CHECK-LABEL: define{{.*}} zeroext i8 @check_uchar()
unsigned char check_uchar() { return 0; }
// CHECK-LABEL: define{{.*}} zeroext i16 @check_ushort()
unsigned short check_ushort() { return 0; }
// CHECK-LABEL: define{{.*}} signext i32 @check_uint()
unsigned int check_uint() { return 0; }
// CHECK-LABEL: define{{.*}} i64 @check_ulong()
unsigned long check_ulong() { return 0; }
// CHECK-LABEL: define{{.*}} i64 @check_ulonglong()
unsigned long long check_ulonglong() { return 0; }
// CHECK-LABEL: define{{.*}} float @check_float()
float check_float() { return 0; }
// CHECK-LABEL: define{{.*}} double @check_double()
double check_double() { return 0; }
// CHECK-LABEL: define{{.*}} fp128 @check_longdouble()
long double check_longdouble() { return 0; }
/// Part 1: Scalar arguments and return value.
/// 1. 1 < WOA <= GRLEN
/// a. Argument is passed in a single argument register, or on the stack by
/// value if none is available.
/// i. If the argument is floating-point type, the argument is passed in FAR. if
/// no FAR is available, its passed in GAR. If no GAR is available, its
/// passed on the stack. When passed in registers or on the stack,
/// floating-point types narrower than GRLEN bits are widened to GRLEN bits,
/// with the upper bits undefined.
/// ii. If the argument is integer or pointer type, the argument is passed in
/// GAR. If no GAR is available, its passed on the stack. When passed in
/// registers or on the stack, the unsigned integer scalars narrower than GRLEN
/// bits are zero-extended to GRLEN bits, and the signed integer scalars are
/// sign-extended.
/// 2. GRLEN < WOA ≤ 2 × GRLEN
/// a. The argument is passed in a pair of GAR, with the low-order GRLEN bits in
/// the lower-numbered register and the high-order GRLEN bits in the
/// higher-numbered register. If exactly one register is available, the
/// low-order GRLEN bits are passed in the register and the high-order GRLEN
/// bits are passed on the stack. If no GAR is available, its passed on the
/// stack.
/// Note that most of these conventions are handled by the backend, so here we
/// only check the correctness of argument (or return value)'s sign/zero
/// extension attribute.
// CHECK-LABEL: define{{.*}} signext i32 @f_scalar(i1 noundef zeroext %a, i8 noundef signext %b, i8 noundef zeroext %c, i16 noundef signext %d, i16 noundef zeroext %e, i32 noundef signext %f, i32 noundef signext %g, i64 noundef %h, i1 noundef zeroext %i, i8 noundef signext %j, i8 noundef zeroext %k, i16 noundef signext %l, i16 noundef zeroext %m, i32 noundef signext %n, i32 noundef signext %o, i64 noundef %p)
int f_scalar(_Bool a, int8_t b, uint8_t c, int16_t d, uint16_t e, int32_t f,
uint32_t g, int64_t h, _Bool i, int8_t j, uint8_t k, int16_t l,
uint16_t m, int32_t n, uint32_t o, int64_t p) {
return 0;
}
/// Part 2: Structure arguments and return value.
/// Empty structures are ignored by C compilers which support them as a
/// non-standard extension(same as union arguments and return values). Bits
/// unused due to padding, and bits past the end of a structure whose size in
/// bits is not divisible by GRLEN, are undefined. And the layout of the
/// structure on the stack is consistent with that in memory.
/// Check empty structs are ignored.
struct empty_s {};
// CHECK-LABEL: define{{.*}} void @f_empty_s()
struct empty_s f_empty_s(struct empty_s x) {
return x;
}
/// 1. 0 < WOA ≤ GRLEN
/// a. The structure has only fixed-point members. If there is an available GAR,
/// the structure is passed through the GAR by value passing; If no GAR is
/// available, its passed on the stack.
struct i16x4_s {
int16_t a, b, c, d;
};
// CHECK-LABEL: define{{.*}} i64 @f_i16x4_s(i64 %x.coerce)
struct i16x4_s f_i16x4_s(struct i16x4_s x) {
return x;
}
/// b. The structure has only floating-point members:
/// i. One floating-point member. The argument is passed in a FAR; If no FAR is
/// available, the value is passed in a GAR; if no GAR is available, the value
/// is passed on the stack.
struct f32x1_s {
float a;
};
struct f64x1_s {
double a;
};
// CHECK-LABEL: define{{.*}} float @f_f32x1_s(float %0)
struct f32x1_s f_f32x1_s(struct f32x1_s x) {
return x;
}
// CHECK-LABEL: define{{.*}} double @f_f64x1_s(double %0)
struct f64x1_s f_f64x1_s(struct f64x1_s x) {
return x;
}
/// ii. Two floating-point members. The argument is passed in a pair of
/// available FAR, with the low-order float member bits in the lower-numbered
/// FAR and the high-order float member bits in the higher-numbered FAR. If the
/// number of available FAR is less than 2, its passed in a GAR, and passed on
/// the stack if no GAR is available.
struct f32x2_s {
float a, b;
};
// CHECK-LABEL: define{{.*}} { float, float } @f_f32x2_s(float %0, float %1)
struct f32x2_s f_f32x2_s(struct f32x2_s x) {
return x;
}
/// c. The structure has both fixed-point and floating-point members, i.e. the
/// structure has one float member and...
/// i. Multiple fixed-point members. If there are available GAR, the structure
/// is passed in a GAR, and passed on the stack if no GAR is available.
struct f32x1_i16x2_s {
float a;
int16_t b, c;
};
// CHECK-LABEL: define{{.*}} i64 @f_f32x1_i16x2_s(i64 %x.coerce)
struct f32x1_i16x2_s f_f32x1_i16x2_s(struct f32x1_i16x2_s x) {
return x;
}
/// ii. Only one fixed-point member. If one FAR and one GAR are available, the
/// floating-point member of the structure is passed in the FAR, and the integer
/// member of the structure is passed in the GAR; If no floating-point register
/// but one GAR is available, its passed in GAR; If no GAR is available, its
/// passed on the stack.
struct f32x1_i32x1_s {
float a;
int32_t b;
};
// CHECK-LABEL: define{{.*}} { float, i32 } @f_f32x1_i32x1_s(float %0, i32 %1)
struct f32x1_i32x1_s f_f32x1_i32x1_s(struct f32x1_i32x1_s x) {
return x;
}
/// 2. GRLEN < WOA ≤ 2 × GRLEN
/// a. Only fixed-point members.
/// i. The argument is passed in a pair of available GAR, with the low-order
/// bits in the lower-numbered GAR and the high-order bits in the
/// higher-numbered GAR. If only one GAR is available, the low-order bits are in
/// the GAR and the high-order bits are on the stack, and passed on the stack if
/// no GAR is available.
struct i64x2_s {
int64_t a, b;
};
// CHECK-LABEL: define{{.*}} [2 x i64] @f_i64x2_s([2 x i64] %x.coerce)
struct i64x2_s f_i64x2_s(struct i64x2_s x) {
return x;
}
/// b. Only floating-point members.
/// i. The structure has one long double member or one double member and two
/// adjacent float members or 3-4 float members. The argument is passed in a
/// pair of available GAR, with the low-order bits in the lower-numbered GAR and
/// the high-order bits in the higher-numbered GAR. If only one GAR is
/// available, the low-order bits are in the GAR and the high-order bits are on
/// the stack, and passed on the stack if no GAR is available.
struct f128x1_s {
long double a;
};
// CHECK-LABEL: define{{.*}} i128 @f_f128x1_s(i128 %x.coerce)
struct f128x1_s f_f128x1_s(struct f128x1_s x) {
return x;
}
struct f64x1_f32x2_s {
double a;
float b, c;
};
// CHECK-LABEL: define{{.*}} [2 x i64] @f_f64x1_f32x2_s([2 x i64] %x.coerce)
struct f64x1_f32x2_s f_f64x1_f32x2_s(struct f64x1_f32x2_s x) {
return x;
}
struct f32x3_s {
float a, b, c;
};
// CHECK-LABEL: define{{.*}} [2 x i64] @f_f32x3_s([2 x i64] %x.coerce)
struct f32x3_s f_f32x3_s(struct f32x3_s x) {
return x;
}
struct f32x4_s {
float a, b, c, d;
};
// CHECK-LABEL: define{{.*}} [2 x i64] @f_f32x4_s([2 x i64] %x.coerce)
struct f32x4_s f_f32x4_s(struct f32x4_s x) {
return x;
}
/// ii. The structure with two double members is passed in a pair of available
/// FARs. If no a pair of available FARs, its passed in GARs. A structure with
/// one double member and one float member is same.
struct f64x2_s {
double a, b;
};
// CHECK-LABEL: define{{.*}} { double, double } @f_f64x2_s(double %0, double %1)
struct f64x2_s f_f64x2_s(struct f64x2_s x) {
return x;
}
/// c. Both fixed-point and floating-point members.
/// i. The structure has one double member and only one fixed-point member.
/// A. If one FAR and one GAR are available, the floating-point member of the
/// structure is passed in the FAR, and the integer member of the structure is
/// passed in the GAR; If no floating-point registers but two GARs are
/// available, its passed in the two GARs; If only one GAR is available, the
/// low-order bits are in the GAR and the high-order bits are on the stack; And
/// its passed on the stack if no GAR is available.
struct f64x1_i64x1_s {
double a;
int64_t b;
};
// CHECK-LABEL: define{{.*}} { double, i64 } @f_f64x1_i64x1_s(double %0, i64 %1)
struct f64x1_i64x1_s f_f64x1_i64x1_s(struct f64x1_i64x1_s x) {
return x;
}
/// ii. Others
/// A. The argument is passed in a pair of available GAR, with the low-order
/// bits in the lower-numbered GAR and the high-order bits in the
/// higher-numbered GAR. If only one GAR is available, the low-order bits are in
/// the GAR and the high-order bits are on the stack, and passed on the stack if
/// no GAR is available.
struct f64x1_i32x2_s {
double a;
int32_t b, c;
};
// CHECK-LABEL: define{{.*}} [2 x i64] @f_f64x1_i32x2_s([2 x i64] %x.coerce)
struct f64x1_i32x2_s f_f64x1_i32x2_s(struct f64x1_i32x2_s x) {
return x;
}
struct f32x2_i32x2_s {
float a, b;
int32_t c, d;
};
// CHECK-LABEL: define{{.*}} [2 x i64] @f_f32x2_i32x2_s([2 x i64] %x.coerce)
struct f32x2_i32x2_s f_f32x2_i32x2_s(struct f32x2_i32x2_s x) {
return x;
}
/// 3. WOA > 2 × GRLEN
/// a. Its passed by reference and are replaced in the argument list with the
/// address. If there is an available GAR, the reference is passed in the GAR,
/// and passed on the stack if no GAR is available.
struct i64x4_s {
int64_t a, b, c, d;
};
// CHECK-LABEL: define{{.*}} void @f_i64x4_s(ptr{{.*}} sret(%struct.i64x4_s) align 8 %agg.result, ptr{{.*}} %x)
struct i64x4_s f_i64x4_s(struct i64x4_s x) {
return x;
}
struct f64x4_s {
double a, b, c, d;
};
// CHECK-LABEL: define{{.*}} void @f_f64x4_s(ptr{{.*}} sret(%struct.f64x4_s) align 8 %agg.result, ptr{{.*}} %x)
struct f64x4_s f_f64x4_s(struct f64x4_s x) {
return x;
}
/// Part 3: Union arguments and return value.
/// Check empty unions are ignored.
union empty_u {};
// CHECK-LABEL: define{{.*}} void @f_empty_u()
union empty_u f_empty_u(union empty_u x) {
return x;
}
/// Union is passed in GAR or stack.
/// 1. 0 < WOA ≤ GRLEN
/// a. The argument is passed in a GAR, or on the stack by value if no GAR is
/// available.
union i32_f32_u {
int32_t a;
float b;
};
// CHECK-LABEL: define{{.*}} i64 @f_i32_f32_u(i64 %x.coerce)
union i32_f32_u f_i32_f32_u(union i32_f32_u x) {
return x;
}
union i64_f64_u {
int64_t a;
double b;
};
// CHECK-LABEL: define{{.*}} i64 @f_i64_f64_u(i64 %x.coerce)
union i64_f64_u f_i64_f64_u(union i64_f64_u x) {
return x;
}
/// 2. GRLEN < WOA ≤ 2 × GRLEN
/// a. The argument is passed in a pair of available GAR, with the low-order
/// bits in the lower-numbered GAR and the high-order bits in the
/// higher-numbered GAR. If only one GAR is available, the low-order bits are in
/// the GAR and the high-order bits are on the stack. The arguments are passed
/// on the stack when no GAR is available.
union i128_f128_u {
__int128_t a;
long double b;
};
// CHECK-LABEL: define{{.*}} i128 @f_i128_f128_u(i128 %x.coerce)
union i128_f128_u f_i128_f128_u(union i128_f128_u x) {
return x;
}
/// 3. WOA > 2 × GRLEN
/// a. Its passed by reference and are replaced in the argument list with the
/// address. If there is an available GAR, the reference is passed in the GAR,
/// and passed on the stack if no GAR is available.
union i64_arr3_u {
int64_t a[3];
};
// CHECK-LABEL: define{{.*}} void @f_i64_arr3_u(ptr{{.*}} sret(%union.i64_arr3_u) align 8 %agg.result, ptr{{.*}} %x)
union i64_arr3_u f_i64_arr3_u(union i64_arr3_u x) {
return x;
}
/// Part 4: Complex number arguments and return value.
/// A complex floating-point number, or a structure containing just one complex
/// floating-point number, is passed as though it were a structure containing
/// two floating-point reals.
// CHECK-LABEL: define{{.*}} { float, float } @f_floatcomplex(float noundef %x.coerce0, float noundef %x.coerce1)
float __complex__ f_floatcomplex(float __complex__ x) { return x; }
// CHECK-LABEL: define{{.*}} { double, double } @f_doublecomplex(double noundef %x.coerce0, double noundef %x.coerce1)
double __complex__ f_doublecomplex(double __complex__ x) { return x; }
struct floatcomplex_s {
float __complex__ c;
};
// CHECK-LABEL: define{{.*}} { float, float } @f_floatcomplex_s(float %0, float %1)
struct floatcomplex_s f_floatcomplex_s(struct floatcomplex_s x) {
return x;
}
struct doublecomplex_s {
double __complex__ c;
};
// CHECK-LABEL: define{{.*}} { double, double } @f_doublecomplex_s(double %0, double %1)
struct doublecomplex_s f_doublecomplex_s(struct doublecomplex_s x) {
return x;
}
/// Part 5: Variadic arguments.
/// Variadic arguments are passed in GARs in the same manner as named arguments.
int f_va_callee(int, ...);
// CHECK-LABEL: define{{.*}} void @f_va_caller()
// CHECK: call signext i32 (i32, ...) @f_va_callee(i32 noundef signext 1, i32 noundef signext 2, i64 noundef 3, double noundef 4.000000e+00, double noundef 5.000000e+00, i64 {{.*}}, [2 x i64] {{.*}})
void f_va_caller(void) {
f_va_callee(1, 2, 3LL, 4.0f, 5.0, (struct i16x4_s){6, 7, 8, 9},
(struct i64x2_s){10, 11});
}
// CHECK-LABEL: @f_va_int(
// CHECK-NEXT: entry:
// CHECK-NEXT: [[FMT_ADDR:%.*]] = alloca ptr, align 8
// CHECK-NEXT: [[VA:%.*]] = alloca ptr, align 8
// CHECK-NEXT: [[V:%.*]] = alloca i32, align 4
// CHECK-NEXT: store ptr [[FMT:%.*]], ptr [[FMT_ADDR]], align 8
// CHECK-NEXT: call void @llvm.va_start(ptr [[VA]])
// CHECK-NEXT: [[ARGP_CUR:%.*]] = load ptr, ptr [[VA]], align 8
// CHECK-NEXT: [[ARGP_NEXT:%.*]] = getelementptr inbounds i8, ptr [[ARGP_CUR]], i64 8
// CHECK-NEXT: store ptr [[ARGP_NEXT]], ptr [[VA]], align 8
// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[ARGP_CUR]], align 8
// CHECK-NEXT: store i32 [[TMP0]], ptr [[V]], align 4
// CHECK-NEXT: call void @llvm.va_end(ptr [[VA]])
// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[V]], align 4
// CHECK-NEXT: ret i32 [[TMP1]]
int f_va_int(char *fmt, ...) {
__builtin_va_list va;
__builtin_va_start(va, fmt);
int v = __builtin_va_arg(va, int);
__builtin_va_end(va);
return v;
}
/// Part 6. Structures with zero size fields (bitfields or arrays).
/// Check that zero size fields in structure are ignored.
/// Note that this rule is not explicitly documented in ABI spec but it matches
/// GCC's behavior.
struct f64x2_zsfs_s {
double a;
int : 0;
__int128_t : 0;
int b[0];
__int128_t c[0];
double d;
};
// CHECK-LABEL: define{{.*}} { double, double } @f_f64x2_zsfs_s(double %0, double %1)
struct f64x2_zsfs_s f_f64x2_zsfs_s(struct f64x2_zsfs_s x) {
return x;
}