#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>

#define MAX_ARR 24
#define PERROR \
        printf("This test is testing mips32r2 instructions in fpu64 mode.\n");
#define FLAGS_RM_MASK 0xFFFFFFFF

typedef enum {
   CVTLS,   CVTLD,   ROUNDLS, ROUNDLD,
   TRUNCLS, TRUNCLD, FLOORLS, FLOORLD,
   CEILLS,  CEILLD
} flt_round_op_t;

const char *flt_round_op_names[] = {
   "cvt.l.s",   "cvt.l.d",   "round.l.s", "round.l.d",
   "trunc.l.s", "trunc.l.d", "floor.l.s", "floor.l.d"
   "ceil.l.s",  "ceil.l.d"
};

typedef enum {
   TO_NEAREST=0, TO_ZERO, TO_PLUS_INFINITY, TO_MINUS_INFINITY } round_mode_t;
char *round_mode_name[] = { "near", "zero", "+inf", "-inf" };

const float fs_f[] = {
   0,         456.25,   3,          -1,
   1384.5,    -7.25,    1000000000, -5786.25,
   1752,      0.015625, 0.03125,    -248562.75,
   -45786.5,  456,      34.03125,   45786.75,
   1752065,   107,      -45667.25,  -7,
   -347856.5, 356047,   -1.25,      23.0625
};

const double fs_d[] = {
   0,         456.25,   3,          -1,
   1384.5,    -7.25,    1000000000, -5786.25,
   1752,      0.015625, 0.03125,    -24856226678933.75,
   -45786.5,  456,      34.03125,   45786.75,
   1752065,   107,      -45667.25,  -7,
   -347856.5, 356047,   -1.25,      23.0625
};

#define UNOPsl(op)                \
   __asm__ __volatile__(          \
      op"   $f0, %2"     "\n\t"   \
      "sdc1 $f0, 0(%1)"  "\n\t"   \
      "cfc1 %0,  $31"    "\n\t"   \
      : "=r" (fcsr)               \
      : "r"(&fd_l), "f"(fs_f[i])  \
      : "$f0"                     \
   );

#define UNOPdl(op)                \
   __asm__ __volatile__(          \
      op"   $f0, %2"     "\n\t"   \
      "sdc1 $f0, 0(%1)"  "\n\t"   \
      "cfc1 %0,  $31"    "\n\t"   \
      : "=r" (fcsr)               \
      : "r"(&fd_l), "f"(fs_d[i])  \
      : "$f0"                     \
   );

#define TEST_FPU64                \
   __asm__ __volatile__(          \
      "cvt.l.s $f0, $f0"  "\n\t"  \
      :                           \
      :                           \
      : "$f0"                     \
   );

#if (__mips==32) && (__mips_isa_rev>=2) && (__mips_fpr==64)
void set_rounding_mode(round_mode_t mode)
{
   switch(mode) {
      case TO_NEAREST:
         __asm__ volatile("ctc1 $zero, $31"  "\n\t");
         break;
      case TO_ZERO:
         __asm__ volatile("li    $t0, 0x1"  "\n\t"
                          "ctc1  $t0, $31"  "\n\t");
         break;
      case TO_PLUS_INFINITY:
          __asm__ volatile("li    $t0, 0x2"  "\n\t"
                           "ctc1  $t0, $31"  "\n\t");
         break;
      case TO_MINUS_INFINITY:
          __asm__ volatile("li    $t0, 0x3"  "\n\t"
                           "ctc1  $t0, $31"  "\n\t");
         break;
   }
}

struct test {
   void (*test)(void);
   int sig;
   int code;
};

static void handler(int sig)
{
   PERROR;
   exit(0);
}

int FCSRRoundingMode(flt_round_op_t op)
{
   long long int fd_l;
   int i;
   int fcsr = 0;
   round_mode_t rm;
   for (rm = TO_NEAREST; rm <= TO_MINUS_INFINITY; rm ++) {
      printf("roundig mode: %s\n", round_mode_name[rm]);
      for (i = 0; i < MAX_ARR; i++) {
         set_rounding_mode(rm);
         switch(op) {
            case CVTLS:
               UNOPsl("cvt.l.s");
               printf("%s %lld %f\n",
                      flt_round_op_names[op], fd_l, fs_f[i]);
               printf("fcsr: 0x%x\n", fcsr & FLAGS_RM_MASK);
               break;
            case CVTLD:
               UNOPdl("cvt.l.d");
               printf("%s %lld %lf\n",
                      flt_round_op_names[op], fd_l, fs_d[i]);
               printf("fcsr: 0x%x\n", fcsr & FLAGS_RM_MASK);
               break;
            case ROUNDLS:
               UNOPsl("round.l.s");
               printf("%s %lld %f\n",
                      flt_round_op_names[op], fd_l, fs_f[i]);
               printf("fcsr: 0x%x\n", fcsr & FLAGS_RM_MASK);
               break;
            case ROUNDLD:
               UNOPdl("round.l.d");
               printf("%s %lld %lf\n",
                      flt_round_op_names[op], fd_l, fs_d[i]);
               printf("fcsr: 0x%x\n", fcsr & FLAGS_RM_MASK);
               break;
            case TRUNCLS:
               UNOPsl("trunc.l.s");
               printf("%s %lld %f\n",
                      flt_round_op_names[op], fd_l, fs_f[i]);
               printf("fcsr: 0x%x\n", fcsr & FLAGS_RM_MASK);
               break;
            case TRUNCLD:
               UNOPdl("trunc.l.d");
               printf("%s %lld %lf\n",
                      flt_round_op_names[op], fd_l, fs_d[i]);
               printf("fcsr: 0x%x\n", fcsr & FLAGS_RM_MASK);
               break;
            case FLOORLS:
               UNOPsl("floor.l.s");
               printf("%s %lld %f\n",
                      flt_round_op_names[op], fd_l, fs_f[i]);
               printf("fcsr: 0x%x\n", fcsr & FLAGS_RM_MASK);
               break;
            case FLOORLD:
               UNOPdl("floor.l.d");
               printf("%s %lld %lf\n",
                      flt_round_op_names[op], fd_l, fs_d[i]);
               printf("fcsr: 0x%x\n", fcsr & FLAGS_RM_MASK);
               break;
            case CEILLS:
               UNOPsl("ceil.l.s");
               printf("%s %lld %f\n",
                      flt_round_op_names[op], fd_l, fs_f[i]);
               printf("fcsr: 0x%x\n", fcsr & FLAGS_RM_MASK);
               break;
            case CEILLD:
               UNOPdl("ceil.l.d");
               printf("%s %lld %lf\n",
                      flt_round_op_names[op], fd_l, fs_d[i]);
               printf("fcsr: 0x%x\n", fcsr & FLAGS_RM_MASK);
               break;
            default:
               printf("error\n");
               break;
         }
      }
   }
   return 0;
}
#endif


int main()
{
#if (__mips==32) && (__mips_isa_rev>=2) && (__mips_fpr==64)
   flt_round_op_t op;
   signal(SIGILL, handler);
   /* Test fpu64 mode. */
   TEST_FPU64;
   printf("-------------------------- %s --------------------------\n",
          "test FPU Conversion Operations Using the FCSR Rounding Mode");
   for (op = CVTLS; op <= CEILLD; op++)
      FCSRRoundingMode(op);
#else
   PERROR;
#endif
   return 0;
}