/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef jit_BaselineFrameInfo_h
#define jit_BaselineFrameInfo_h

#include "mozilla/Attributes.h"

#include <new>

#include "jit/BaselineFrame.h"
#include "jit/FixedList.h"
#include "jit/MacroAssembler.h"
#include "jit/SharedICRegisters.h"

namespace js {
namespace jit {

struct BytecodeInfo;

// FrameInfo overview.
//
// FrameInfo is used by the compiler to track values stored in the frame. This
// includes locals, arguments and stack values. Locals and arguments are always
// fully synced. Stack values can either be synced, stored as constant, stored in
// a Value register or refer to a local slot. Syncing a StackValue ensures it's
// stored on the stack, e.g. kind == Stack.
//
// To see how this works, consider the following statement:
//
//    var y = x + 9;
//
// Here two values are pushed: StackValue(LocalSlot(0)) and StackValue(Int32Value(9)).
// Only when we reach the ADD op, code is generated to load the operands directly
// into the right operand registers and sync all other stack values.
//
// For stack values, the following invariants hold (and are checked between ops):
//
// (1) If a value is synced (kind == Stack), all values below it must also be synced.
//     In other words, values with kind other than Stack can only appear on top of the
//     abstract stack.
//
// (2) When we call a stub or IC, all values still on the stack must be synced.

// Represents a value pushed on the stack. Note that StackValue is not used for
// locals or arguments since these are always fully synced.
class StackValue
{
  public:
    enum Kind {
        Constant,
        Register,
        Stack,
        LocalSlot,
        ArgSlot,
        ThisSlot,
        EvalNewTargetSlot
#ifdef DEBUG
        // In debug builds, assert Kind is initialized.
        , Uninitialized
#endif
    };

  private:
    MOZ_INIT_OUTSIDE_CTOR Kind kind_;

    MOZ_INIT_OUTSIDE_CTOR union Data {
        JS::Value constant;
        ValueOperand reg;
        uint32_t localSlot;
        uint32_t argSlot;

        // |constant| has a non-trivial constructor and therefore MUST be
        // placement-new'd into existence.
        MOZ_PUSH_DISABLE_NONTRIVIAL_UNION_WARNINGS
        Data() {}
        MOZ_POP_DISABLE_NONTRIVIAL_UNION_WARNINGS
    } data;

    MOZ_INIT_OUTSIDE_CTOR JSValueType knownType_;

  public:
    StackValue() {
        reset();
    }

    Kind kind() const {
        return kind_;
    }
    bool hasKnownType() const {
        return knownType_ != JSVAL_TYPE_UNKNOWN;
    }
    bool hasKnownType(JSValueType type) const {
        MOZ_ASSERT(type != JSVAL_TYPE_UNKNOWN);
        return knownType_ == type;
    }
    bool isKnownBoolean() const {
        return hasKnownType(JSVAL_TYPE_BOOLEAN);
    }
    JSValueType knownType() const {
        MOZ_ASSERT(hasKnownType());
        return knownType_;
    }
    void reset() {
#ifdef DEBUG
        kind_ = Uninitialized;
        knownType_ = JSVAL_TYPE_UNKNOWN;
#endif
    }
    Value constant() const {
        MOZ_ASSERT(kind_ == Constant);
        return data.constant;
    }
    ValueOperand reg() const {
        MOZ_ASSERT(kind_ == Register);
        return data.reg;
    }
    uint32_t localSlot() const {
        MOZ_ASSERT(kind_ == LocalSlot);
        return data.localSlot;
    }
    uint32_t argSlot() const {
        MOZ_ASSERT(kind_ == ArgSlot);
        return data.argSlot;
    }

    void setConstant(const Value& v) {
        kind_ = Constant;
        new (&data.constant) Value(v);
        knownType_ = v.isDouble() ? JSVAL_TYPE_DOUBLE : v.extractNonDoubleType();
    }
    void setRegister(const ValueOperand& val, JSValueType knownType = JSVAL_TYPE_UNKNOWN) {
        kind_ = Register;
        new (&data.reg) ValueOperand(val);
        knownType_ = knownType;
    }
    void setLocalSlot(uint32_t slot) {
        kind_ = LocalSlot;
        new (&data.localSlot) uint32_t(slot);
        knownType_ = JSVAL_TYPE_UNKNOWN;
    }
    void setArgSlot(uint32_t slot) {
        kind_ = ArgSlot;
        new (&data.argSlot) uint32_t(slot);
        knownType_ = JSVAL_TYPE_UNKNOWN;
    }
    void setThis() {
        kind_ = ThisSlot;
        knownType_ = JSVAL_TYPE_UNKNOWN;
    }
    void setEvalNewTarget() {
        kind_ = EvalNewTargetSlot;
        knownType_ = JSVAL_TYPE_UNKNOWN;
    }
    void setStack() {
        kind_ = Stack;
        knownType_ = JSVAL_TYPE_UNKNOWN;
    }
};

enum StackAdjustment { AdjustStack, DontAdjustStack };

class FrameInfo
{
    JSScript* script;
    MacroAssembler& masm;

    FixedList<StackValue> stack;
    size_t spIndex;

  public:
    FrameInfo(JSScript* script, MacroAssembler& masm)
      : script(script),
        masm(masm),
        stack(),
        spIndex(0)
    { }

    MOZ_MUST_USE bool init(TempAllocator& alloc);

    size_t nlocals() const {
        return script->nfixed();
    }
    size_t nargs() const {
        return script->functionNonDelazifying()->nargs();
    }

  private:
    inline StackValue* rawPush() {
        StackValue* val = &stack[spIndex++];
        val->reset();
        return val;
    }

  public:
    inline size_t stackDepth() const {
        return spIndex;
    }
    inline void setStackDepth(uint32_t newDepth) {
        if (newDepth <= stackDepth()) {
            spIndex = newDepth;
        } else {
            uint32_t diff = newDepth - stackDepth();
            for (uint32_t i = 0; i < diff; i++) {
                StackValue* val = rawPush();
                val->setStack();
            }

            MOZ_ASSERT(spIndex == newDepth);
        }
    }
    inline StackValue* peek(int32_t index) const {
        MOZ_ASSERT(index < 0);
        return const_cast<StackValue*>(&stack[spIndex + index]);
    }

    inline void pop(StackAdjustment adjust = AdjustStack);
    inline void popn(uint32_t n, StackAdjustment adjust = AdjustStack);
    inline void push(const Value& val) {
        StackValue* sv = rawPush();
        sv->setConstant(val);
    }
    inline void push(const ValueOperand& val, JSValueType knownType=JSVAL_TYPE_UNKNOWN) {
        StackValue* sv = rawPush();
        sv->setRegister(val, knownType);
    }
    inline void pushLocal(uint32_t local) {
        MOZ_ASSERT(local < nlocals());
        StackValue* sv = rawPush();
        sv->setLocalSlot(local);
    }
    inline void pushArg(uint32_t arg) {
        StackValue* sv = rawPush();
        sv->setArgSlot(arg);
    }
    inline void pushThis() {
        StackValue* sv = rawPush();
        sv->setThis();
    }
    inline void pushEvalNewTarget() {
        MOZ_ASSERT(script->isForEval());
        StackValue* sv = rawPush();
        sv->setEvalNewTarget();
    }

    inline void pushScratchValue() {
        masm.pushValue(addressOfScratchValue());
        StackValue* sv = rawPush();
        sv->setStack();
    }
    inline Address addressOfLocal(size_t local) const {
        MOZ_ASSERT(local < nlocals());
        return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(local));
    }
    Address addressOfArg(size_t arg) const {
        MOZ_ASSERT(arg < nargs());
        return Address(BaselineFrameReg, BaselineFrame::offsetOfArg(arg));
    }
    Address addressOfThis() const {
        return Address(BaselineFrameReg, BaselineFrame::offsetOfThis());
    }
    Address addressOfEvalNewTarget() const {
        return Address(BaselineFrameReg, BaselineFrame::offsetOfEvalNewTarget());
    }
    Address addressOfCalleeToken() const {
        return Address(BaselineFrameReg, BaselineFrame::offsetOfCalleeToken());
    }
    Address addressOfEnvironmentChain() const {
        return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfEnvironmentChain());
    }
    Address addressOfFlags() const {
        return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags());
    }
    Address addressOfReturnValue() const {
        return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfReturnValue());
    }
    Address addressOfArgsObj() const {
        return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfArgsObj());
    }
    Address addressOfStackValue(const StackValue* value) const {
        MOZ_ASSERT(value->kind() == StackValue::Stack);
        size_t slot = value - &stack[0];
        MOZ_ASSERT(slot < stackDepth());
        return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(nlocals() + slot));
    }
    Address addressOfScratchValue() const {
        return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfScratchValue());
    }

    void popValue(ValueOperand dest);

    void sync(StackValue* val);
    void syncStack(uint32_t uses);
    uint32_t numUnsyncedSlots();
    void popRegsAndSync(uint32_t uses);

    inline void assertSyncedStack() const {
        MOZ_ASSERT_IF(stackDepth() > 0, peek(-1)->kind() == StackValue::Stack);
    }

#ifdef DEBUG
    // Assert the state is valid before excuting "pc".
    void assertValidState(const BytecodeInfo& info);
#else
    inline void assertValidState(const BytecodeInfo& info) {}
#endif
};

} // namespace jit
} // namespace js

#endif /* jit_BaselineFrameInfo_h */
