/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * 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_Snapshot_h
#define jit_Snapshot_h

#include "mozilla/Attributes.h"

#include "jit/CompactBuffer.h"
#include "jit/IonTypes.h"
#include "jit/Registers.h"
#include "js/AllocPolicy.h"
#include "js/HashTable.h"
#include "js/TypeDecls.h"

namespace js {
class JS_PUBLIC_API GenericPrinter;

namespace jit {

class RValueAllocation;

// A Recover Value Allocation mirror what is known at compiled time as being the
// MIRType and the LAllocation.  This is read out of the snapshot to recover the
// value which would be there if this frame was an interpreter frame instead of
// an Ion frame.
//
// It is used with the SnapshotIterator to recover a Value from the stack,
// spilled registers or the list of constant of the compiled script.
//
// Unit tests are located in jsapi-tests/testJitRValueAlloc.cpp.
class RValueAllocation {
 public:
  // See RValueAllocation encoding in Snapshots.cpp
  enum Mode {
    CONSTANT = 0x00,
    CST_UNDEFINED = 0x01,
    CST_NULL = 0x02,
    DOUBLE_REG = 0x03,
    ANY_FLOAT_REG = 0x04,
    ANY_FLOAT_STACK = 0x05,
#if defined(JS_NUNBOX32)
    UNTYPED_REG_REG = 0x06,
    UNTYPED_REG_STACK = 0x07,
    UNTYPED_STACK_REG = 0x08,
    UNTYPED_STACK_STACK = 0x09,
#elif defined(JS_PUNBOX64)
    UNTYPED_REG = 0x06,
    UNTYPED_STACK = 0x07,
#endif

    // Recover instructions.
    RECOVER_INSTRUCTION = 0x0a,
    RI_WITH_DEFAULT_CST = 0x0b,

    // The JSValueType is packed in the Mode.
    TYPED_REG_MIN = 0x10,
    TYPED_REG_MAX = 0x1f,
    TYPED_REG = TYPED_REG_MIN,

    // The JSValueType is packed in the Mode.
    TYPED_STACK_MIN = 0x20,
    TYPED_STACK_MAX = 0x2f,
    TYPED_STACK = TYPED_STACK_MIN,

    // This mask can be used with any other valid mode. When this flag is
    // set on the mode, this inform the snapshot iterator that even if the
    // allocation is readable, the content of if might be incomplete unless
    // all side-effects are executed.
    RECOVER_SIDE_EFFECT_MASK = 0x80,

    // This mask represents the set of bits which can be used to encode a
    // value in a snapshot. The mode is used to determine how to interpret
    // the union of values and how to pack the value in memory.
    MODE_BITS_MASK = 0x17f,

    INVALID = 0x100,
  };

  enum { PACKED_TAG_MASK = 0x0f };

  // See Payload encoding in Snapshots.cpp
  enum PayloadType {
    PAYLOAD_NONE,
    PAYLOAD_INDEX,
    PAYLOAD_STACK_OFFSET,
    PAYLOAD_GPR,
    PAYLOAD_FPU,
    PAYLOAD_PACKED_TAG
  };

  struct Layout {
    PayloadType type1;
    PayloadType type2;
    const char* name;
  };

 private:
  Mode mode_;

  // Additional information to recover the content of the allocation.
  struct FloatRegisterBits {
    uint32_t data;
    bool operator==(const FloatRegisterBits& other) const {
      return data == other.data;
    }
    uint32_t code() const { return data; }
    const char* name() const {
      FloatRegister tmp = FloatRegister::FromCode(data);
      return tmp.name();
    }
  };

  union Payload {
    uint32_t index;
    int32_t stackOffset;
    Register gpr;
    FloatRegisterBits fpu;
    JSValueType type;

    Payload() : index(0) {
      static_assert(sizeof(index) == sizeof(Payload),
                    "All Payload bits are initialized.");
    }
  };

  Payload arg1_;
  Payload arg2_;

  static Payload payloadOfIndex(uint32_t index) {
    Payload p;
    p.index = index;
    return p;
  }
  static Payload payloadOfStackOffset(int32_t offset) {
    Payload p;
    p.stackOffset = offset;
    return p;
  }
  static Payload payloadOfRegister(Register reg) {
    Payload p;
    p.gpr = reg;
    return p;
  }
  static Payload payloadOfFloatRegister(FloatRegister reg) {
    Payload p;
    FloatRegisterBits b;
    b.data = reg.code();
    p.fpu = b;
    return p;
  }
  static Payload payloadOfValueType(JSValueType type) {
    Payload p;
    p.type = type;
    return p;
  }

  static const Layout& layoutFromMode(Mode mode);

  static void readPayload(CompactBufferReader& reader, PayloadType t,
                          uint8_t* mode, Payload* p);
  static void writePayload(CompactBufferWriter& writer, PayloadType t,
                           Payload p);
  static void writePadding(CompactBufferWriter& writer);
#ifdef JS_JITSPEW
  static void dumpPayload(GenericPrinter& out, PayloadType t, Payload p);
#endif
  static bool equalPayloads(PayloadType t, Payload lhs, Payload rhs);

  RValueAllocation(Mode mode, Payload a1, Payload a2)
      : mode_(mode), arg1_(a1), arg2_(a2) {}

  RValueAllocation(Mode mode, Payload a1) : mode_(mode), arg1_(a1) {
    arg2_.index = 0;
  }

  explicit RValueAllocation(Mode mode) : mode_(mode) {
    arg1_.index = 0;
    arg2_.index = 0;
  }

 public:
  RValueAllocation() : mode_(INVALID) {
    arg1_.index = 0;
    arg2_.index = 0;
  }

  // DOUBLE_REG
  static RValueAllocation Double(FloatRegister reg) {
    return RValueAllocation(DOUBLE_REG, payloadOfFloatRegister(reg));
  }

  // ANY_FLOAT_REG or ANY_FLOAT_STACK
  static RValueAllocation AnyFloat(FloatRegister reg) {
    return RValueAllocation(ANY_FLOAT_REG, payloadOfFloatRegister(reg));
  }
  static RValueAllocation AnyFloat(int32_t offset) {
    return RValueAllocation(ANY_FLOAT_STACK, payloadOfStackOffset(offset));
  }

  // TYPED_REG or TYPED_STACK
  static RValueAllocation Typed(JSValueType type, Register reg) {
    MOZ_ASSERT(type != JSVAL_TYPE_DOUBLE && type != JSVAL_TYPE_MAGIC &&
               type != JSVAL_TYPE_NULL && type != JSVAL_TYPE_UNDEFINED);
    return RValueAllocation(TYPED_REG, payloadOfValueType(type),
                            payloadOfRegister(reg));
  }
  static RValueAllocation Typed(JSValueType type, int32_t offset) {
    MOZ_ASSERT(type != JSVAL_TYPE_MAGIC && type != JSVAL_TYPE_NULL &&
               type != JSVAL_TYPE_UNDEFINED);
    return RValueAllocation(TYPED_STACK, payloadOfValueType(type),
                            payloadOfStackOffset(offset));
  }

  // UNTYPED
#if defined(JS_NUNBOX32)
  static RValueAllocation Untyped(Register type, Register payload) {
    return RValueAllocation(UNTYPED_REG_REG, payloadOfRegister(type),
                            payloadOfRegister(payload));
  }

  static RValueAllocation Untyped(Register type, int32_t payloadStackOffset) {
    return RValueAllocation(UNTYPED_REG_STACK, payloadOfRegister(type),
                            payloadOfStackOffset(payloadStackOffset));
  }

  static RValueAllocation Untyped(int32_t typeStackOffset, Register payload) {
    return RValueAllocation(UNTYPED_STACK_REG,
                            payloadOfStackOffset(typeStackOffset),
                            payloadOfRegister(payload));
  }

  static RValueAllocation Untyped(int32_t typeStackOffset,
                                  int32_t payloadStackOffset) {
    return RValueAllocation(UNTYPED_STACK_STACK,
                            payloadOfStackOffset(typeStackOffset),
                            payloadOfStackOffset(payloadStackOffset));
  }

#elif defined(JS_PUNBOX64)
  static RValueAllocation Untyped(Register reg) {
    return RValueAllocation(UNTYPED_REG, payloadOfRegister(reg));
  }

  static RValueAllocation Untyped(int32_t stackOffset) {
    return RValueAllocation(UNTYPED_STACK, payloadOfStackOffset(stackOffset));
  }
#endif

  // common constants.
  static RValueAllocation Undefined() {
    return RValueAllocation(CST_UNDEFINED);
  }
  static RValueAllocation Null() { return RValueAllocation(CST_NULL); }

  // CONSTANT's index
  static RValueAllocation ConstantPool(uint32_t index) {
    return RValueAllocation(CONSTANT, payloadOfIndex(index));
  }

  // Recover instruction's index
  static RValueAllocation RecoverInstruction(uint32_t index) {
    return RValueAllocation(RECOVER_INSTRUCTION, payloadOfIndex(index));
  }
  static RValueAllocation RecoverInstruction(uint32_t riIndex,
                                             uint32_t cstIndex) {
    return RValueAllocation(RI_WITH_DEFAULT_CST, payloadOfIndex(riIndex),
                            payloadOfIndex(cstIndex));
  }

  void setNeedSideEffect() {
    MOZ_ASSERT(!needSideEffect() && mode_ != INVALID);
    mode_ = Mode(mode_ | RECOVER_SIDE_EFFECT_MASK);
  }

  void writeHeader(CompactBufferWriter& writer, JSValueType type,
                   uint32_t regCode) const;

 public:
  static RValueAllocation read(CompactBufferReader& reader);
  void write(CompactBufferWriter& writer) const;

 public:
  bool valid() const { return mode_ != INVALID; }
  Mode mode() const { return Mode(mode_ & MODE_BITS_MASK); }
  bool needSideEffect() const { return mode_ & RECOVER_SIDE_EFFECT_MASK; }

  uint32_t index() const {
    MOZ_ASSERT(layoutFromMode(mode()).type1 == PAYLOAD_INDEX);
    return arg1_.index;
  }
  int32_t stackOffset() const {
    MOZ_ASSERT(layoutFromMode(mode()).type1 == PAYLOAD_STACK_OFFSET);
    return arg1_.stackOffset;
  }
  Register reg() const {
    MOZ_ASSERT(layoutFromMode(mode()).type1 == PAYLOAD_GPR);
    return arg1_.gpr;
  }
  FloatRegister fpuReg() const {
    MOZ_ASSERT(layoutFromMode(mode()).type1 == PAYLOAD_FPU);
    FloatRegisterBits b = arg1_.fpu;
    return FloatRegister::FromCode(b.data);
  }
  JSValueType knownType() const {
    MOZ_ASSERT(layoutFromMode(mode()).type1 == PAYLOAD_PACKED_TAG);
    return arg1_.type;
  }

  uint32_t index2() const {
    MOZ_ASSERT(layoutFromMode(mode()).type2 == PAYLOAD_INDEX);
    return arg2_.index;
  }
  int32_t stackOffset2() const {
    MOZ_ASSERT(layoutFromMode(mode()).type2 == PAYLOAD_STACK_OFFSET);
    return arg2_.stackOffset;
  }
  Register reg2() const {
    MOZ_ASSERT(layoutFromMode(mode()).type2 == PAYLOAD_GPR);
    return arg2_.gpr;
  }

 public:
#ifdef JS_JITSPEW
  void dump(GenericPrinter& out) const;
#endif

  bool operator==(const RValueAllocation& rhs) const {
    // Note, this equality compares the verbatim content of the payload,
    // which is made possible because we ensure that the payload content is
    // fully initialized during the creation.
    static_assert(sizeof(int32_t) == sizeof(Payload),
                  "All Payload bits are compared.");
    return mode_ == rhs.mode_ && arg1_.index == rhs.arg1_.index &&
           arg2_.index == rhs.arg2_.index;
  }

  HashNumber hash() const;

  struct Hasher {
    using Key = RValueAllocation;
    using Lookup = Key;
    static HashNumber hash(const Lookup& v) { return v.hash(); }
    static bool match(const Key& k, const Lookup& l) { return k == l; }
  };
};

class RecoverWriter;

// Collects snapshots in a contiguous buffer, which is copied into IonScript
// memory after code generation.
class SnapshotWriter {
  CompactBufferWriter writer_;
  CompactBufferWriter allocWriter_;

  // Map RValueAllocations to an offset in the allocWriter_ buffer.  This is
  // useful as value allocations are repeated frequently.
  using RVA = RValueAllocation;
  typedef HashMap<RVA, uint32_t, RVA::Hasher, SystemAllocPolicy> RValueAllocMap;
  RValueAllocMap allocMap_;

  // This is only used to assert sanity.
  uint32_t allocWritten_;

  // Used to report size of the snapshot in the spew messages.
  SnapshotOffset lastStart_;

 public:
  SnapshotWriter();

  SnapshotOffset startSnapshot(RecoverOffset recoverOffset, BailoutKind kind);
#ifdef TRACK_SNAPSHOTS
  void trackSnapshot(uint32_t pcOpcode, uint32_t mirOpcode, uint32_t mirId,
                     uint32_t lirOpcode, uint32_t lirId);
#endif
  [[nodiscard]] bool add(const RValueAllocation& slot);

  uint32_t allocWritten() const { return allocWritten_; }
  void endSnapshot();

  bool oom() const {
    return writer_.oom() || writer_.length() >= MAX_BUFFER_SIZE ||
           allocWriter_.oom() || allocWriter_.length() >= MAX_BUFFER_SIZE;
  }

  size_t listSize() const { return writer_.length(); }
  const uint8_t* listBuffer() const { return writer_.buffer(); }

  size_t RVATableSize() const { return allocWriter_.length(); }
  const uint8_t* RVATableBuffer() const { return allocWriter_.buffer(); }
};

class MNode;

class RecoverWriter {
  CompactBufferWriter writer_;

  uint32_t instructionCount_;
  uint32_t instructionsWritten_;

 public:
  SnapshotOffset startRecover(uint32_t instructionCount);

  void writeInstruction(const MNode* rp);

  void endRecover();

  size_t size() const { return writer_.length(); }
  const uint8_t* buffer() const { return writer_.buffer(); }

  bool oom() const {
    return writer_.oom() || writer_.length() >= MAX_BUFFER_SIZE;
  }
};

class RecoverReader;

// A snapshot reader reads the entries out of the compressed snapshot buffer in
// a script. These entries describe the equivalent interpreter frames at a given
// position in JIT code. Each entry is an Ion's value allocations, used to
// recover the corresponding Value from an Ion frame.
class SnapshotReader {
  CompactBufferReader reader_;
  CompactBufferReader allocReader_;
  const uint8_t* allocTable_;

  BailoutKind bailoutKind_;
  uint32_t allocRead_;           // Number of slots that have been read.
  RecoverOffset recoverOffset_;  // Offset of the recover instructions.

#ifdef TRACK_SNAPSHOTS
 private:
  uint32_t pcOpcode_;
  uint32_t mirOpcode_;
  uint32_t mirId_;
  uint32_t lirOpcode_;
  uint32_t lirId_;

 public:
  void readTrackSnapshot();
  void spewBailingFrom() const;
#endif

 private:
  void readSnapshotHeader();
  uint32_t readAllocationIndex();

 public:
  SnapshotReader(const uint8_t* snapshots, uint32_t offset,
                 uint32_t RVATableSize, uint32_t listSize);

  RValueAllocation readAllocation();
  void skipAllocation() { readAllocationIndex(); }

  BailoutKind bailoutKind() const { return bailoutKind_; }
  RecoverOffset recoverOffset() const { return recoverOffset_; }

  uint32_t numAllocationsRead() const { return allocRead_; }
  void resetNumAllocationsRead() { allocRead_ = 0; }
};

class MOZ_NON_PARAM RInstructionStorage {
  static constexpr size_t Size = 4 * sizeof(uint32_t);

  // This presumes all RInstructionStorage are safely void*-alignable.
  // RInstruction::readRecoverData asserts that no RInstruction subclass
  // has stricter alignment requirements than RInstructionStorage.
  static constexpr size_t Alignment = alignof(void*);

  alignas(Alignment) unsigned char mem[Size];

 public:
  const void* addr() const { return mem; }
  void* addr() { return mem; }

  RInstructionStorage() = default;

  // Making a copy of raw bytes holding a RInstruction instance would be a
  // strict aliasing violation: see bug 1269319 for an instance of bytewise
  // copying having caused crashes.
  RInstructionStorage(const RInstructionStorage&) = delete;
  RInstructionStorage& operator=(const RInstructionStorage& other) = delete;
};

class RInstruction;

class RecoverReader {
  CompactBufferReader reader_;

  // Number of encoded instructions.
  uint32_t numInstructions_;

  // Number of instruction read.
  uint32_t numInstructionsRead_;

  // Space is reserved as part of the RecoverReader to avoid allocations of
  // data which is needed to decode the current instruction.
  RInstructionStorage rawData_;

 private:
  void readRecoverHeader();
  void readInstruction();

 public:
  RecoverReader(SnapshotReader& snapshot, const uint8_t* recovers,
                uint32_t size);
  explicit RecoverReader(const RecoverReader& rr);
  RecoverReader& operator=(const RecoverReader& rr);

  uint32_t numInstructions() const { return numInstructions_; }
  uint32_t numInstructionsRead() const { return numInstructionsRead_; }

  bool moreInstructions() const {
    return numInstructionsRead_ < numInstructions_;
  }
  void nextInstruction() { readInstruction(); }

  const RInstruction* instruction() const {
    return reinterpret_cast<const RInstruction*>(rawData_.addr());
  }
};

}  // namespace jit
}  // namespace js

#endif /* jit_Snapshot_h */
