/* 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/BaselineFrameInfo.h"

#include <algorithm>

#include "jit/BaselineIC.h"
#ifdef DEBUG
#  include "jit/BytecodeAnalysis.h"
#endif

#include "jit/BaselineFrameInfo-inl.h"
#include "jit/JitFrames.h"
#include "jit/MacroAssembler-inl.h"

using namespace js;
using namespace js::jit;

bool CompilerFrameInfo::init(TempAllocator& alloc) {
  // An extra slot is needed for global scopes because INITGLEXICAL (stack
  // depth 1) is compiled as a SETPROP (stack depth 2) on the global lexical
  // scope.
  size_t extra = script->isGlobalCode() ? 1 : 0;
  size_t nstack =
      std::max(script->nslots() - script->nfixed(), size_t(MinJITStackSize)) +
      extra;
  if (!stack.init(alloc, nstack)) {
    return false;
  }

  return true;
}

void CompilerFrameInfo::sync(StackValue* val) {
  switch (val->kind()) {
    case StackValue::Stack:
      break;
    case StackValue::LocalSlot:
      masm.pushValue(addressOfLocal(val->localSlot()));
      break;
    case StackValue::ArgSlot:
      masm.pushValue(addressOfArg(val->argSlot()));
      break;
    case StackValue::ThisSlot:
      masm.pushValue(addressOfThis());
      break;
    case StackValue::Register:
      masm.pushValue(val->reg());
      break;
    case StackValue::Constant:
      masm.pushValue(val->constant());
      break;
    default:
      MOZ_CRASH("Invalid kind");
  }

  val->setStack();
}

void CompilerFrameInfo::syncStack(uint32_t uses) {
  MOZ_ASSERT(uses <= stackDepth());

  uint32_t depth = stackDepth() - uses;

  for (uint32_t i = 0; i < depth; i++) {
    StackValue* current = &stack[i];
    sync(current);
  }
}

uint32_t CompilerFrameInfo::numUnsyncedSlots() {
  // Start at the bottom, find the first value that's not synced.
  uint32_t i = 0;
  for (; i < stackDepth(); i++) {
    if (peek(-int32_t(i + 1))->kind() == StackValue::Stack) {
      break;
    }
  }
  return i;
}

void CompilerFrameInfo::popValue(ValueOperand dest) {
  StackValue* val = peek(-1);

  switch (val->kind()) {
    case StackValue::Constant:
      masm.moveValue(val->constant(), dest);
      break;
    case StackValue::LocalSlot:
      masm.loadValue(addressOfLocal(val->localSlot()), dest);
      break;
    case StackValue::ArgSlot:
      masm.loadValue(addressOfArg(val->argSlot()), dest);
      break;
    case StackValue::ThisSlot:
      masm.loadValue(addressOfThis(), dest);
      break;
    case StackValue::Stack:
      masm.popValue(dest);
      break;
    case StackValue::Register:
      masm.moveValue(val->reg(), dest);
      break;
    default:
      MOZ_CRASH("Invalid kind");
  }

  // masm.popValue already adjusted the stack pointer, don't do it twice.
  pop(DontAdjustStack);
}

void CompilerFrameInfo::popRegsAndSync(uint32_t uses) {
  // x86 has only 3 Value registers. Only support 2 regs here for now,
  // so that there's always a scratch Value register for reg -> reg
  // moves.
  MOZ_ASSERT(uses > 0);
  MOZ_ASSERT(uses <= 2);
  MOZ_ASSERT(uses <= stackDepth());

  syncStack(uses);

  switch (uses) {
    case 1:
      popValue(R0);
      break;
    case 2: {
      // If the second value is in R1, move it to R2 so that it's not
      // clobbered by the first popValue.
      StackValue* val = peek(-2);
      if (val->kind() == StackValue::Register && val->reg() == R1) {
        masm.moveValue(R1, ValueOperand(R2));
        val->setRegister(R2);
      }
      popValue(R1);
      popValue(R0);
      break;
    }
    default:
      MOZ_CRASH("Invalid uses");
  }
  // On arm64, SP may be < PSP now (that's OK).
  // eg testcase: tests/bug1580246.js
}

void InterpreterFrameInfo::popRegsAndSync(uint32_t uses) {
  switch (uses) {
    case 1:
      popValue(R0);
      break;
    case 2: {
      popValue(R1);
      popValue(R0);
      break;
    }
    default:
      MOZ_CRASH("Invalid uses");
  }
  // On arm64, SP may be < PSP now (that's OK).
  // eg testcase: tests/backup-point-bug1315634.js
}

void InterpreterFrameInfo::bumpInterpreterICEntry() {
  masm.addPtr(Imm32(sizeof(ICEntry)), addressOfInterpreterICEntry());
}

void CompilerFrameInfo::storeStackValue(int32_t depth, const Address& dest,
                                        const ValueOperand& scratch) {
  const StackValue* source = peek(depth);
  switch (source->kind()) {
    case StackValue::Constant:
      masm.storeValue(source->constant(), dest);
      break;
    case StackValue::Register:
      masm.storeValue(source->reg(), dest);
      break;
    case StackValue::LocalSlot:
      masm.loadValue(addressOfLocal(source->localSlot()), scratch);
      masm.storeValue(scratch, dest);
      break;
    case StackValue::ArgSlot:
      masm.loadValue(addressOfArg(source->argSlot()), scratch);
      masm.storeValue(scratch, dest);
      break;
    case StackValue::ThisSlot:
      masm.loadValue(addressOfThis(), scratch);
      masm.storeValue(scratch, dest);
      break;
    case StackValue::Stack:
      masm.loadValue(addressOfStackValue(depth), scratch);
      masm.storeValue(scratch, dest);
      break;
    default:
      MOZ_CRASH("Invalid kind");
  }
}

#ifdef DEBUG
void CompilerFrameInfo::assertValidState(const BytecodeInfo& info) {
  // Check stack depth.
  MOZ_ASSERT(stackDepth() == info.stackDepth);

  // Start at the bottom, find the first value that's not synced.
  uint32_t i = 0;
  for (; i < stackDepth(); i++) {
    if (stack[i].kind() != StackValue::Stack) {
      break;
    }
  }

  // Assert all values on top of it are also not synced.
  for (; i < stackDepth(); i++) {
    MOZ_ASSERT(stack[i].kind() != StackValue::Stack);
  }

  // Assert every Value register is used by at most one StackValue.
  // R2 is used as scratch register by the compiler and FrameInfo,
  // so it shouldn't be used for StackValues.
  bool usedR0 = false, usedR1 = false;

  for (i = 0; i < stackDepth(); i++) {
    if (stack[i].kind() == StackValue::Register) {
      ValueOperand reg = stack[i].reg();
      if (reg == R0) {
        MOZ_ASSERT(!usedR0);
        usedR0 = true;
      } else if (reg == R1) {
        MOZ_ASSERT(!usedR1);
        usedR1 = true;
      } else {
        MOZ_CRASH("Invalid register");
      }
    }
  }
}
#endif
