// #target: linux-riscv32
// #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.

// Doubles 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(double a, double b, double c, double d, double e, double f,
                    double g, double 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 Double_s { double f; }

fn void f_double_s_arg(Double_s a) {}

fn Double_s f_ret_double_s() {
  return {1.0};
}

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

struct Double_double_s { double f; double g; }
struct Double_float_s { double f; float g; }

fn void f_double_double_s_arg(Double_double_s a) {}

fn Double_double_s f_ret_double_double_s() {
  return {1.0, 2.0};
}

fn void f_double_float_s_arg(Double_float_s a) {}

fn Double_float_s f_ret_double_float_s() {
  return {1.0, 2.0};
}

fn void f_double_double_s_arg_insufficient_fprs(float a, double b, double c, double d,
    double e, double f, double g, Double_double_s h) {}

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

struct Double_int8_s { double f; ichar i; }
struct Double_uint8_s { double f; char i; }
struct Double_int32_s { double f; int i; }
struct Double_int64_s { double f; long i; }

fn void f_double_int8_s_arg(Double_int8_s a) {}

fn Double_int8_s f_ret_double_int8_s() {
  return {1.0, 2};
}

fn void f_double_uint8_s_arg(Double_uint8_s a) {}

fn Double_uint8_s f_ret_double_uint8_s() {
  return {1.0, 2};
}

fn void f_double_int32_s_arg(Double_int32_s a) {}

fn Double_int32_s f_ret_double_int32_s() {
  return {1.0, 2};
}

fn void f_double_int64_s_arg(Double_int64_s a) {}

fn Double_int64_s f_ret_double_int64_s() {
  return {1.0, 2};
}



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

fn void f_struct_double_int8_insufficient_fprs(float a, double b, double c, double d,
                                           double e, double f, double g, double h, Double_int8_s i) {}

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

struct Doublearr1_s { double[1] a; }

fn void f_doublearr1_s_arg(Doublearr1_s a) {}

fn Doublearr1_s f_ret_doublearr1_s() {
  return {{1.0}};
}

struct Doublearr2_s { double[2] a; }

fn void f_doublearr2_s_arg(Doublearr2_s a) {}

fn Doublearr2_s f_ret_doublearr2_s() {
  return {{1.0, 2.0}};
}

struct Inner { double[1] f; }
struct Doublearr2_tricky1_s { Inner[2] g; }

fn void f_doublearr2_tricky1_s_arg(Doublearr2_tricky1_s a) {}

fn Doublearr2_tricky1_s f_ret_doublearr2_tricky1_s() {
  return {{{{1.0}}, {{2.0}}}};
}

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

struct Int_double_int_s { int a; double b; int c; }

fn void f_int_double_int_s_arg(Int_double_int_s a) {}

fn Int_double_int_s f_ret_int_double_int_s() {
  return {1, 2.0, 3};
}

struct Int64_double_s { long a; double b; }

fn void f_int64_double_s_arg(Int64_double_s a) {}

fn Int64_double_s f_ret_int64_double_s() {
  return {1, 2.0};
}

struct Char_char_double_s { ichar a; ichar b; double c; }

fn void f_char_char_double_s_arg(Char_char_double_s a) {}

fn Char_char_double_s f_ret_char_char_double_s() {
  return {1, 2, 3.0};
}

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

union Double_u { double a; }

fn void f_double_u_arg(Double_u a) {}

fn Double_u f_ret_double_u() {
  return {1.0};
}

// Test that we don't incorrectly think double+int/double+double structs will
// be returned indirectly and thus have an off-by-one error for the number of
// GPRs available (this is an edge case when structs > 2*XLEN are still
// returned in registers). This includes complex doubles, which are treated as
// double+double structs by the ABI.

fn Double_int32_s f_ret_double_int32_s_double_int32_s_just_sufficient_gprs(
    int a, int b, int c, int d, int e, int f, int g, Double_int32_s h) {
  return {1.0, 2};
}

fn Double_double_s f_ret_double_double_s_double_int32_s_just_sufficient_gprs(
    int a, int b, int c, int d, int e, int f, int g, Double_int32_s h) {
  return {1.0, 2.0};
}


/* #expect: test.ll

define void @test.f_fpr_tracking(double %0, double %1, double %2, double %3, double %4, double %5, double %6, double %7, i8 zeroext %8) #0 {
entry:
  ret void
}


define void @test.f_double_s_arg(double %0) #0 {
entry:
  %a = alloca %Double_s, align 8
  store double %0, ptr %a, align 8
  ret void
}


define double @test.f_ret_double_s() #0 {
entry:
  %literal = alloca %Double_s, align 8
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %literal, ptr align 8 @.__const, i32 8, i1 false)
  %0 = load double, ptr %literal, align 8
  ret double %0
}


define void @test.f_double_double_s_arg(double %0, double %1) #0 {
entry:
  %a = alloca %Double_double_s, align 8
  store double %0, ptr %a, align 8
  %ptradd = getelementptr inbounds i8, ptr %a, i32 8
  store double %1, ptr %a, align 8
  ret void
}


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


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


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


define void @test.f_double_double_s_arg_insufficient_fprs(float %0, double %1, double %2, double %3, double %4, double %5, double %6, ptr align 8 %7) #0 {
entry:
  ret void
}


define void @test.f_double_int8_s_arg(double %0, i8 %1) #0 {
entry:
  %a = alloca %Double_int8_s, align 8
  store double %0, ptr %a, align 8
  %ptradd = getelementptr inbounds i8, ptr %a, i32 8
  store i8 %1, ptr %a, align 8
  ret void
}


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


define void @test.f_double_uint8_s_arg(double %0, i8 %1) #0 {
entry:
  %a = alloca %Double_uint8_s, align 8
  store double %0, ptr %a, align 8
  %ptradd = getelementptr inbounds i8, ptr %a, i32 8
  store i8 %1, ptr %a, align 8
  ret void
}


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


define void @test.f_double_int32_s_arg(double %0, i32 %1) #0 {
entry:
  %a = alloca %Double_int32_s, align 8
  store double %0, ptr %a, align 8
  %ptradd = getelementptr inbounds i8, ptr %a, i32 8
  store i32 %1, ptr %a, align 8
  ret void
}


define { double, i32 } @test.f_ret_double_int32_s() #0 {
entry:
  %literal = alloca %Double_int32_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 double, ptr %literal, align 8
  %ptradd = getelementptr inbounds i8, ptr %literal, i32 8
  %1 = load i32, ptr %literal, align 8
  %2 = insertvalue { double, i32 } undef, double %0, 0
  %3 = insertvalue { double, i32 } %2, i32 %1, 1
  ret { double, i32 } %3
}


define void @test.f_double_int64_s_arg(ptr align 8 %0) #0 {
entry:
  ret void
}


define void @test.f_ret_double_int64_s(ptr noalias sret(%Double_int64_s) align 8 %0) #0 {
entry:
  %literal = alloca %Double_int64_s, align 8
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %literal, ptr align 8 @.__const.6, i32 16, i1 false)
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %0, ptr align 8 %literal, i32 16, i1 false)
  ret void
}


define void @test.f_double_int8_s_arg_insufficient_gprs(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4, i32 %5, i32 %6, i32 %7, ptr align 8 %8) #0 {
entry:
  ret void
}


define void @test.f_struct_double_int8_insufficient_fprs(float %0, double %1, double %2, double %3, double %4, double %5, double %6, double %7, ptr align 8 %8) #0 {
entry:
  ret void
}


define void @test.f_doublearr1_s_arg(double %0) #0 {
entry:
  %a = alloca %Doublearr1_s, align 8
  store double %0, ptr %a, align 8
  ret void
}


define double @test.f_ret_doublearr1_s() #0 {
entry:
  %literal = alloca %Doublearr1_s, align 8
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %literal, ptr align 8 @.__const.7, i32 8, i1 false)
  %0 = load double, ptr %literal, align 8
  ret double %0
}


define void @test.f_doublearr2_s_arg(double %0, double %1) #0 {
entry:
  %a = alloca %Doublearr2_s, align 8
  store double %0, ptr %a, align 8
  %ptradd = getelementptr inbounds i8, ptr %a, i32 8
  store double %1, ptr %a, align 8
  ret void
}


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


define void @test.f_doublearr2_tricky1_s_arg(double %0, double %1) #0 {
entry:
  %a = alloca %Doublearr2_tricky1_s, align 8
  store double %0, ptr %a, align 8
  %ptradd = getelementptr inbounds i8, ptr %a, i32 8
  store double %1, ptr %a, align 8
  ret void
}


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


define void @test.f_int_double_int_s_arg(ptr align 8 %0) #0 {
entry:
  ret void
}


define void @test.f_ret_int_double_int_s(ptr noalias sret(%Int_double_int_s) align 8 %0) #0 {
entry:
  %literal = alloca %Int_double_int_s, align 8
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %literal, ptr align 8 @.__const.10, i32 24, i1 false)
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %0, ptr align 8 %literal, i32 24, i1 false)
  ret void
}


define void @test.f_int64_double_s_arg(ptr align 8 %0) #0 {
entry:
  ret void
}


define void @test.f_ret_int64_double_s(ptr noalias sret(%Int64_double_s) align 8 %0) #0 {
entry:
  %literal = alloca %Int64_double_s, align 8
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %literal, ptr align 8 @.__const.11, i32 16, i1 false)
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %0, ptr align 8 %literal, i32 16, i1 false)
  ret void
}


define void @test.f_char_char_double_s_arg(ptr align 8 %0) #0 {
entry:
  ret void
}


define void @test.f_ret_char_char_double_s(ptr noalias sret(%Char_char_double_s) align 8 %0) #0 {
entry:
  %literal = alloca %Char_char_double_s, align 8
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %literal, ptr align 8 @.__const.12, i32 16, i1 false)
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %0, ptr align 8 %literal, i32 16, i1 false)
  ret void
}


define void @test.f_double_u_arg(i64 %0) #0 {
entry:
  %a = alloca %Double_u, align 8
  store i64 %0, ptr %a, align 8
  ret void
}


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


define { double, i32 } @test.f_ret_double_int32_s_double_int32_s_just_sufficient_gprs(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4, i32 %5, i32 %6, double %7, i32 %8) #0 {
entry:
  %h = alloca %Double_int32_s, align 8
  %literal = alloca %Double_int32_s, align 8
  store double %7, ptr %h, align 8
  %ptradd = getelementptr inbounds i8, ptr %h, i32 8
  store i32 %8, ptr %h, align 8
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %literal, ptr align 8 @.__const.14, i32 16, i1 false)
  %9 = load double, ptr %literal, align 8
  %ptradd1 = getelementptr inbounds i8, ptr %literal, i32 8
  %10 = load i32, ptr %literal, align 8
  %11 = insertvalue { double, i32 } undef, double %9, 0
  %12 = insertvalue { double, i32 } %11, i32 %10, 1
  ret { double, i32 } %12
}


define { double, double } @test.f_ret_double_double_s_double_int32_s_just_sufficient_gprs(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4, i32 %5, i32 %6, double %7, i32 %8) #0 {
entry:
  %h = alloca %Double_int32_s, align 8
  %literal = alloca %Double_double_s, align 8
  store double %7, ptr %h, align 8
  %ptradd = getelementptr inbounds i8, ptr %h, i32 8
  store i32 %8, ptr %h, align 8
  call void @llvm.memcpy.p0.p0.i32(ptr align 8 %literal, ptr align 8 @.__const.15, i32 16, i1 false)
  %9 = load double, ptr %literal, align 8
  %ptradd1 = getelementptr inbounds i8, ptr %literal, i32 8
  %10 = load double, ptr %literal, align 8
  %11 = insertvalue { double, double } undef, double %9, 0
  %12 = insertvalue { double, double } %11, double %10, 1
  ret { double, double } %12
}
