#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include "opcodes.h"

/* Test "convert to fixed"  with "per fpc" rounding.
   Covers all generally available rounding modes.

   Note, that "convert to fixed" with rounding mode specified as
   part of the insn is already tested in fpconv.c
*/

void
set_rounding_mode(unsigned mode)
{
   register unsigned r asm("1") = mode;
   __asm__ volatile ( SFPC(1) : : "d"(r) );
}

unsigned
get_rounding_mode(void)
{
   unsigned fpc;

   __asm__ volatile ("stfpc  %0\n\t" : "=m"(fpc));

   return fpc & 0x7;
}


const char *
rtext(unsigned fpc_round)
{
   switch (fpc_round) {
   case 0: return "[-> near]";
   case 1: return "[-> zero]";
   case 2: return "[-> +inf]";
   case 3: return "[-> -inf]";
   }
   assert(0);
}

#define convert_to_int(opcode,src_type,dst_type,dst_fmt,round,value) \
do { \
   src_type src = value; \
   dst_type dst;         \
   unsigned cc;          \
                         \
   __asm__ volatile (opcode " %[dst]," #round ",%[src]\n\t"     \
                     "ipm %[cc]\n\t"                  \
                     "srl %[cc],28\n\t"               \
                     : [dst] "=d"(dst), [cc] "=d"(cc) \
                     : [src] "f"(src)                 \
                     : "cc");                         \
                                                      \
   printf("%s %f\t-> %"dst_fmt"\tcc = %u\n",    \
          opcode, src, dst, cc);        \
} while (0)


#define cfebr(value) \
        convert_to_int("cfebr",float,int32_t,PRId32,0,value)
#define cfdbr(value) \
        convert_to_int("cfdbr",double,int32_t,PRId32,0,value)
#define cgebr(value) \
        convert_to_int("cgebr",float,int64_t,PRId64,0,value)
#define cgdbr(value) \
        convert_to_int("cgdbr",double,int64_t,PRId64,0,value)

int main(void)
{
   int i, j;
   static const unsigned rmodes[] = { 0, 1, 2, 3 };
   static const float fval[] = {
      1.4f, 1.5f, 2.5f, 1.6f, -1.4f, -1.5f, -2.5f, -1.6f, 0.0f,
   };
   static const double dval[] = {
      1.4, 1.5, 2.5, 1.6, -1.4, -1.5, -2.5, -1.6, 0.0,
   };


   for (i = 0; i < sizeof rmodes / sizeof rmodes[0]; ++i) {
      printf("setting rounding mode to %s\n", rtext(rmodes[i]));
      set_rounding_mode(rmodes[i]);
      assert(get_rounding_mode() == rmodes[i]);

      /* f32 -> i32 */
      for (j = 0; j < sizeof fval / sizeof fval[0]; ++j) {
         cfebr(fval[j]);
         assert(get_rounding_mode() == rmodes[i]);
      }

      /* f32 -> i64 */
      for (j = 0; j < sizeof fval / sizeof fval[0]; ++j) {
         cgebr(fval[j]);
         assert(get_rounding_mode() == rmodes[i]);
      }

      /* f64 -> i32 */
      for (j = 0; j < sizeof dval / sizeof dval[0]; ++j) {
         cfdbr(dval[j]);
         assert(get_rounding_mode() == rmodes[i]);
      }

      /* f64 -> i64 */
      for (j = 0; j < sizeof dval / sizeof dval[0]; ++j) {
         cgdbr(dval[j]);
         assert(get_rounding_mode() == rmodes[i]);
      }

   }

   return 0;
}
