/*
 * Copyright (C) 2011, 2013 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once

#if ENABLE(DFG_JIT)

#include "DFGMinifiedIDInlines.h"
#include "DFGVariableEvent.h"
#include "DFGVariableEventStream.h"
#include "DataFormat.h"

namespace JSC { namespace DFG {

// === GenerationInfo ===
//
// This class is used to track the current status of live values during code generation.
// Can provide information as to whether a value is in machine registers, and if so which,
// whether a value has been spilled to the RegisterFile, and if so may be able to provide
// details of the format in memory (all values are spilled in a boxed form, but we may be
// able to track the type of box), and tracks how many outstanding uses of a value remain,
// so that we know when the value is dead and the machine registers associated with it
// may be released.
class GenerationInfo {
public:
    GenerationInfo()
        : m_node(0)
        , m_useCount(0)
        , m_registerFormat(DataFormatNone)
        , m_spillFormat(DataFormatNone)
        , m_canFill(false)
        , m_bornForOSR(false)
        , m_isConstant(false)
    {
    }

    void initConstant(Node* node, uint32_t useCount)
    {
        m_node = node;
        m_useCount = useCount;
        m_registerFormat = DataFormatNone;
        m_spillFormat = DataFormatNone;
        m_canFill = true;
        m_bornForOSR = false;
        m_isConstant = true;
        ASSERT(m_useCount);
    }
    void initGPR(Node* node, uint32_t useCount, GPRReg gpr, DataFormat format)
    {
        ASSERT(gpr != InvalidGPRReg);
        m_node = node;
        m_useCount = useCount;
        m_registerFormat = format;
        m_spillFormat = DataFormatNone;
        m_canFill = false;
        u.gpr = gpr;
        m_bornForOSR = false;
        m_isConstant = false;
        ASSERT(m_useCount);
    }
    void initInt32(Node* node, uint32_t useCount, GPRReg gpr)
    {
        initGPR(node, useCount, gpr, DataFormatInt32);
    }
    void initInt52(Node* node, uint32_t useCount, GPRReg reg, DataFormat format)
    {
        ASSERT(format == DataFormatInt52 || format == DataFormatStrictInt52);
        initGPR(node, useCount, reg, format);
    }
    void initInt52(Node* node, uint32_t useCount, GPRReg reg)
    {
        initGPR(node, useCount, reg, DataFormatInt52);
    }
    void initStrictInt52(Node* node, uint32_t useCount, GPRReg reg)
    {
        initGPR(node, useCount, reg, DataFormatStrictInt52);
    }
#if USE(JSVALUE64)
    void initJSValue(Node* node, uint32_t useCount, GPRReg gpr, DataFormat format = DataFormatJS)
    {
        ASSERT(format & DataFormatJS);
        initGPR(node, useCount, gpr, format);
    }
#elif USE(JSVALUE32_64)
    void initJSValue(Node* node, uint32_t useCount, GPRReg tagGPR, GPRReg payloadGPR, DataFormat format = DataFormatJS)
    {
        ASSERT(format & DataFormatJS);

        m_node = node;
        m_useCount = useCount;
        m_registerFormat = format;
        m_spillFormat = DataFormatNone;
        m_canFill = false;
        u.v.tagGPR = tagGPR;
        u.v.payloadGPR = payloadGPR;
        m_bornForOSR = false;
        m_isConstant = false;
        ASSERT(m_useCount);
    }
#endif
    void initCell(Node* node, uint32_t useCount, GPRReg gpr)
    {
        initGPR(node, useCount, gpr, DataFormatCell);
    }
    void initBoolean(Node* node, uint32_t useCount, GPRReg gpr)
    {
        initGPR(node, useCount, gpr, DataFormatBoolean);
    }
    void initDouble(Node* node, uint32_t useCount, FPRReg fpr)
    {
        ASSERT(fpr != InvalidFPRReg);
        m_node = node;
        m_useCount = useCount;
        m_registerFormat = DataFormatDouble;
        m_spillFormat = DataFormatNone;
        m_canFill = false;
        u.fpr = fpr;
        m_bornForOSR = false;
        m_isConstant = false;
        ASSERT(m_useCount);
    }
    void initStorage(Node* node, uint32_t useCount, GPRReg gpr)
    {
        initGPR(node, useCount, gpr, DataFormatStorage);
    }

    // Get the node that produced this value.
    Node* node() { return m_node; }

    void noticeOSRBirth(VariableEventStream& stream, Node* node, VirtualRegister virtualRegister)
    {
        if (m_node != node)
            return;
        if (!alive())
            return;
        if (m_bornForOSR)
            return;

        m_bornForOSR = true;

        if (m_isConstant)
            appendBirth(stream);
        else if (m_registerFormat != DataFormatNone)
            appendFill(BirthToFill, stream);
        else if (m_spillFormat != DataFormatNone)
            appendSpill(BirthToSpill, stream, virtualRegister);
    }

    // Mark the value as having been used (decrement the useCount).
    // Returns true if this was the last use of the value, and any
    // associated machine registers may be freed.
    bool use(VariableEventStream& stream)
    {
        ASSERT(m_useCount);
        bool result = !--m_useCount;

        if (result && m_bornForOSR) {
            ASSERT(m_node);
            stream.appendAndLog(VariableEvent::death(MinifiedID(m_node)));
        }

        return result;
    }

    // Used to check the operands of operations to see if they are on
    // their last use; in some cases it may be safe to reuse the same
    // machine register for the result of the operation.
    uint32_t useCount()
    {
        ASSERT(m_useCount);
        return m_useCount;
    }

    // Get the format of the value in machine registers (or 'none').
    DataFormat registerFormat() { return m_registerFormat; }
    // Get the format of the value as it is spilled in the JSStack (or 'none').
    DataFormat spillFormat() { return m_spillFormat; }

    bool isFormat(DataFormat expectedFormat)
    {
        return registerFormat() == expectedFormat || spillFormat() == expectedFormat;
    }

    bool isJSFormat(DataFormat expectedFormat)
    {
        return JSC::isJSFormat(registerFormat(), expectedFormat) || JSC::isJSFormat(spillFormat(), expectedFormat);
    }

    bool isJSInt32()
    {
        return isJSFormat(DataFormatJSInt32);
    }

    bool isInt52()
    {
        return isFormat(DataFormatInt52);
    }

    bool isStrictInt52()
    {
        return isFormat(DataFormatStrictInt52);
    }

    bool isJSDouble()
    {
        return isJSFormat(DataFormatJSDouble);
    }

    bool isJSCell()
    {
        return isJSFormat(DataFormatJSCell);
    }

    bool isJSBoolean()
    {
        return isJSFormat(DataFormatJSBoolean);
    }

    bool isUnknownJS()
    {
        return spillFormat() == DataFormatNone
            ? registerFormat() == DataFormatJS || registerFormat() == DataFormatNone
            : spillFormat() == DataFormatJS;
    }

    // Get the machine resister currently holding the value.
#if USE(JSVALUE64)
    GPRReg gpr() { ASSERT(m_registerFormat && m_registerFormat != DataFormatDouble); return u.gpr; }
    FPRReg fpr() { ASSERT(m_registerFormat == DataFormatDouble); return u.fpr; }
    JSValueRegs jsValueRegs() { ASSERT(m_registerFormat & DataFormatJS); return JSValueRegs(u.gpr); }
#elif USE(JSVALUE32_64)
    GPRReg gpr() { ASSERT(!(m_registerFormat & DataFormatJS) && m_registerFormat != DataFormatDouble); return u.gpr; }
    GPRReg tagGPR() { ASSERT(m_registerFormat & DataFormatJS); return u.v.tagGPR; }
    GPRReg payloadGPR() { ASSERT(m_registerFormat & DataFormatJS); return u.v.payloadGPR; }
    FPRReg fpr() { ASSERT(m_registerFormat == DataFormatDouble || m_registerFormat == DataFormatJSDouble); return u.fpr; }
    JSValueRegs jsValueRegs() { ASSERT(m_registerFormat & DataFormatJS); return JSValueRegs(u.v.tagGPR, u.v.payloadGPR); }
#endif

    // Check whether a value needs spilling in order to free up any associated machine registers.
    bool needsSpill()
    {
        // This should only be called on values that are currently in a register.
        ASSERT(m_registerFormat != DataFormatNone);
        // Constants do not need spilling, nor do values that have already been
        // spilled to the JSStack.
        return !m_canFill;
    }

    // Called when a VirtualRegister is being spilled to the JSStack for the first time.
    void spill(VariableEventStream& stream, VirtualRegister virtualRegister, DataFormat spillFormat)
    {
        // We shouldn't be spill values that don't need spilling.
        ASSERT(!m_canFill);
        ASSERT(m_spillFormat == DataFormatNone);
        // We should only be spilling values that are currently in machine registers.
        ASSERT(m_registerFormat != DataFormatNone);

        m_registerFormat = DataFormatNone;
        m_spillFormat = spillFormat;
        m_canFill = true;

        if (m_bornForOSR)
            appendSpill(Spill, stream, virtualRegister);
    }

    // Called on values that don't need spilling (constants and values that have
    // already been spilled), to mark them as no longer being in machine registers.
    void setSpilled(VariableEventStream& stream, VirtualRegister virtualRegister)
    {
        // Should only be called on values that don't need spilling, and are currently in registers.
        ASSERT(m_canFill && m_registerFormat != DataFormatNone);
        m_registerFormat = DataFormatNone;

        if (m_bornForOSR)
            appendSpill(Spill, stream, virtualRegister);
    }

    void killSpilled()
    {
        m_spillFormat = DataFormatNone;
        m_canFill = false;
    }

    void fillGPR(VariableEventStream& stream, GPRReg gpr, DataFormat format)
    {
        ASSERT(gpr != InvalidGPRReg);
        m_registerFormat = format;
        u.gpr = gpr;
        if (m_bornForOSR)
            appendFill(Fill, stream);
    }

    // Record that this value is filled into machine registers,
    // tracking which registers, and what format the value has.
#if USE(JSVALUE64)
    void fillJSValue(VariableEventStream& stream, GPRReg gpr, DataFormat format = DataFormatJS)
    {
        ASSERT(format & DataFormatJS);
        fillGPR(stream, gpr, format);
    }
#elif USE(JSVALUE32_64)
    void fillJSValue(VariableEventStream& stream, GPRReg tagGPR, GPRReg payloadGPR, DataFormat format = DataFormatJS)
    {
        ASSERT(format & DataFormatJS);
        m_registerFormat = format;
        u.v.tagGPR = tagGPR; // FIXME: for JSValues with known type (boolean, integer, cell etc.) no tagGPR is needed?
        u.v.payloadGPR = payloadGPR;

        if (m_bornForOSR)
            appendFill(Fill, stream);
    }
    void fillCell(VariableEventStream& stream, GPRReg gpr)
    {
        fillGPR(stream, gpr, DataFormatCell);
    }
#endif
    void fillInt32(VariableEventStream& stream, GPRReg gpr)
    {
        fillGPR(stream, gpr, DataFormatInt32);
    }
    void fillInt52(VariableEventStream& stream, GPRReg gpr, DataFormat format)
    {
        ASSERT(format == DataFormatInt52 || format == DataFormatStrictInt52);
        fillGPR(stream, gpr, format);
    }
    void fillInt52(VariableEventStream& stream, GPRReg gpr)
    {
        fillGPR(stream, gpr, DataFormatInt52);
    }
    void fillStrictInt52(VariableEventStream& stream, GPRReg gpr)
    {
        fillGPR(stream, gpr, DataFormatStrictInt52);
    }
    void fillBoolean(VariableEventStream& stream, GPRReg gpr)
    {
        fillGPR(stream, gpr, DataFormatBoolean);
    }
    void fillDouble(VariableEventStream& stream, FPRReg fpr)
    {
        ASSERT(fpr != InvalidFPRReg);
        m_registerFormat = DataFormatDouble;
        u.fpr = fpr;

        if (m_bornForOSR)
            appendFill(Fill, stream);
    }
    void fillStorage(VariableEventStream& stream, GPRReg gpr)
    {
        fillGPR(stream, gpr, DataFormatStorage);
    }

    bool alive()
    {
        return m_useCount;
    }

    ValueRecovery recovery(VirtualRegister spillSlot) const
    {
        if (m_isConstant)
            return ValueRecovery::constant(m_node->constant()->value());

        if (m_registerFormat == DataFormatDouble)
            return ValueRecovery::inFPR(u.fpr, DataFormatDouble);

#if USE(JSVALUE32_64)
        if (m_registerFormat & DataFormatJS) {
            if (m_registerFormat == DataFormatJS)
                return ValueRecovery::inPair(u.v.tagGPR, u.v.payloadGPR);
            return ValueRecovery::inGPR(u.v.payloadGPR, static_cast<DataFormat>(m_registerFormat & ~DataFormatJS));
        }
#endif
        if (m_registerFormat)
            return ValueRecovery::inGPR(u.gpr, m_registerFormat);

        ASSERT(m_spillFormat);

        return ValueRecovery::displacedInJSStack(spillSlot, m_spillFormat);
    }

private:
    void appendBirth(VariableEventStream& stream)
    {
        stream.appendAndLog(VariableEvent::birth(MinifiedID(m_node)));
    }

    void appendFill(VariableEventKind kind, VariableEventStream& stream)
    {
        ASSERT(m_bornForOSR);

        if (m_registerFormat == DataFormatDouble) {
            stream.appendAndLog(VariableEvent::fillFPR(kind, MinifiedID(m_node), u.fpr));
            return;
        }
#if USE(JSVALUE32_64)
        if (m_registerFormat & DataFormatJS) {
            stream.appendAndLog(VariableEvent::fillPair(kind, MinifiedID(m_node), u.v.tagGPR, u.v.payloadGPR));
            return;
        }
#endif
        stream.appendAndLog(VariableEvent::fillGPR(kind, MinifiedID(m_node), u.gpr, m_registerFormat));
    }

    void appendSpill(VariableEventKind kind, VariableEventStream& stream, VirtualRegister virtualRegister)
    {
        stream.appendAndLog(VariableEvent::spill(kind, MinifiedID(m_node), virtualRegister, m_spillFormat));
    }

    // The node whose result is stored in this virtual register.
    Node* m_node;
    uint32_t m_useCount;
    DataFormat m_registerFormat;
    DataFormat m_spillFormat;
    bool m_canFill;
    bool m_bornForOSR;
    bool m_isConstant;
    union {
        GPRReg gpr;
        FPRReg fpr;
#if USE(JSVALUE32_64)
        struct {
            GPRReg tagGPR;
            GPRReg payloadGPR;
        } v;
#endif
    } u;
};

} } // namespace JSC::DFG

#endif
