/*
    JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine
    Release Version 2.4

    A project from the Physics Dept, The University of Oxford

    Copyright (C) 2007-2010 The University of Oxford

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 2 as published by
    the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
    Details (including contact information) can be found at: 

    jpc.sourceforge.net
    or the developer website
    sourceforge.net/projects/jpc/

    Conceived and Developed by:
    Rhys Newman, Ian Preston, Chris Dennis

    End of licence header
*/


package org.jpc.emulator.processor.fpu64;

import org.jpc.emulator.processor.*;
import org.jpc.j2se.*;

import java.io.*;
import java.util.logging.*;

public class FpuState64 extends FpuState
{
    private static final Logger LOGGING = Logger.getLogger(FpuState64.class.getName());
            
    public final static int FPU_SPECIAL_TAG_NONE = 0;
    public final static int FPU_SPECIAL_TAG_NAN = 1;
    public final static int FPU_SPECIAL_TAG_UNSUPPORTED = 2;
    public final static int FPU_SPECIAL_TAG_INFINITY = 3;
    public final static int FPU_SPECIAL_TAG_DENORMAL = 4;
    public final static int FPU_SPECIAL_TAG_SNAN = 5;

    public final static double UNDERFLOW_THRESHOLD = Math.pow(2.0, -1022.0);

    public static final double DEFAULT_NAN = -1.5; // Double.longBitsToDouble(0xFFF8000000000000L);// to match Bochs' floatx80_default_nan truncation

    private final Processor cpu;

    double[] data;
    int[] tag;
    int[] specialTag;

    // status word

    private int statusWord;

    private boolean invalidOperation;
    private boolean denormalizedOperand;
    private boolean zeroDivide;
    private boolean overflow;
    private boolean underflow;
    private boolean precision;
    private boolean stackFault;

    public void saveState(DataOutput output) throws IOException
    {
        output.writeInt(statusWord);
        output.writeInt(maskWord);
        output.writeInt(precisionControl);
        output.writeInt(roundingControl);
        output.writeBoolean(invalidOperation);
        output.writeBoolean(denormalizedOperand);
        output.writeBoolean(zeroDivide);
        output.writeBoolean(overflow);
        output.writeBoolean(underflow);
        output.writeBoolean(precision);
        output.writeBoolean(stackFault);
        output.writeInt(data.length);
        for (int i=0; i< data.length; i++)
            output.writeDouble(data[i]);
        output.writeInt(tag.length);
        for (int i=0; i< tag.length; i++)
            output.writeInt(tag[i]);
        output.writeInt(specialTag.length);
        for (int i=0; i< specialTag.length; i++)
            output.writeInt(specialTag[i]);
    }

    public void loadState(DataInput input) throws IOException
    {
        statusWord  = input.readInt();
        maskWord = input.readInt();
        precisionControl = input.readInt();
        roundingControl = input.readInt();
        invalidOperation = input.readBoolean();
        denormalizedOperand = input.readBoolean();
        zeroDivide = input.readBoolean();
        overflow = input.readBoolean();
        underflow = input.readBoolean();
        precision = input.readBoolean();
        stackFault = input.readBoolean();
        int len = input.readInt();
        data = new double[len];
        for (int i=0; i< data.length; i++)
            data[i]  = input.readDouble();
        len = input.readInt();
        tag = new int[len];
        for (int i=0; i< tag.length; i++)
            tag[i] = input.readInt();
        len = input.readInt();
        specialTag = new int[len];
        for (int i=0; i< specialTag.length; i++)
            specialTag[i] = input.readInt();
    }

    public boolean getInvalidOperation() { return ((statusWord & 0x01) != 0); }
    public boolean getDenormalizedOperand() { return ((statusWord&0x02) != 0); }
    public boolean getZeroDivide() { return ((statusWord & 0x04) != 0); }
    public boolean getOverflow() { return ((statusWord & 0x08) != 0); }
    public boolean getUnderflow() { return ((statusWord & 0x10) != 0); }
    public boolean getPrecision() { return ((statusWord & 0x20) != 0); }
    public boolean getStackFault() { return ((statusWord & 0x40) != 0); }

    public void setInvalidOperation() { statusWord |= 0x01;}
    public void setDenormalizedOperand() { statusWord |= 0x02;}
    public void setZeroDivide() { statusWord |= 0x04;}
    public void setOverflow() { statusWord |= 0x08;}
    public void setUnderflow() {statusWord |= 0x10;}
    public void setPrecision() { statusWord |= 0x20;}
    public void setStackFault() { statusWord |= 0x40;}
    private static final boolean checkPendingExceptions = true;

    public void prepareFPU(Processor cpu, boolean checkExceptions)
    {
        if (((cpu.getCR0() & Processor.CR0_FPU_EMULATION) != 0) || (((cpu.getCR0() & Processor.CR0_MONITOR_COPROCESSOR) != 0) && ((cpu.getCR0() & Processor.CR0_TASK_SWITCHED) != 0)))
            throw ProcessorException.NO_FPU;

        if (checkExceptions)
            checkExceptions();
    }

    public void setC0(boolean val)
    {
        if (val)
            conditionCode |= 1;
        else
            conditionCode &= ~0x1;
    }

    public void setC1(boolean val)
    {
        if (val)
            conditionCode |= 2;
        else
            conditionCode &= ~2;
    }

    public void setC2(boolean val)
    {
        if (val)
            conditionCode |= 4;
        else
            conditionCode &= ~4;
    }

    public void setC3(boolean val)
    {
        if (val)
            conditionCode |= 8;
        else
            conditionCode &= ~8;
    }

    public boolean getBusy() { return getErrorSummaryStatus(); }

    public boolean getErrorSummaryStatus()
    {
        // (note stack fault is a subset of invalid operation)
        return (((statusWord & 0x3f) & ~maskWord) != 0);
    }

    public void checkExceptions() throws ProcessorException
    {
        if (getErrorSummaryStatus())
	    cpu.reportFPUException();
    }

    public void clearExceptions() { statusWord = 0; }

    // control word

    private int maskWord;
    private int precisionControl;
    private int roundingControl;

    public boolean getInvalidOperationMask() { return ((maskWord & 1) != 0); }
    public boolean getDenormalizedOperandMask() { return ((maskWord & 2) != 0);}
    public boolean getZeroDivideMask() { return ((maskWord & 4) != 0); }
    public boolean getOverflowMask() { return ((maskWord & 8) != 0); }
    public boolean getUnderflowMask() { return ((maskWord & 0x10) != 0); }
    public boolean getPrecisionMask() { return ((maskWord & 0x20) != 0); }
    public int getPrecisionControl() { return precisionControl; }
    public int getRoundingControl() { return roundingControl; }

    public void setInvalidOperationMask(boolean value)
    { 
        if (value) maskWord |= 1;
        else maskWord &= ~1;
    }

    public void setDenormalizedOperandMask(boolean value)
    { 
        if (value) maskWord |= 2;
        else maskWord &= ~2;
    }

    public void setZeroDivideMask(boolean value)
    { 
        if (value) maskWord |= 4;
        else maskWord &= ~4;
    }

    public void setOverflowMask(boolean value)
    { 
        if (value) maskWord |= 8;
        else maskWord &= ~8;
    }

    public void setUnderflowMask(boolean value)
    { 
        if (value) maskWord |= 0x10;
        else maskWord &= ~0x10;
    }

    public void setPrecisionMask(boolean value)
    { 
        if (value) maskWord |= 0x20;
        else maskWord &= ~0x20;
    }

    public void setAllMasks(boolean value)
    {
        if (value) maskWord |= 0x3f;
        else maskWord = 0;
    }

    public void setPrecisionControl(int value)
    { 
        precisionControl = value & 3;
    }

    public void setRoundingControl(int value)
    { 
        roundingControl = value & 3;
    }

    public FpuState64(Processor owner)
    {
	    cpu = owner;
        data = new double[STACK_DEPTH];
        tag = new int[STACK_DEPTH];
        specialTag = new int[STACK_DEPTH];
    }

    public void init()
    {
        //Bochs does these checks, but they stop many things booting!
        if (Option.useBochs.isSet())
            prepareFPU(cpu, !checkPendingExceptions);

        for (int i = 0; i < tag.length; ++i)
            tag[i] = FPU_TAG_EMPTY;
        for (int i = 0; i < specialTag.length; ++i)
            specialTag[i] = FPU_SPECIAL_TAG_NONE;
        // status word
        clearExceptions();
        conditionCode = 0;
        top = 0;
        // control word
        setAllMasks(true);
        infinityControl = false;
        setPrecisionControl(FPU_PRECISION_CONTROL_EXTENDED);
        setRoundingControl(FPU_ROUNDING_CONTROL_EVEN); // default
        lastIP = lastData = lastOpcode = 0;
    }

    public int tagCode(double x)
    {
        if (x == 0.0) return FPU_TAG_ZERO;
        else if (Double.isNaN(x)||Double.isInfinite(x)) return FPU_TAG_SPECIAL;
        else return FPU_TAG_VALID;
    }

    public static boolean isDenormal(double x)
    {
        long n = Double.doubleToRawLongBits(x);
        int exponent = (int)((n >> 52) & 0x7ff);
        if (exponent != 0) return false;
        long fraction = (n & ~(0xfffL << 52));
        if (fraction == 0L) return false;
        return true;
    }

    public static boolean isSNaN(long n)
    {
        // have to determine this based on 64-bit bit pattern,
        // since reassignment might cause Java to rationalize it to infinity
        int exponent = (int)((n >> 52) & 0x7ff);
        if (exponent != 0x7ff) return false;
        long fraction = (n & ~(0xfffL << 52));
        if ((fraction & (1L << 51)) != 0) return false;
        return (fraction != 0L);
    }

    // SNaN's aren't generated internally by x87.  Instead, they are
    // detected when they are read in from memory.  So if you push()
    // from memory, find out before whether it's an SNaN, then push(),
    // then set the tag word accordingly.
    public static int specialTagCode(double x)
    {
        // decode special:  NaN, unsupported, infinity, or denormal
        if (Double.isNaN(x)) return FPU_SPECIAL_TAG_NAN; // QNaN by default
        else if (Double.isInfinite(x)) return FPU_SPECIAL_TAG_INFINITY;
        //else if (Math.abs(x) < UNDERFLOW_THRESHOLD)
        else if (isDenormal(x))
            return FPU_SPECIAL_TAG_DENORMAL;
        else return FPU_SPECIAL_TAG_NONE;
    }

    public void push(double x) throws ProcessorException
    {
        if (--top < 0) top = STACK_DEPTH - 1;
        if (tag[top] != FPU_TAG_EMPTY)
        {
            setInvalidOperation();
            setStackFault();
            conditionCode |= 2; // C1 set to indicate stack overflow
            checkExceptions();
            x = DEFAULT_NAN;
        }
        data[top] = x;
        tag[top] = tagCode(x);
        specialTag[top] = specialTagCode(x);
    }

//     public void pushBig(BigDecimal x) throws ProcessorException
//     {
//         push(x.doubleValue());
//     }

    public double pop() throws ProcessorException
    {
        if (tag[top] == FPU_TAG_EMPTY)
        {
            setInvalidOperation();
            setStackFault();
            conditionCode &= ~2; // C1 cleared to indicate stack underflow
            checkExceptions();
            // TODO:  if IE masked, do we just return whatever
            // random contents there are?  That's what it seems
            // from the reference.
        }
        else if (specialTag[top] == FPU_SPECIAL_TAG_SNAN)
        {
            setInvalidOperation();
            checkExceptions();
            return Double.NaN; // QNaN if masked
        }
        double x = data[top];
        tag[top] = FPU_TAG_EMPTY;
        if (++top >= STACK_DEPTH) top = 0;
        return x;
    }

//     public BigDecimal popBig() throws ProcessorException
//     {
//         return new BigDecimal(pop());
//     }
    public double[] getStack()
    {
        double[] res = new double[8];
        for (int i=0; i < 8; i++)
            res[i] = data[(i+top)&7];
        return res;
    }

    public void setStack(double[] s)
    {
        for (int i=0; i < 8; i++)
            data[(i+top)&7] = s[i];
    }

    public double ST(int index) throws ProcessorException
    {
        int i = ((top + index) & 0x7);
        if (tag[i] == FPU_TAG_EMPTY)
        {
            // an attempt to read an empty register is technically
            // a "stack underflow"
            setInvalidOperation();
            setStackFault();
            conditionCode &= ~2; // C1 cleared to indicate stack underflow
            checkExceptions();
        }
        else if (specialTag[i] == FPU_SPECIAL_TAG_SNAN)
        {
            setInvalidOperation();
            checkExceptions();
            return Double.NaN; // QNaN if masked
        }
        return data[i];
    }

//     public BigDecimal bigST(int index) throws ProcessorException
//     {
//         return new BigDecimal(ST(index));
//     }

    public int getTag(int index)
    {
        int i = ((top + index) & 0x7);
        return tag[i];
    }

    public int getSpecialTag(int index)
    {
        int i = ((top + index) & 0x7);
        return specialTag[i];
    }

    public void setTagEmpty(int index)
    {
        // used by FFREE
        int i = ((top + index) & 0x7);
        tag[i] = FpuState.FPU_TAG_EMPTY;
    }

    public void setST(int index, double value)
    {
        int i = ((top + index) & 0x7);
        data[i] = value;
        tag[i] = tagCode(value);
        specialTag[i] = specialTagCode(value);
    }

    public int getStatus()
    {
        int w = statusWord;
        if (getErrorSummaryStatus()) w |= 0x80;
        if (getBusy()) w |= 0x8000;
        w |= (top << 11);
        w |= ((conditionCode & 0x7) << 8);
        w |= ((conditionCode & 0x8) << 11);
        return w;
    }

    public void setStatus(int w)
    {
        statusWord &= ~0x7f;
        statusWord |= (w & 0x7f);
        top = ((w >> 11) & 0x7);
        conditionCode = ((w >> 8) & 0x7);
        conditionCode |= ((w >>> 14) & 1);
    }

    public int getControl()
    {
        int w = maskWord;
        w |= ((precisionControl & 0x3) << 8);
        w |= ((roundingControl & 0x3) << 10);
        w |= 0x40; // reserved bit
        if (infinityControl) w |= 0x1000;
        return w;
    }

    public void setControl(int w)
    {
        maskWord &= ~0x3f;
        maskWord |= (w & 0x3f);

        infinityControl = ((w & 0x1000) != 0);
        setPrecisionControl((w >> 8) & 3);
        setRoundingControl((w >> 10) & 3);
    }

    public int getTagWord()
    {
        int w = 0;
        for (int i = STACK_DEPTH - 1; i >= 0; --i)
            w = ((w << 2) | (tag[i] & 0x3));
        return w;
    }

    public void setTagWord(int w)
    {
        for (int i = 0; i < tag.length; ++i)
        {
            int t = (w & 0x3);
            if (t == FPU_TAG_EMPTY)
            {
                tag[i] = FPU_TAG_EMPTY;
            }
            else
            {
                tag[i] = tagCode(data[i]);
                if (specialTag[i] != FPU_SPECIAL_TAG_SNAN)
                    specialTag[i] = specialTagCode(data[i]);
                // SNaN is sticky, and Java doesn't preserve the bit pattern.
            }
            w >>= 2;
        }
    }

    public double round(double in)
    {
        if (!Double.isInfinite(in)) // preserve infinities
        {
            switch(getRoundingControl())
            {
                case FpuState.FPU_ROUNDING_CONTROL_EVEN:
                    return Math.rint(in);
                case FpuState.FPU_ROUNDING_CONTROL_DOWN:
                    return Math.floor(in);
                case FpuState.FPU_ROUNDING_CONTROL_UP:
                    return Math.ceil(in);
                case FpuState.FPU_ROUNDING_CONTROL_TRUNCATE:
                    return Math.signum(in) * Math.floor(Math.abs(in));
                default:
                    throw new IllegalStateException("Invalid rounding control value");
            }
        }
        return in;
    }

    public static byte[] doubleToExtended(double x, boolean isSignalNaN)
    {
        byte[] b = new byte[10];
        long fraction;
        int iexp = 0;
        // other special forms?
        if (isSignalNaN)
        {
            fraction = 0xc000000000000000L;
        }
        else
        {
            long n = Double.doubleToRawLongBits(x);
            fraction = (n & ~(0xfffL << 52));
            iexp = ((int)(n >> 52) & 0x7ff);
            boolean sgn = ((n & (1L << 63)) != 0);
            // insert implicit 1
            fraction |= (1L << 52);
            fraction <<= 11;
            // re-bias exponent
            iexp += (16383 - 1023);
            if (sgn) iexp |= 0x8000;
        }     
        for (int i = 0; i < 8; ++i)
        {
            b[i] = (byte)fraction;
            fraction >>>= 8;
        }
        b[8] = (byte)iexp;
        b[9] = (byte)(iexp >> 8);
        return b;
    }

    public static int specialTagCode(byte[] b)
    {
        long fraction = 0;
        for (int i = 7; i >= 0; --i)
        {
            long w = ((long)b[i] & 0xff);
            fraction |= w;
            fraction <<= 8;
        }
        int iexp = (((int)b[8] & 0xff) | (((int)b[9] & 0x7f) << 8));
        boolean sgn = ((b[9] & 0x80) != 0);
        boolean integ = ((b[7] & 0x80) != 0); // explicit integer bit

        if (iexp == 0)
        {
            if (integ)
            {
                // "pseudo-denormals" - treated like a normal denormal
                return FPU_SPECIAL_TAG_DENORMAL;
            }
            else
            {
                // normal denormals
                return FPU_SPECIAL_TAG_DENORMAL;
            }
        }
        else if (iexp == 0x7fff)
        {
            if (fraction == 0L)
            {
                // "pseudo-infinity"
                return FPU_SPECIAL_TAG_UNSUPPORTED;
            }
            else if (integ)
            {
                if ((fraction << 1) == 0)
                {
                    // infinity
                    return FPU_SPECIAL_TAG_INFINITY;
                }
                else
                {
                    // NaN's
                    if ((fraction >>> 62) == 0) return FPU_SPECIAL_TAG_SNAN;
                    else return FPU_SPECIAL_TAG_NAN;
                }
            }
            else
            {
                // pseudo-NaN
                return FPU_SPECIAL_TAG_UNSUPPORTED;
            }
        }
        else
        {
            if (integ)
            {
                // normal float
                return FPU_SPECIAL_TAG_NONE;
            }
            else
            {
                // "unnormal"
                return FPU_SPECIAL_TAG_UNSUPPORTED;
            }
        }
    }


    public static double extendedToDouble(byte[] b)
    {
        long fraction = 0;
        for (int i = 7; i >= 0; --i)
        {
            long w = ((long)b[i] & 0xff);
            fraction |= w;
            fraction <<= 8;
        }
        int iexp = (((int)b[8] & 0xff) | (((int)b[9] & 0x7f) << 8));
        boolean sgn = ((b[9] & 0x80) != 0);
        boolean integ = ((b[7] & 0x80) != 0); // explicit integer bit

        if (iexp == 0)
        {
            if (integ)
            {
                // "pseudo-denormals" - treat exponent as value 1 and
                // mantissa as the same
                // (http://www.ragestorm.net/downloads/387intel.txt)
                iexp = 1;
            }
            // now treat as a normal denormal (from denormal).
            // actually, given that min unbiased exponent is -16383 for
            // extended, and only -1023 for double, a denormalized
            // extended is pretty much zero in double!
            return 0.0;
        }
        else if (iexp == 0x7fff)
        {
            if (fraction == 0L)
            {
                // "pseudo-infinity":  if #IA masked, return QNaN
                // more technically, sign bit should be set to indicate
                // "QNaN floating-point indefinite"
                return Double.NaN;
            }
            else if (integ)
            {
                if ((fraction << 1) == 0)
                {
                    return (sgn) ? Double.NEGATIVE_INFINITY :
                                   Double.POSITIVE_INFINITY;
                }
                else
                {
                    // a conventional NaN
                    return Double.NaN;
                }
            }
            else
            {
                // pseudo-NaN
                return Double.NaN;
            }
        }
        else
        {
            if (integ)
            {
                // normal float:  decode
                iexp += 1023 - 16383; // rebias for double format
                fraction >>>= 11; // truncate rounding (is this the right way?)
                if (iexp > 0x7ff)
                {
                    // too big an exponent
                    return (sgn) ? Double.NEGATIVE_INFINITY :
                                   Double.POSITIVE_INFINITY;
                }
                else if (iexp < 0)
                {
                    // denormal (from normal)
                    fraction >>>= (- iexp);
                    iexp = 0;
                }
                fraction &= ~(0xfffL << 52); // this cuts off explicit 1
                fraction |= (((long)iexp & 0x7ff) << 52);
                if (sgn) fraction |= (1L << 63);
                return Double.longBitsToDouble(fraction);
            }
            else
            {
                // "unnormal":  if #IA masked, return QNaN FP indefinite
                return Double.NaN;
            }
        }
    }

}
