#include <u.h>
#include <libc.h>
#include <bio.h>
#include <mach.h>
#define Extern extern
#include "power.h"

ulong   setfpscr(void);
void    setfpcc(double);
void    farith(ulong);
void    farith2(ulong);
void    fariths(ulong);
void    fcmp(ulong);
void    mtfsb1(ulong);
void    mcrfs(ulong);
void    mtfsb0(ulong);
void    mtfsf(ulong);
void    mtfsfi(ulong);
void    mffs(ulong);
void    mtfsf(ulong);

Inst    op59[] = {
[18] {fariths, "fdivs", Ifloat},
[20] {fariths, "fsubs", Ifloat},
[21] {fariths, "fadds", Ifloat},
[22] {unimp, "fsqrts", Ifloat},
[24] {unimp, "fres", Ifloat},
[25] {fariths, "fmuls", Ifloat},
[28] {fariths, "fmsubs", Ifloat},
[29] {fariths, "fmadds", Ifloat},
[30] {fariths, "fnmsubs", Ifloat},
[31] {fariths, "fnmadds", Ifloat},
};

Inset   ops59 = {op59, nelem(op59)};

Inst    op63a[] = {
[12] {farith, "frsp", Ifloat},
[14] {farith, "fctiw", Ifloat},
[15] {farith, "fctiwz", Ifloat},
[18] {farith, "fdiv", Ifloat},
[20] {farith, "fsub", Ifloat},
[21] {farith, "fadd", Ifloat},
[22] {unimp, "frsqrt", Ifloat},
[23] {unimp, "fsel", Ifloat},
[25] {farith, "fmul", Ifloat},
[26] {unimp, "frsqrte", Ifloat},
[28] {farith, "fmsub", Ifloat},
[29] {farith, "fmadd", Ifloat},
[30] {farith, "fnmsub", Ifloat},
[31] {farith, "fnmadd", Ifloat},
};

Inset   ops63a= {op63a, nelem(op63a)};

Inst    op63b[] = {
[0] {fcmp, "fcmpu", Ifloat},
[32] {fcmp, "fcmpo", Ifloat},
[38] {mtfsb1, "mtfsb1", Ifloat},
[40] {farith2, "fneg", Ifloat},
[64] {mcrfs, "mcrfs", Ifloat},
[70] {mtfsb0, "mtfsb0", Ifloat},
[72] {farith2, "fmr", Ifloat},
[134] {mtfsfi, "mtfsfi", Ifloat},
[136] {farith2, "fnabs", Ifloat},
[264] {farith2, "fabs", Ifloat},
[583] {mffs, "mffs", Ifloat},
[711] {mtfsf, "mtfsf", Ifloat},
};

Inset   ops63b = {op63b, nelem(op63b)};

void
fpreginit(void)
{
       int i;

       /* Normally initialised by the kernel */
       reg.fd[27] = 4503601774854144.0;
       reg.fd[29] = 0.5;
       reg.fd[28] = 0.0;
       reg.fd[30] = 1.0;
       reg.fd[31] = 2.0;
       for(i = 0; i < 27; i++)
               reg.fd[i] = reg.fd[28];
}

static double
v2fp(uvlong v)
{
       FPdbleword f;

       f.hi = v>>32;
       f.lo = v;
       return f.x;
}

static uvlong
fp2v(double d)
{
       FPdbleword f;

       f.x = d;
       return ((uvlong)f.hi<<32) | f.lo;
}

void
lfs(ulong ir)
{
       ulong ea;
       int imm, ra, rd, upd;
       union {
               ulong   i;
               float   f;
       } u;

       getairr(ir);
       ea = imm;
       upd = (ir&(1L<<26))!=0;
       if(ra) {
               ea += reg.r[ra];
               if(upd)
                       reg.r[ra] = ea;
       } else {
               if(upd)
                       undef(ir);
       }
       if(trace)
               itrace("%s\tf%d,%ld(r%d) ea=%lux", ci->name, rd, imm, ra, ea);

       u.i = getmem_w(ea);
       reg.fd[rd] = u.f;
}

void
lfsx(ulong ir)
{
       ulong ea;
       int rd, ra, rb, upd;
       union {
               ulong   i;
               float   f;
       } u;

       getarrr(ir);
       ea = reg.r[rb];
       upd = ((ir>>1)&0x3FF)==567;
       if(ra){
               ea += reg.r[ra];
               if(upd)
                       reg.r[ra] = ea;
               if(trace)
                       itrace("%s\tf%d,(r%d+r%d) ea=%lux", ci->name, rd, ra, rb, ea);
       } else {
               if(upd)
                       undef(ir);
               if(trace)
                       itrace("%s\tf%d,(r%d) ea=%lux", ci->name, rd, rb, ea);
       }

       u.i = getmem_w(ea);
       reg.fd[rd] = u.f;
}

void
lfd(ulong ir)
{
       ulong ea;
       int imm, ra, rd, upd;

       getairr(ir);
       ea = imm;
       upd = (ir&(1L<<26))!=0;
       if(ra) {
               ea += reg.r[ra];
               if(upd)
                       reg.r[ra] = ea;
       } else {
               if(upd)
                       undef(ir);
       }
       if(trace)
               itrace("%s\tf%d,%ld(r%d) ea=%lux", ci->name, rd, imm, ra, ea);

       reg.fd[rd] = v2fp(getmem_v(ea));
}

void
lfdx(ulong ir)
{
       ulong ea;
       int rd, ra, rb, upd;

       getarrr(ir);
       ea = reg.r[rb];
       upd = ((ir>>1)&0x3FF)==631;
       if(ra){
               ea += reg.r[ra];
               if(upd)
                       reg.r[ra] = ea;
               if(trace)
                       itrace("%s\tf%d,(r%d+r%d) ea=%lux", ci->name, rd, ra, rb, ea);
       } else {
               if(upd)
                       undef(ir);
               if(trace)
                       itrace("%s\tf%d,(r%d) ea=%lux", ci->name, rd, rb, ea);
       }

       reg.fd[rd] = v2fp(getmem_v(ea));
}

void
stfs(ulong ir)
{
       ulong ea;
       int imm, ra, rd, upd;
       union {
               float f;
               ulong w;
       } u;

       getairr(ir);
       ea = imm;
       upd = (ir&(1L<<26))!=0;
       if(ra) {
               ea += reg.r[ra];
               if(upd)
                       reg.r[ra] = ea;
       } else {
               if(upd)
                       undef(ir);
       }
       if(trace)
               itrace("%s\tf%d,%ld(r%d) %lux=%g",
                                       ci->name, rd, imm, ra, ea, reg.fd[rd]);
       u.f = reg.fd[rd];       /* BUG: actual PPC conversion is more subtle than this */
       putmem_w(ea, u.w);
}

void
stfsx(ulong ir)
{
       ulong ea;
       int rd, ra, rb, upd;
       union {
               float   f;
               ulong   w;
       } u;

       getarrr(ir);
       ea = reg.r[rb];
       upd = getxo(ir)==695;
       if(ra){
               ea += reg.r[ra];
               if(upd)
                       reg.r[ra] = ea;
               if(trace)
                       itrace("%s\tf%d,(r%d+r%d) %lux=%g", ci->name, rd, ra, rb, ea, (float)reg.fd[rd]);
       } else {
               if(upd)
                       undef(ir);
               if(trace)
                       itrace("%s\tf%d,(r%d) %lux=%g", ci->name, rd, rb, ea, (float)reg.fd[rd]);
       }

       u.f = reg.fd[rd];       /* BUG: actual PPC conversion is more subtle than this */
       putmem_w(ea, u.w);
}

void
stfd(ulong ir)
{
       ulong ea;
       int imm, ra, rd, upd;

       getairr(ir);
       ea = imm;
       upd = (ir&(1L<<26))!=0;
       if(ra) {
               ea += reg.r[ra];
               if(upd)
                       reg.r[ra] = ea;
       } else {
               if(upd)
                       undef(ir);
       }
       if(trace)
               itrace("%s\tf%d,%ld(r%d) %lux=%g",
                                       ci->name, rd, imm, ra, ea, reg.fd[rd]);

       putmem_v(ea, fp2v(reg.fd[rd]));
}

void
stfdx(ulong ir)
{
       ulong ea;
       int rd, ra, rb, upd;

       getarrr(ir);
       ea = reg.r[rb];
       upd = ((ir>>1)&0x3FF)==759;
       if(ra){
               ea += reg.r[ra];
               if(upd)
                       reg.r[ra] = ea;
               if(trace)
                       itrace("%s\tf%d,(r%d+r%d) %lux=%g", ci->name, rd, ra, rb, ea, reg.fd[rd]);
       } else {
               if(upd)
                       undef(ir);
               if(trace)
                       itrace("%s\tf%d,(r%d) %lux=%g", ci->name, rd, rb, ea, reg.fd[rd]);
       }

       putmem_v(ea, fp2v(reg.fd[rd]));
}

void
mcrfs(ulong ir)
{
       ulong rd, ra, rb;
       static ulong fpscr0[] ={
               FPS_FX|FPS_OX,
               FPS_UX|FPS_ZX|FPS_XX|FPS_VXSNAN,
               FPS_VXISI|FPS_VXIDI|FPS_VXZDZ|FPS_VXIMZ,
               FPS_VXVC,
               0,
               FPS_VXCVI,
       };

       getarrr(ir);
       if(rb || ra&3 || rd&3)
               undef(ir);
       ra >>= 2;
       rd >>= 2;
       reg.cr = (reg.cr & ~mkCR(rd, 0xF)) | mkCR(rd, getCR(ra, reg.fpscr));
       reg.fpscr &= ~fpscr0[ra];
       if(trace)
               itrace("mcrfs\tcrf%d,crf%d\n", rd, ra);
}

void
mffs(ulong ir)
{
       int rd, ra, rb;
       FPdbleword d;

       getarrr(ir);
       if(ra || rb)
               undef(ir);
       d.hi = 0xFFF80000UL;
       d.lo = reg.fpscr;
       reg.fd[rd] = d.x;
       /* it's anyone's guess how CR1 should be set when ir&1 */
       reg.cr &= ~mkCR(1, 0xE);        /* leave SO, reset others */
       if(trace)
               itrace("mffs%s\tfr%d\n", ir&1?".":"", rd);
}

void
mtfsb1(ulong ir)
{
       int rd, ra, rb;

       getarrr(ir);
       if(ra || rb)
               undef(ir);
       reg.fpscr |= (1L << (31-rd));
       /* BUG: should set summary bits */
       if(ir & 1)
               reg.cr &= ~mkCR(1, 0xE);        /* BUG: manual unclear: leave SO, reset others? */
       if(trace)
               itrace("mtfsb1%s\tfr%d\n", ir&1?".":"", rd);
}

void
mtfsb0(ulong ir)
{
       int rd, ra, rb;

       getarrr(ir);
       if(ra || rb)
               undef(ir);
       reg.fpscr &= ~(1L << (31-rd));
       if(ir & 1)
               reg.cr &= ~mkCR(1, 0xE);                /* BUG: manual unclear: leave SO, reset others? */
       if(trace)
               itrace("mtfsb0%s\tfr%d\n", ir&1?".":"", rd);
}

void
mtfsf(ulong ir)
{
       int fm, rb, i;
       FPdbleword d;
       ulong v;

       if(ir & ((1L << 25)|(1L << 16)))
               undef(ir);
       rb = (ir >> 11) & 0x1F;
       fm = (ir >> 17) & 0xFF;
       d.x = reg.fd[rb];
       v = d.lo;
       for(i=0; i<8; i++)
               if(fm & (1 << (7-i)))
                       reg.fpscr = (reg.fpscr & ~mkCR(i, 0xF)) | mkCR(i, getCR(i, v));
       /* BUG: should set FEX and VX `according to the usual rule' */
       if(ir & 1)
               reg.cr &= ~mkCR(1, 0xE);                /* BUG: manual unclear: leave SO, reset others? */
       if(trace)
               itrace("mtfsf%s\t#%.2x,fr%d", ir&1?".":"", fm, rb);
}

void
mtfsfi(ulong ir)
{
       int imm, rd;

       if(ir & ((0x7F << 16)|(1L << 11)))
               undef(ir);
       rd = (ir >> 23) & 0xF;
       imm = (ir >> 12) & 0xF;
       reg.fpscr = (reg.fpscr & ~mkCR(rd, 0xF)) | mkCR(rd, imm);
       /* BUG: should set FEX and VX `according to the usual rule' */
       if(ir & 1)
               reg.cr &= ~mkCR(1, 0xE);                /* BUG: manual unclear: leave SO, reset others? */
       if(trace)
               itrace("mtfsfi%s\tcrf%d,#%x", ir&1?".":"", rd, imm);
}

void
fcmp(ulong ir)
{
       int fc, rd, ra, rb;

       getarrr(ir);
       if(rd & 3)
               undef(ir);
       rd >>= 2;
       SET(fc);
       switch(getxo(ir)) {
       default:
               undef(ir);
       case 0:
               if(trace)
                       itrace("fcmpu\tcr%d,f%d,f%d", rd, ra, rb);
               if(isNaN(reg.fd[ra]) || isNaN(reg.fd[rb])) {
                       fc = CRFU;
                       break;
               }
               if(reg.fd[ra] == reg.fd[rb]) {
                       fc = CREQ;
                       break;
               }
               if(reg.fd[ra] < reg.fd[rb]) {
                       fc = CRLT;
                       break;
               }
               if(reg.fd[ra] > reg.fd[rb]) {
                       fc = CRGT;
                       break;
               }
               print("qi: fcmp error\n");
               break;
       case 32:
               if(trace)
                       itrace("fcmpo\tcr%d,f%d,f%d", rd, ra, rb);
               if(isNaN(reg.fd[ra]) || isNaN(reg.fd[rb])) {    /* BUG: depends whether quiet or signalling ... */
                       fc = CRFU;
                       Bprint(bioout, "invalid_fp_register\n");
                       longjmp(errjmp, 0);
               }
               if(reg.fd[ra] == reg.fd[rb]) {
                       fc = CREQ;
                       break;
               }
               if(reg.fd[ra] < reg.fd[rb]) {
                       fc = CRLT;
                       break;
               }
               if(reg.fd[ra] > reg.fd[rb]) {
                       fc = CRGT;
                       break;
               }
               print("qi: fcmp error\n");
               break;

       }
       fc >>= 28;
       reg.cr = (reg.cr & ~mkCR(rd,~0)) | mkCR(rd, fc);
       reg.fpscr = (reg.fpscr & ~0xF800) | (fc<<11);
       /* BUG: update FX, VXSNAN, VXVC */
}

/*
* the farith functions probably don't produce the right results
* in the presence of NaNs, Infs, etc., esp. wrt exception handling,
*/
void
fariths(ulong ir)
{
       int rd, ra, rb, rc, fmt;
       char *cc;
       ulong fpscr;

       fmt = 0;
       rc = (ir>>6)&0x1F;
       getarrr(ir);
       switch(getxo(ir)&0x1F) {        /* partial XO decode */
       default:
               undef(ir);
       case 18:
               if((float)reg.fd[rb] == 0.0) {
                       Bprint(bioout, "fp_exception ZX\n");
                       reg.fpscr |= FPS_ZX | FPS_FX;
                       longjmp(errjmp, 0);
               }
               reg.fd[rd] = (float)(reg.fd[ra] / reg.fd[rb]);
               break;
       case 20:
               reg.fd[rd] = (float)(reg.fd[ra] - reg.fd[rb]);
               break;
       case 21:
               reg.fd[rd] = (float)(reg.fd[ra] + reg.fd[rb]);
               break;
       case 25:
               reg.fd[rd] = (float)(reg.fd[ra] * reg.fd[rc]);
               rb = rc;
               break;
       case 28:
               reg.fd[rd] = (float)((reg.fd[ra] * reg.fd[rc]) - reg.fd[rb]);
               fmt = 2;
               break;
       case 29:
               reg.fd[rd] = (float)((reg.fd[ra] * reg.fd[rc]) + reg.fd[rb]);
               fmt = 2;
               break;
       case 30:
               reg.fd[rd] = (float)-((reg.fd[ra] * reg.fd[rc]) - reg.fd[rb]);
               fmt = 2;
               break;
       case 31:
               reg.fd[rd] = (float)-((reg.fd[ra] * reg.fd[rc]) + reg.fd[rb]);
               fmt = 2;
               break;
       }
       if(fmt==1 && ra)
               undef(ir);
       fpscr = setfpscr();
       setfpcc(reg.fd[rd]);
       cc = "";
       if(ir & 1) {
               cc = ".";
               reg.cr = (reg.cr & ~mkCR(1, ~0)) | mkCR(1, (fpscr>>28));
       }
       if(trace) {
               switch(fmt) {
               case 0:
                       itrace("%s%s\tfr%d,fr%d,fr%d", ci->name, cc, rd, ra, rb);
                       break;
               case 1:
                       itrace("%s%s\tfr%d,fr%d", ci->name, cc, rd, rb);
                       break;
               case 2:
                       itrace("%s%s\tfr%d,fr%d,fr%d,fr%d", ci->name, cc, rd, ra, rc, rb);
                       break;
               }
       }
}

void
farith(ulong ir)
{
       vlong vl;
       int rd, ra, rb, rc, fmt;
       char *cc;
       ulong fpscr;
       int nocc;
       double d;

       fmt = 0;
       nocc = 0;
       rc = (ir>>6)&0x1F;
       getarrr(ir);
       switch(getxo(ir)&0x1F) { /* partial XO decode */
       default:
               undef(ir);
       case 12:        /* frsp */
               reg.fd[rd] = (float)reg.fd[rb];
               fmt = 1;
               break;
       case 14:        /* fctiw */     /* BUG: ignores rounding mode */
       case 15:        /* fctiwz */
               d = reg.fd[rb];
               if(d >= 0x7fffffff)
                       vl = 0x7fffffff;
               else if(d < 0x80000000)
                       vl = 0x80000000;
               else
                       vl = d;
               reg.fd[rd] = v2fp(vl);
               fmt = 1;
               nocc = 1;
               break;
       case 18:
               if(reg.fd[rb] == 0.0) {
                       Bprint(bioout, "fp_exception ZX\n");
                       reg.fpscr |= FPS_ZX | FPS_FX;
                       longjmp(errjmp, 0);
               }
               reg.fd[rd] = reg.fd[ra] / reg.fd[rb];
               break;
       case 20:
               reg.fd[rd] = reg.fd[ra] - reg.fd[rb];
               break;
       case 21:
               reg.fd[rd] = reg.fd[ra] + reg.fd[rb];
               break;
       case 25:
               reg.fd[rd] = reg.fd[ra] * reg.fd[rc];
               rb = rc;
               break;
       case 28:
               reg.fd[rd] = (reg.fd[ra] * reg.fd[rc]) - reg.fd[rb];
               fmt = 2;
               break;
       case 29:
               reg.fd[rd] = (reg.fd[ra] * reg.fd[rc]) + reg.fd[rb];
               fmt = 2;
               break;
       case 30:
               reg.fd[rd] = -((reg.fd[ra] * reg.fd[rc]) - reg.fd[rb]);
               fmt = 2;
               break;
       case 31:
               reg.fd[rd] = -((reg.fd[ra] * reg.fd[rc]) + reg.fd[rb]);
               fmt = 2;
               break;
       }
       if(fmt==1 && ra)
               undef(ir);
       fpscr = setfpscr();
       if(nocc == 0)
               setfpcc(reg.fd[rd]);
       cc = "";
       if(ir & 1) {
               cc = ".";
               reg.cr = (reg.cr & ~mkCR(1, ~0)) | mkCR(1, (fpscr>>28));
       }
       if(trace) {
               switch(fmt) {
               case 0:
                       itrace("%s%s\tfr%d,fr%d,fr%d", ci->name, cc, rd, ra, rb);
                       break;
               case 1:
                       itrace("%s%s\tfr%d,fr%d", ci->name, cc, rd, rb);
                       break;
               case 2:
                       itrace("%s%s\tfr%d,fr%d,fr%d,fr%d", ci->name, cc, rd, ra, rc, rb);
                       break;
               }
       }
}

void
farith2(ulong ir)
{
       int rd, ra, rb;
       char *cc;
       ulong fpscr;

       getarrr(ir);
       switch(getxo(ir)) { /* full XO decode */
       default:
               undef(ir);
       case 40:
               reg.fd[rd] = -reg.fd[rb];
               break;
       case 72:
               reg.fd[rd] = reg.fd[rb];
               break;
       case 136:
               reg.fd[rd] = -fabs(reg.fd[rb]);
               break;
       case 264:
               reg.fd[rd] = fabs(reg.fd[rb]);
               break;
       }
       if(ra)
               undef(ir);
       fpscr = setfpscr();
       setfpcc(reg.fd[rd]);
       cc = "";
       if(ir & 1) {
               cc = ".";
               reg.cr = (reg.cr & ~mkCR(1, ~0)) | mkCR(1, (fpscr>>28));
       }
       if(trace)
               itrace("%s%s\tfr%d,fr%d", ci->name, cc, rd, rb);
}

ulong
setfpscr(void)
{
       ulong fps, fpscr;

       fps = getfsr();
       fpscr = reg.fpscr;
       if(fps & FPAOVFL)
               fpscr |= FPS_OX;
       if(fps & FPAINEX)
               fpscr |= FPS_XX;
       if(fps & FPAUNFL)
               fpscr |= FPS_UX;
       if(fps & FPAZDIV)
               fpscr |= FPS_ZX;
       if(fpscr != reg.fpscr) {
               fpscr |= FPS_FX;
               reg.fpscr = fpscr;
       }
       return fpscr;
}

void
setfpcc(double r)
{
       int c;

       c = 0;
       if(r == 0)
               c |= 2;
       else if(r < 0)
               c |= 4;
       else
               c |= 8;
       if(isNaN(r))
               c |= 1;
       reg.fpscr = (reg.fpscr & ~0xF800) | (0<<15) | (c<<11); /* unsure about class bit */
}