// #target: linux-riscv64
// #opt: --riscvfloat=double
module test;

// Verify that the tracking of used GPRs and FPRs works correctly by checking
// that small integers are sign/zero extended when passed in registers.

// Floats are passed in FPRs, so argument 'i' will be passed zero-extended
// because it will be passed in a GPR.

fn void f_fpr_tracking(float a, float b, float c, float d, float e, float f,
                    float g, float h, char i) {}

// Check that fp, fp+fp, and int+fp structs are lowered correctly. These will
// be passed in FPR, FPR+FPR, or GPR+FPR regs if sufficient registers are
// available the widths are <= XLEN and FLEN, and should be expanded to
// separate arguments in IR. They are passed by the same rules for returns,
// but will be lowered to simple two-element structs if necessary (as LLVM IR
// functions cannot return multiple values).

// A struct containing just one floating-point real is passed as though it
// were a standalone floating-point real.

struct Float_s { float f; }

fn void f_float_s_arg(Float_s a) {}
fn Float_s f_ret_float_s() {
  return {1.0};
}


// Check that structs containing two float values (FLEN <= width) are expanded
// provided sufficient FPRs are available.

struct Float_float_s { float f; float g; }

fn void f_float_float_s_arg(Float_float_s a) {}

fn Float_float_s f_ret_float_float_s() {
  return {1.0, 2.0};
}

fn void f_float_float_s_arg_insufficient_fprs(float a, float b, float c, float d,
    float e, float f, float g, Float_float_s h) {}

// Check that structs containing int+float values are expanded, provided
// sufficient FPRs and GPRs are available. The integer components are neither
// sign or zero-extended.

struct Float_int8_s { float f; ichar i; }
struct Float_uint8_s { float f; char i; }
struct Float_int32_s { float f; int i; }
struct Float_int64_s { float f; long i; }

fn void f_float_int8_s_arg(Float_int8_s a) {}
fn Float_int8_s f_ret_float_int8_s() {
  return {1.0, 2};
}

fn void f_float_uint8_s_arg(Float_uint8_s a) {}
fn Float_uint8_s f_ret_float_uint8_s() {
  return {1.0, 2};
}

fn void f_float_int32_s_arg(Float_int32_s a) {}
fn Float_int32_s f_ret_float_int32_s() {
  return {1.0, 2};
}

fn void f_float_int64_s_arg(Float_int64_s a) {}
fn Float_int64_s f_ret_float_int64_s() {
  return {1.0, 2};
}


fn void f_float_int8_s_arg_insufficient_gprs(int a, int b, int c, int d, int e,
                                          int f, int g, int h, Float_int8_s i) {}

fn void f_struct_float_int8_insufficient_fprs(float a, float b, float c, float d,
                                           float e, float f, float g, float h, Float_int8_s i) {}

// Test single or two-element structs that need flattening. e.g. those
// containing nested structs, floats in small arrays, zero-length structs etc.

struct Floatarr1_s { float[1] a; }

fn void f_floatarr1_s_arg(Floatarr1_s a) {}
fn Floatarr1_s f_ret_floatarr1_s() {
  return {{1.0}};
}

struct Floatarr2_s { float[2] a; }
fn void f_floatarr2_s_arg(Floatarr2_s a) {}

fn Floatarr2_s f_ret_floatarr2_s() {
  return {{1.0, 2.0}};
}

struct Inner { float[1] f; }
struct Floatarr2_tricky1_s { Inner[2] g; }

fn void f_floatarr2_tricky1_s_arg(Floatarr2_tricky1_s a) {}
fn Floatarr2_tricky1_s f_ret_floatarr2_tricky1_s() {
  return {{{{1.0}}, {{2.0}}}};
}


// Test structs that should be passed according to the normal integer calling
// convention.

struct Int_float_int_s { int a; float b; int c; }

fn void f_int_float_int_s_arg(Int_float_int_s a) {}

fn Int_float_int_s f_ret_int_float_int_s() {
  return {1, 2.0, 3};
}

struct Char_char_float_s { char a; char b; float c; }

fn void f_char_char_float_s_arg(Char_char_float_s a) {}
fn Char_char_float_s f_ret_char_char_float_s() {
  return {1, 2, 3.0};
}

// Unions are always passed according to the integer calling convention, even
// if they can only contain a float.

union Float_u { float a; }

fn void f_float_u_arg(Float_u a) {}
fn Float_u f_ret_float_u() {
  return {1.0};
}

/* #expect: test.ll

define void @test.f_fpr_tracking(float %0, float %1, float %2, float %3, float %4, float %5, float %6, float %7, i8 zeroext %8) #0 {

define void @test.f_float_s_arg(float %0) #0 {
entry:
  %a = alloca %Float_s, align 4
  store float %0, ptr %a, align 4
  ret void
}


define float @test.f_ret_float_s() #0 {
entry:
  %literal = alloca %Float_s, align 4
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %literal, ptr align 4 @.__const, i32 4, i1 false)
  %0 = load float, ptr %literal, align 4
  ret float %0
}


define void @test.f_float_float_s_arg(float %0, float %1) #0 {
entry:
  %a = alloca %Float_float_s, align 4
  store float %0, ptr %a, align 4
  %ptradd = getelementptr inbounds i8, ptr %a, i64 4
  store float %1, ptr %a, align 4
  ret void
}


define { float, float } @test.f_ret_float_float_s() #0 {
entry:
  %literal = alloca %Float_float_s, align 4
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %literal, ptr align 4 @.__const.1, i32 8, i1 false)
  %0 = load float, ptr %literal, align 4
  %ptradd = getelementptr inbounds i8, ptr %literal, i64 4
  %1 = load float, ptr %literal, align 4
  %2 = insertvalue { float, float } undef, float %0, 0
  %3 = insertvalue { float, float } %2, float %1, 1
  ret { float, float } %3
}


define void @test.f_float_float_s_arg_insufficient_fprs(float %0, float %1, float %2, float %3, float %4, float %5, float %6, i64 %7) #0 {
entry:
  %h = alloca %Float_float_s, align 4
  store i64 %7, ptr %h, align 4
  ret void
}


define void @test.f_float_int8_s_arg(float %0, i8 %1) #0 {
entry:
  %a = alloca %Float_int8_s, align 4
  store float %0, ptr %a, align 4
  %ptradd = getelementptr inbounds i8, ptr %a, i64 4
  store i8 %1, ptr %a, align 4
  ret void
}


define { float, i8 } @test.f_ret_float_int8_s() #0 {
entry:
  %literal = alloca %Float_int8_s, align 4
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %literal, ptr align 4 @.__const.2, i32 8, i1 false)
  %0 = load float, ptr %literal, align 4
  %ptradd = getelementptr inbounds i8, ptr %literal, i64 4
  %1 = load i8, ptr %literal, align 4
  %2 = insertvalue { float, i8 } undef, float %0, 0
  %3 = insertvalue { float, i8 } %2, i8 %1, 1
  ret { float, i8 } %3
}


define void @test.f_float_uint8_s_arg(float %0, i8 %1) #0 {
entry:
  %a = alloca %Float_uint8_s, align 4
  store float %0, ptr %a, align 4
  %ptradd = getelementptr inbounds i8, ptr %a, i64 4
  store i8 %1, ptr %a, align 4
  ret void
}


define { float, i8 } @test.f_ret_float_uint8_s() #0 {
entry:
  %literal = alloca %Float_uint8_s, align 4
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %literal, ptr align 4 @.__const.3, i32 8, i1 false)
  %0 = load float, ptr %literal, align 4
  %ptradd = getelementptr inbounds i8, ptr %literal, i64 4
  %1 = load i8, ptr %literal, align 4
  %2 = insertvalue { float, i8 } undef, float %0, 0
  %3 = insertvalue { float, i8 } %2, i8 %1, 1
  ret { float, i8 } %3
}


define void @test.f_float_int32_s_arg(float %0, i32 %1) #0 {
entry:
  %a = alloca %Float_int32_s, align 4
  store float %0, ptr %a, align 4
  %ptradd = getelementptr inbounds i8, ptr %a, i64 4
  store i32 %1, ptr %a, align 4
  ret void
}


define { float, i32 } @test.f_ret_float_int32_s() #0 {
entry:
  %literal = alloca %Float_int32_s, align 4
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %literal, ptr align 4 @.__const.4, i32 8, i1 false)
  %0 = load float, ptr %literal, align 4
  %ptradd = getelementptr inbounds i8, ptr %literal, i64 4
  %1 = load i32, ptr %literal, align 4
  %2 = insertvalue { float, i32 } undef, float %0, 0
  %3 = insertvalue { float, i32 } %2, i32 %1, 1
  ret { float, i32 } %3
}


define void @test.f_float_int64_s_arg(float %0, i64 %1) #0 {
entry:
  %a = alloca %Float_int64_s, align 8
  store float %0, ptr %a, align 8
  %ptradd = getelementptr inbounds i8, ptr %a, i64 8
  store i64 %1, ptr %a, align 8
  ret void
}


define { float, i64 } @test.f_ret_float_int64_s() #0 {
entry:
  %literal = alloca %Float_int64_s, align 8
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %literal, ptr align 8 @.__const.5, i32 16, i1 false)
  %0 = load float, ptr %literal, align 8
  %ptradd = getelementptr inbounds i8, ptr %literal, i64 8
  %1 = load i64, ptr %literal, align 8
  %2 = insertvalue { float, i64 } undef, float %0, 0
  %3 = insertvalue { float, i64 } %2, i64 %1, 1
  ret { float, i64 } %3
}


define void @test.f_float_int8_s_arg_insufficient_gprs(i32 signext %0, i32 signext %1, i32 signext %2, i32 signext %3, i32 signext %4, i32 signext %5, i32 signext %6, i32 signext %7, i64 %8) #0 {
entry:
  %i = alloca %Float_int8_s, align 4
  store i64 %8, ptr %i, align 4
  ret void
}


define void @test.f_struct_float_int8_insufficient_fprs(float %0, float %1, float %2, float %3, float %4, float %5, float %6, float %7, i64 %8) #0 {
entry:
  %i = alloca %Float_int8_s, align 4
  store i64 %8, ptr %i, align 4
  ret void
}


define void @test.f_floatarr1_s_arg(float %0) #0 {
entry:
  %a = alloca %Floatarr1_s, align 4
  store float %0, ptr %a, align 4
  ret void
}


define float @test.f_ret_floatarr1_s() #0 {
entry:
  %literal = alloca %Floatarr1_s, align 4
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %literal, ptr align 4 @.__const.6, i32 4, i1 false)
  %0 = load float, ptr %literal, align 4
  ret float %0
}


define void @test.f_floatarr2_s_arg(float %0, float %1) #0 {
entry:
  %a = alloca %Floatarr2_s, align 4
  store float %0, ptr %a, align 4
  %ptradd = getelementptr inbounds i8, ptr %a, i64 4
  store float %1, ptr %a, align 4
  ret void
}


define { float, float } @test.f_ret_floatarr2_s() #0 {
entry:
  %literal = alloca %Floatarr2_s, align 4
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %literal, ptr align 4 @.__const.7, i32 8, i1 false)
  %0 = load float, ptr %literal, align 4
  %ptradd = getelementptr inbounds i8, ptr %literal, i64 4
  %1 = load float, ptr %literal, align 4
  %2 = insertvalue { float, float } undef, float %0, 0
  %3 = insertvalue { float, float } %2, float %1, 1
  ret { float, float } %3
}


define void @test.f_floatarr2_tricky1_s_arg(float %0, float %1) #0 {
entry:
  %a = alloca %Floatarr2_tricky1_s, align 4
  store float %0, ptr %a, align 4
  %ptradd = getelementptr inbounds i8, ptr %a, i64 4
  store float %1, ptr %a, align 4
  ret void
}


define { float, float } @test.f_ret_floatarr2_tricky1_s() #0 {
entry:
  %literal = alloca %Floatarr2_tricky1_s, align 4
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %literal, ptr align 4 @.__const.8, i32 8, i1 false)
  %0 = load float, ptr %literal, align 4
  %ptradd = getelementptr inbounds i8, ptr %literal, i64 4
  %1 = load float, ptr %literal, align 4
  %2 = insertvalue { float, float } undef, float %0, 0
  %3 = insertvalue { float, float } %2, float %1, 1
  ret { float, float } %3
}


define void @test.f_int_float_int_s_arg([2 x i64] %0) #0 {
entry:
  %a = alloca %Int_float_int_s, align 4
  %tempcoerce = alloca [2 x i64], align 8
  store [2 x i64] %0, ptr %tempcoerce, align 8
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %a, ptr align 8 %tempcoerce, i32 12, i1 false)
  ret void
}


define [2 x i64] @test.f_ret_int_float_int_s() #0 {
entry:
  %literal = alloca %Int_float_int_s, align 4
  %tempcoerce = alloca [2 x i64], align 8
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %literal, ptr align 4 @.__const.9, i32 12, i1 false)
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %tempcoerce, ptr align 4 %literal, i32 12, i1 false)
  %0 = load [2 x i64], ptr %tempcoerce, align 8
  ret [2 x i64] %0
}


define void @test.f_char_char_float_s_arg(i64 %0) #0 {
entry:
  %a = alloca %Char_char_float_s, align 4
  store i64 %0, ptr %a, align 4
  ret void
}


define i64 @test.f_ret_char_char_float_s() #0 {
entry:
  %literal = alloca %Char_char_float_s, align 4
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %literal, ptr align 4 @.__const.10, i32 8, i1 false)
  %0 = load i64, ptr %literal, align 4
  ret i64 %0
}


define void @test.f_float_u_arg(i64 %0) #0 {
entry:
  %a = alloca %Float_u, align 4
  %tempcoerce = alloca i64, align 8
  store i64 %0, ptr %tempcoerce, align 8
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %a, ptr align 8 %tempcoerce, i32 4, i1 false)
  ret void
}


define i64 @test.f_ret_float_u() #0 {
entry:
  %literal = alloca %Float_u, align 4
  %tempcoerce = alloca i64, align 8
  call void @llvm.memcpy.p0.p0.i32(ptr align 4 %literal, ptr align 4 @.__const.11, i32 4, i1 false)
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %tempcoerce, ptr align 4 %literal, i32 4, i1 false)
  %0 = load i64, ptr %tempcoerce, align 8
  ret i64 %0
}
