/* -*- 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/. */

#include "jit/Safepoints.h"

#include "mozilla/MathAlgorithms.h"

#include "jit/BitSet.h"
#include "jit/IonScript.h"
#include "jit/JitSpewer.h"
#include "jit/LIR.h"
#include "jit/SafepointIndex.h"

using namespace js;
using namespace jit;

using mozilla::FloorLog2;

SafepointWriter::SafepointWriter(uint32_t localSlotsSize,
                                 uint32_t argumentsSize)
    : localSlots_((localSlotsSize / sizeof(intptr_t)) +
                  1),  // Stack slot counts are inclusive.
      argumentSlots_(argumentsSize / sizeof(intptr_t)) {}

bool SafepointWriter::init(TempAllocator& alloc) {
  return localSlots_.init(alloc) && argumentSlots_.init(alloc);
}

uint32_t SafepointWriter::startEntry() {
  JitSpew(JitSpew_Safepoints,
          "Encoding safepoint (position %zu):", stream_.length());
  return uint32_t(stream_.length());
}

void SafepointWriter::writeOsiCallPointOffset(uint32_t osiCallPointOffset) {
  stream_.writeUnsigned(osiCallPointOffset);
}

static void WriteRegisterMask(CompactBufferWriter& stream,
                              PackedRegisterMask bits) {
  if (sizeof(PackedRegisterMask) == 1) {
    stream.writeByte(bits);
  } else {
    MOZ_ASSERT(sizeof(PackedRegisterMask) <= 4);
    stream.writeUnsigned(bits);
  }
}

static PackedRegisterMask ReadRegisterMask(CompactBufferReader& stream) {
  if (sizeof(PackedRegisterMask) == 1) {
    return stream.readByte();
  }
  MOZ_ASSERT(sizeof(PackedRegisterMask) <= 4);
  return stream.readUnsigned();
}

static void WriteFloatRegisterMask(CompactBufferWriter& stream,
                                   FloatRegisters::SetType bits) {
  switch (sizeof(FloatRegisters::SetType)) {
#ifdef JS_CODEGEN_ARM64
    case 16:
      stream.writeUnsigned64(bits.low());
      stream.writeUnsigned64(bits.high());
      break;
#else
    case 1:
      stream.writeByte(bits);
      break;
    case 4:
      stream.writeUnsigned(bits);
      break;
    case 8:
      stream.writeUnsigned64(bits);
      break;
#endif
    default:
      MOZ_CRASH("WriteFloatRegisterMask: unexpected size");
  }
}

static FloatRegisters::SetType ReadFloatRegisterMask(
    CompactBufferReader& stream) {
  switch (sizeof(FloatRegisters::SetType)) {
#ifdef JS_CODEGEN_ARM64
    case 16: {
      uint64_t low = stream.readUnsigned64();
      uint64_t high = stream.readUnsigned64();
      return Bitset128(high, low);
    }
#else
    case 1:
      return stream.readByte();
    case 2:
    case 3:
    case 4:
      return stream.readUnsigned();
    case 8:
      return stream.readUnsigned64();
#endif
    default:
      MOZ_CRASH("ReadFloatRegisterMask: unexpected size");
  }
}

void SafepointWriter::writeGcRegs(LSafepoint* safepoint) {
  LiveGeneralRegisterSet gc(safepoint->gcRegs());
  LiveGeneralRegisterSet spilledGpr(safepoint->liveRegs().gprs());
  LiveFloatRegisterSet spilledFloat(safepoint->liveRegs().fpus());
  LiveGeneralRegisterSet slots(safepoint->slotsOrElementsRegs());
  LiveGeneralRegisterSet valueRegs;

  WriteRegisterMask(stream_, spilledGpr.bits());
  if (!spilledGpr.empty()) {
    WriteRegisterMask(stream_, gc.bits());
    WriteRegisterMask(stream_, slots.bits());

#ifdef JS_PUNBOX64
    valueRegs = safepoint->valueRegs();
    WriteRegisterMask(stream_, valueRegs.bits());
#endif
  }

  // GC registers are a subset of the spilled registers.
  MOZ_ASSERT((valueRegs.bits() & ~spilledGpr.bits()) == 0);
  MOZ_ASSERT((gc.bits() & ~spilledGpr.bits()) == 0);

  WriteFloatRegisterMask(stream_, spilledFloat.bits());

#ifdef JS_JITSPEW
  if (JitSpewEnabled(JitSpew_Safepoints)) {
    for (GeneralRegisterForwardIterator iter(spilledGpr); iter.more(); ++iter) {
      const char* type = gc.has(*iter)          ? "gc"
                         : slots.has(*iter)     ? "slots"
                         : valueRegs.has(*iter) ? "value"
                                                : "any";
      JitSpew(JitSpew_Safepoints, "    %s reg: %s", type, (*iter).name());
    }
    for (FloatRegisterForwardIterator iter(spilledFloat); iter.more(); ++iter) {
      JitSpew(JitSpew_Safepoints, "    float reg: %s", (*iter).name());
    }
  }
#endif
}

static void WriteBitset(const BitSet& set, CompactBufferWriter& stream) {
  size_t count = set.rawLength();
  const uint32_t* words = set.raw();
  for (size_t i = 0; i < count; i++) {
    stream.writeUnsigned(words[i]);
  }
}

static void MapSlotsToBitset(BitSet& stackSet, BitSet& argumentSet,
                             CompactBufferWriter& stream,
                             const LSafepoint::SlotList& slots) {
  stackSet.clear();
  argumentSet.clear();

  for (uint32_t i = 0; i < slots.length(); i++) {
    // Slots are represented at a distance from |fp|. We divide by the
    // pointer size, since we only care about pointer-sized/aligned slots
    // here.
    MOZ_ASSERT(slots[i].slot % sizeof(intptr_t) == 0);
    size_t index = slots[i].slot / sizeof(intptr_t);
    (slots[i].stack ? stackSet : argumentSet).insert(index);
  }

  WriteBitset(stackSet, stream);
  WriteBitset(argumentSet, stream);
}

void SafepointWriter::writeGcSlots(LSafepoint* safepoint) {
  LSafepoint::SlotList& slots = safepoint->gcSlots();

#ifdef JS_JITSPEW
  for (uint32_t i = 0; i < slots.length(); i++) {
    JitSpew(JitSpew_Safepoints, "    gc slot: %u", slots[i].slot);
  }
#endif

  MapSlotsToBitset(localSlots_, argumentSlots_, stream_, slots);
}

void SafepointWriter::writeSlotsOrElementsSlots(LSafepoint* safepoint) {
  LSafepoint::SlotList& slots = safepoint->slotsOrElementsSlots();

  stream_.writeUnsigned(slots.length());

  for (uint32_t i = 0; i < slots.length(); i++) {
    if (!slots[i].stack) {
      MOZ_CRASH();
    }
#ifdef JS_JITSPEW
    JitSpew(JitSpew_Safepoints, "    slots/elements slot: %u", slots[i].slot);
#endif
    stream_.writeUnsigned(slots[i].slot);
  }
}

#ifdef JS_PUNBOX64
void SafepointWriter::writeValueSlots(LSafepoint* safepoint) {
  LSafepoint::SlotList& slots = safepoint->valueSlots();

#  ifdef JS_JITSPEW
  for (uint32_t i = 0; i < slots.length(); i++) {
    JitSpew(JitSpew_Safepoints, "    gc value: %u", slots[i].slot);
  }
#  endif

  MapSlotsToBitset(localSlots_, argumentSlots_, stream_, slots);
}
#endif

#if defined(JS_JITSPEW) && defined(JS_NUNBOX32)
static void DumpNunboxPart(const LAllocation& a) {
  Fprinter& out = JitSpewPrinter();
  if (a.isStackSlot()) {
    out.printf("stack %d", a.toStackSlot()->slot());
  } else if (a.isArgument()) {
    out.printf("arg %d", a.toArgument()->index());
  } else {
    out.printf("reg %s", a.toGeneralReg()->reg().name());
  }
}
#endif  // DEBUG

// Nunbox part encoding:
//
// Reg = 000
// Stack = 001
// Arg = 010
//
// [vwu] nentries:
//    uint16_t:  tttp ppXX XXXY YYYY
//
//     If ttt = Reg, type is reg XXXXX
//     If ppp = Reg, payload is reg YYYYY
//
//     If ttt != Reg, type is:
//          XXXXX if not 11111, otherwise followed by [vwu]
//     If ppp != Reg, payload is:
//          YYYYY if not 11111, otherwise followed by [vwu]
//
enum NunboxPartKind { Part_Reg, Part_Stack, Part_Arg };

static const uint32_t PART_KIND_BITS = 3;
static const uint32_t PART_KIND_MASK = (1 << PART_KIND_BITS) - 1;
static const uint32_t PART_INFO_BITS = 5;
static const uint32_t PART_INFO_MASK = (1 << PART_INFO_BITS) - 1;

static const uint32_t MAX_INFO_VALUE = (1 << PART_INFO_BITS) - 1;
static const uint32_t TYPE_KIND_SHIFT = 16 - PART_KIND_BITS;
static const uint32_t PAYLOAD_KIND_SHIFT = TYPE_KIND_SHIFT - PART_KIND_BITS;
static const uint32_t TYPE_INFO_SHIFT = PAYLOAD_KIND_SHIFT - PART_INFO_BITS;
static const uint32_t PAYLOAD_INFO_SHIFT = TYPE_INFO_SHIFT - PART_INFO_BITS;

static_assert(PAYLOAD_INFO_SHIFT == 0);

#ifdef JS_NUNBOX32
static inline NunboxPartKind AllocationToPartKind(const LAllocation& a) {
  if (a.isRegister()) {
    return Part_Reg;
  }
  if (a.isStackSlot()) {
    return Part_Stack;
  }
  MOZ_ASSERT(a.isArgument());
  return Part_Arg;
}

// gcc 4.5 doesn't actually inline CanEncodeInfoInHeader when only
// using the "inline" keyword, and miscompiles the function as well
// when doing block reordering with branch prediction information.
// See bug 799295 comment 71.
static MOZ_ALWAYS_INLINE bool CanEncodeInfoInHeader(const LAllocation& a,
                                                    uint32_t* out) {
  if (a.isGeneralReg()) {
    *out = a.toGeneralReg()->reg().code();
    return true;
  }

  if (a.isStackSlot()) {
    *out = a.toStackSlot()->slot();
  } else {
    *out = a.toArgument()->index();
  }

  return *out < MAX_INFO_VALUE;
}

void SafepointWriter::writeNunboxParts(LSafepoint* safepoint) {
  LSafepoint::NunboxList& entries = safepoint->nunboxParts();

#  ifdef JS_JITSPEW
  if (JitSpewEnabled(JitSpew_Safepoints)) {
    for (uint32_t i = 0; i < entries.length(); i++) {
      SafepointNunboxEntry& entry = entries[i];
      if (entry.type.isUse() || entry.payload.isUse()) {
        continue;
      }
      JitSpewHeader(JitSpew_Safepoints);
      Fprinter& out = JitSpewPrinter();
      out.printf("    nunbox (type in ");
      DumpNunboxPart(entry.type);
      out.printf(", payload in ");
      DumpNunboxPart(entry.payload);
      out.printf(")\n");
    }
  }
#  endif

  // Safepoints are permitted to have partially filled in entries for nunboxes,
  // provided that only the type is live and not the payload. Omit these from
  // the written safepoint.

  size_t pos = stream_.length();
  stream_.writeUnsigned(entries.length());

  size_t count = 0;
  for (size_t i = 0; i < entries.length(); i++) {
    SafepointNunboxEntry& entry = entries[i];

    if (entry.payload.isUse()) {
      // No allocation associated with the payload.
      continue;
    }

    if (entry.type.isUse()) {
      // No allocation associated with the type. Look for another
      // safepoint entry with an allocation for the type.
      entry.type = safepoint->findTypeAllocation(entry.typeVreg);
      if (entry.type.isUse()) {
        continue;
      }
    }

    count++;

    uint16_t header = 0;

    header |= (AllocationToPartKind(entry.type) << TYPE_KIND_SHIFT);
    header |= (AllocationToPartKind(entry.payload) << PAYLOAD_KIND_SHIFT);

    uint32_t typeVal;
    bool typeExtra = !CanEncodeInfoInHeader(entry.type, &typeVal);
    if (!typeExtra) {
      header |= (typeVal << TYPE_INFO_SHIFT);
    } else {
      header |= (MAX_INFO_VALUE << TYPE_INFO_SHIFT);
    }

    uint32_t payloadVal;
    bool payloadExtra = !CanEncodeInfoInHeader(entry.payload, &payloadVal);
    if (!payloadExtra) {
      header |= (payloadVal << PAYLOAD_INFO_SHIFT);
    } else {
      header |= (MAX_INFO_VALUE << PAYLOAD_INFO_SHIFT);
    }

    stream_.writeFixedUint16_t(header);
    if (typeExtra) {
      stream_.writeUnsigned(typeVal);
    }
    if (payloadExtra) {
      stream_.writeUnsigned(payloadVal);
    }
  }

  // Update the stream with the actual number of safepoint entries written.
  stream_.writeUnsignedAt(pos, count, entries.length());
}
#endif

void SafepointWriter::encode(LSafepoint* safepoint) {
  uint32_t safepointOffset = startEntry();

  MOZ_ASSERT(safepoint->osiCallPointOffset());

  writeOsiCallPointOffset(safepoint->osiCallPointOffset());
  writeGcRegs(safepoint);
  writeGcSlots(safepoint);

#ifdef JS_PUNBOX64
  writeValueSlots(safepoint);
#else
  writeNunboxParts(safepoint);
#endif

  writeSlotsOrElementsSlots(safepoint);

  endEntry();
  safepoint->setOffset(safepointOffset);
}

void SafepointWriter::endEntry() {
  JitSpew(JitSpew_Safepoints, "    -- entry ended at %u",
          uint32_t(stream_.length()));
}

SafepointReader::SafepointReader(IonScript* script, const SafepointIndex* si)
    : stream_(script->safepoints() + si->safepointOffset(),
              script->safepoints() + script->safepointsSize()),
      localSlots_((script->localSlotsSize() / sizeof(intptr_t)) +
                  1),  // Stack slot counts are inclusive.
      argumentSlots_(script->argumentSlotsSize() / sizeof(intptr_t)),
      nunboxSlotsRemaining_(0),
      slotsOrElementsSlotsRemaining_(0) {
  osiCallPointOffset_ = stream_.readUnsigned();

  // gcSpills is a subset of allGprSpills.
  allGprSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
  if (allGprSpills_.empty()) {
    gcSpills_ = allGprSpills_;
    valueSpills_ = allGprSpills_;
    slotsOrElementsSpills_ = allGprSpills_;
  } else {
    gcSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
    slotsOrElementsSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
#ifdef JS_PUNBOX64
    valueSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
#endif
  }

  allFloatSpills_ = FloatRegisterSet(ReadFloatRegisterMask(stream_));

  advanceFromGcRegs();
}

uint32_t SafepointReader::osiReturnPointOffset() const {
  return osiCallPointOffset_ + Assembler::PatchWrite_NearCallSize();
}

CodeLocationLabel SafepointReader::InvalidationPatchPoint(
    IonScript* script, const SafepointIndex* si) {
  SafepointReader reader(script, si);

  return CodeLocationLabel(script->method(),
                           CodeOffset(reader.osiCallPointOffset()));
}

void SafepointReader::advanceFromGcRegs() {
  currentSlotChunk_ = 0;
  nextSlotChunkNumber_ = 0;
  currentSlotsAreStack_ = true;
}

bool SafepointReader::getSlotFromBitmap(SafepointSlotEntry* entry) {
  while (currentSlotChunk_ == 0) {
    // Are there any more chunks to read?
    if (currentSlotsAreStack_) {
      if (nextSlotChunkNumber_ == BitSet::RawLengthForBits(localSlots_)) {
        nextSlotChunkNumber_ = 0;
        currentSlotsAreStack_ = false;
        continue;
      }
    } else if (nextSlotChunkNumber_ ==
               BitSet::RawLengthForBits(argumentSlots_)) {
      return false;
    }

    // Yes, read the next chunk.
    currentSlotChunk_ = stream_.readUnsigned();
    nextSlotChunkNumber_++;
  }

  // The current chunk still has bits in it, so get the next bit, then mask
  // it out of the slot chunk.
  uint32_t bit = FloorLog2(currentSlotChunk_);
  currentSlotChunk_ &= ~(1 << bit);

  // Return the slot, and re-scale it by the pointer size, reversing the
  // transformation in MapSlotsToBitset.
  entry->stack = currentSlotsAreStack_;
  entry->slot = (((nextSlotChunkNumber_ - 1) * BitSet::BitsPerWord) + bit) *
                sizeof(intptr_t);
  return true;
}

bool SafepointReader::getGcSlot(SafepointSlotEntry* entry) {
  if (getSlotFromBitmap(entry)) {
    return true;
  }
  advanceFromGcSlots();
  return false;
}

void SafepointReader::advanceFromGcSlots() {
  // No, reset the counter.
  currentSlotChunk_ = 0;
  nextSlotChunkNumber_ = 0;
  currentSlotsAreStack_ = true;
#ifdef JS_NUNBOX32
  // Nunbox slots are next.
  nunboxSlotsRemaining_ = stream_.readUnsigned();
#else
  // Value slots are next.
#endif
}

bool SafepointReader::getValueSlot(SafepointSlotEntry* entry) {
  if (getSlotFromBitmap(entry)) {
    return true;
  }
  advanceFromNunboxOrValueSlots();
  return false;
}

static inline LAllocation PartFromStream(CompactBufferReader& stream,
                                         NunboxPartKind kind, uint32_t info) {
  if (kind == Part_Reg) {
    return LGeneralReg(Register::FromCode(info));
  }

  if (info == MAX_INFO_VALUE) {
    info = stream.readUnsigned();
  }

  if (kind == Part_Stack) {
    return LStackSlot(info);
  }

  MOZ_ASSERT(kind == Part_Arg);
  return LArgument(info);
}

bool SafepointReader::getNunboxSlot(LAllocation* type, LAllocation* payload) {
  if (!nunboxSlotsRemaining_--) {
    advanceFromNunboxOrValueSlots();
    return false;
  }

  uint16_t header = stream_.readFixedUint16_t();
  NunboxPartKind typeKind =
      (NunboxPartKind)((header >> TYPE_KIND_SHIFT) & PART_KIND_MASK);
  NunboxPartKind payloadKind =
      (NunboxPartKind)((header >> PAYLOAD_KIND_SHIFT) & PART_KIND_MASK);
  uint32_t typeInfo = (header >> TYPE_INFO_SHIFT) & PART_INFO_MASK;
  uint32_t payloadInfo = (header >> PAYLOAD_INFO_SHIFT) & PART_INFO_MASK;

  *type = PartFromStream(stream_, typeKind, typeInfo);
  *payload = PartFromStream(stream_, payloadKind, payloadInfo);
  return true;
}

void SafepointReader::advanceFromNunboxOrValueSlots() {
  slotsOrElementsSlotsRemaining_ = stream_.readUnsigned();
}

bool SafepointReader::getSlotsOrElementsSlot(SafepointSlotEntry* entry) {
  if (!slotsOrElementsSlotsRemaining_--) {
    return false;
  }
  entry->stack = true;
  entry->slot = stream_.readUnsigned();
  return true;
}
