/* -*- 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_Disassembler_h
#define jit_Disassembler_h

#include "jit/MacroAssembler.h"
#include "jit/Registers.h"

namespace js {
namespace jit {

namespace Disassembler {

class ComplexAddress {
    int32_t disp_;
    Register::Encoding base_ : 8;
    Register::Encoding index_ : 8;
    int8_t scale_; // log2 encoding
    bool isPCRelative_;

  public:
    ComplexAddress()
      : disp_(0),
        base_(Registers::Invalid),
        index_(Registers::Invalid),
        scale_(0),
        isPCRelative_(false)
    {
        MOZ_ASSERT(*this == *this);
    }

    ComplexAddress(int32_t disp, Register::Encoding base)
      : disp_(disp),
        base_(base),
        index_(Registers::Invalid),
        scale_(0),
        isPCRelative_(false)
    {
        MOZ_ASSERT(*this == *this);
        MOZ_ASSERT(base != Registers::Invalid);
        MOZ_ASSERT(base_ == base);
    }

    ComplexAddress(int32_t disp, Register::Encoding base, Register::Encoding index, int scale)
      : disp_(disp),
        base_(base),
        index_(index),
        scale_(scale),
        isPCRelative_(false)
    {
        MOZ_ASSERT(scale >= 0 && scale < 4);
        MOZ_ASSERT_IF(index == Registers::Invalid, scale == 0);
        MOZ_ASSERT(*this == *this);
        MOZ_ASSERT(base_ == base);
        MOZ_ASSERT(index_ == index);
    }

    explicit ComplexAddress(const void* addr)
      : disp_(static_cast<uint32_t>(reinterpret_cast<uintptr_t>(addr))),
        base_(Registers::Invalid),
        index_(Registers::Invalid),
        scale_(0),
        isPCRelative_(false)
    {
        MOZ_ASSERT(*this == *this);
        MOZ_ASSERT(reinterpret_cast<const void*>(uintptr_t(disp_)) == addr);
    }

    explicit ComplexAddress(const Operand& op) {
#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
        switch (op.kind()) {
          case Operand::MEM_REG_DISP:
            *this = ComplexAddress(op.disp(), op.base());
            return;
          case Operand::MEM_SCALE:
            *this = ComplexAddress(op.disp(), op.base(), op.index(), op.scale());
            return;
          case Operand::MEM_ADDRESS32:
            *this = ComplexAddress(op.address());
            return;
          default:
            break;
        }
#endif
        MOZ_CRASH("Unexpected Operand kind");
    }

    bool isPCRelative() const {
        return isPCRelative_;
    }

    int32_t disp() const {
        return disp_;
    }

    bool hasBase() const {
        return base_ != Registers::Invalid;
    }

    Register::Encoding base() const {
        MOZ_ASSERT(hasBase());
        return base_;
    }

    bool hasIndex() const {
        return index_ != Registers::Invalid;
    }

    Register::Encoding index() const {
        MOZ_ASSERT(hasIndex());
        return index_;
    }

    uint32_t scale() const {
        return scale_;
    }

#ifdef DEBUG
    bool operator==(const ComplexAddress& other) const;
    bool operator!=(const ComplexAddress& other) const;
#endif
};

// An operand other than a memory operand -- a register or an immediate.
class OtherOperand {
  public:
    enum Kind {
        Imm,
        GPR,
        FPR,
    };

  private:
    Kind kind_;
    union {
        int32_t imm;
        Register::Encoding gpr;
        FloatRegister::Encoding fpr;
    } u_;

  public:
    OtherOperand()
      : kind_(Imm)
    {
        u_.imm = 0;
        MOZ_ASSERT(*this == *this);
    }

    explicit OtherOperand(int32_t imm)
      : kind_(Imm)
    {
        u_.imm = imm;
        MOZ_ASSERT(*this == *this);
    }

    explicit OtherOperand(Register::Encoding gpr)
      : kind_(GPR)
    {
        u_.gpr = gpr;
        MOZ_ASSERT(*this == *this);
    }

    explicit OtherOperand(FloatRegister::Encoding fpr)
      : kind_(FPR)
    {
        u_.fpr = fpr;
        MOZ_ASSERT(*this == *this);
    }

    Kind kind() const {
        return kind_;
    }

    int32_t imm() const {
        MOZ_ASSERT(kind_ == Imm);
        return u_.imm;
    }

    Register::Encoding gpr() const {
        MOZ_ASSERT(kind_ == GPR);
        return u_.gpr;
    }

    FloatRegister::Encoding fpr() const {
        MOZ_ASSERT(kind_ == FPR);
        return u_.fpr;
    }

#ifdef DEBUG
    bool operator==(const OtherOperand& other) const;
    bool operator!=(const OtherOperand& other) const;
#endif
};

class HeapAccess {
  public:
    enum Kind {
        Unknown,
        Load,       // any bits not covered by the load are zeroed
        LoadSext32, // like Load, but sign-extend to 32 bits
        LoadSext64, // like Load, but sign-extend to 64 bits
        Store
    };

  private:
    Kind kind_;
    size_t size_; // The number of bytes of memory accessed
    ComplexAddress address_;
    OtherOperand otherOperand_;

  public:
    HeapAccess()
      : kind_(Unknown),
        size_(0)
    {
        MOZ_ASSERT(*this == *this);
    }

    HeapAccess(Kind kind, size_t size, const ComplexAddress& address, const OtherOperand& otherOperand)
      : kind_(kind),
        size_(size),
        address_(address),
        otherOperand_(otherOperand)
    {
        MOZ_ASSERT(kind != Unknown);
        MOZ_ASSERT_IF(kind == LoadSext32, otherOperand.kind() != OtherOperand::FPR);
        MOZ_ASSERT_IF(kind == Load || kind == LoadSext32, otherOperand.kind() != OtherOperand::Imm);
        MOZ_ASSERT(*this == *this);
    }

    Kind kind() const {
        return kind_;
    }

    size_t size() const {
        MOZ_ASSERT(kind_ != Unknown);
        return size_;
    }

    const ComplexAddress& address() const {
        return address_;
    }

    const OtherOperand& otherOperand() const {
        return otherOperand_;
    }

#ifdef DEBUG
    bool operator==(const HeapAccess& other) const;
    bool operator!=(const HeapAccess& other) const;
#endif
};

MOZ_COLD uint8_t* DisassembleHeapAccess(uint8_t* ptr, HeapAccess* access);

#ifdef DEBUG
void DumpHeapAccess(const HeapAccess& access);

inline void
VerifyHeapAccess(uint8_t* begin, uint8_t* end, const HeapAccess& expected)
{
    HeapAccess disassembled;
    uint8_t* e = DisassembleHeapAccess(begin, &disassembled);
    MOZ_ASSERT(e == end);
    MOZ_ASSERT(disassembled == expected);
}
#endif

} // namespace Disassembler

} // namespace jit
} // namespace js

#endif /* jit_Disassembler_h */