/* -*- 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_BaselineFrame_h
#define jit_BaselineFrame_h

#include <algorithm>

#include "jit/CalleeToken.h"
#include "jit/JitFrames.h"
#include "jit/ScriptFromCalleeToken.h"
#include "vm/Stack.h"

namespace js {
namespace jit {

class ICEntry;
class ICScript;
class JSJitFrameIter;

// The stack looks like this, fp is the frame pointer:
//
// fp+y   arguments
// fp  => JitFrameLayout (frame header)
// fp-x   BaselineFrame
//        locals
//        stack values

class BaselineFrame {
 public:
  enum Flags : uint32_t {
    // The frame has a valid return value. See also InterpreterFrame::HAS_RVAL.
    HAS_RVAL = 1 << 0,

    // The frame is running in the Baseline interpreter instead of JIT.
    RUNNING_IN_INTERPRETER = 1 << 1,

    // An initial environment has been pushed on the environment chain for
    // function frames that need a CallObject or eval frames that need a
    // VarEnvironmentObject.
    HAS_INITIAL_ENV = 1 << 2,

    // Frame has an arguments object, argsObj_.
    HAS_ARGS_OBJ = 1 << 4,

    // See InterpreterFrame::PREV_UP_TO_DATE.
    PREV_UP_TO_DATE = 1 << 5,

    // Frame has execution observed by a Debugger.
    //
    // See comment above 'isDebuggee' in vm/Realm.h for explanation
    // of invariants of debuggee compartments, scripts, and frames.
    DEBUGGEE = 1 << 6,

    // Frame is executing Realm-independent Jitcode, which requires
    // a valid interpreterScript_ field.
    REALM_INDEPENDENT = 1 << 7,
  };

 protected:  // Silence Clang warning about unused private fields.
  // The fields below are only valid if RUNNING_IN_INTERPRETER or
  // isRealmIndependent().
  JSScript* interpreterScript_;
  // The fields below are only valid if RUNNING_IN_INTERPRETER.
  jsbytecode* interpreterPC_;
  ICEntry* interpreterICEntry_;

  JSObject* envChain_;        // Environment chain (always initialized).
  ICScript* icScript_;        // IC script (initialized if Warp is enabled).
  ArgumentsObject* argsObj_;  // If HAS_ARGS_OBJ, the arguments object.

  // We need to split the Value into 2 fields of 32 bits, otherwise the C++
  // compiler may add some padding between the fields.
  uint32_t loScratchValue_;
  uint32_t hiScratchValue_;
  uint32_t flags_;
#ifdef DEBUG
  // Size of the frame. Stored in DEBUG builds when calling into C++. This is
  // BaselineFrame::Size() + the size of the local and expression stack Values.
  //
  // We don't store this in release builds because it's redundant with the frame
  // size computed from the frame pointers. In debug builds it's still useful
  // for assertions.
  uint32_t debugFrameSize_;
#else
  uint32_t unused_;
#endif
  uint32_t loReturnValue_;  // If HAS_RVAL, the frame's return value.
  uint32_t hiReturnValue_;

 public:
  [[nodiscard]] bool initForOsr(InterpreterFrame* fp, uint32_t numStackValues);

#ifdef DEBUG
  uint32_t debugFrameSize() const { return debugFrameSize_; }
  void setDebugFrameSize(uint32_t frameSize) { debugFrameSize_ = frameSize; }
#endif

  JSObject* environmentChain() const { return envChain_; }
  void setEnvironmentChain(JSObject* envChain) { envChain_ = envChain; }

  template <typename SpecificEnvironment>
  inline void pushOnEnvironmentChain(SpecificEnvironment& env);
  template <typename SpecificEnvironment>
  inline void popOffEnvironmentChain();
  inline void replaceInnermostEnvironment(EnvironmentObject& env);

  CalleeToken calleeToken() const { return framePrefix()->calleeToken(); }
  void replaceCalleeToken(CalleeToken token) {
    framePrefix()->replaceCalleeToken(token);
  }
  bool isConstructing() const {
    return CalleeTokenIsConstructing(calleeToken());
  }
  JSScript* script() const {
    return MaybeForwardedScriptFromCalleeToken(calleeToken());
  }
  JSFunction* callee() const { return CalleeTokenToFunction(calleeToken()); }
  Value calleev() const { return ObjectValue(*callee()); }

  size_t numValueSlots(size_t frameSize) const {
    MOZ_ASSERT(frameSize == debugFrameSize());

    MOZ_ASSERT(frameSize >= BaselineFrame::Size());
    frameSize -= BaselineFrame::Size();

    MOZ_ASSERT((frameSize % sizeof(Value)) == 0);
    return frameSize / sizeof(Value);
  }

  Value newTarget() const {
    MOZ_ASSERT(isFunctionFrame());
    MOZ_ASSERT(!callee()->isArrow());

    if (isConstructing()) {
      unsigned pushedArgs = std::max(numFormalArgs(), numActualArgs());
      return argv()[pushedArgs];
    }
    return UndefinedValue();
  }

#ifdef DEBUG
  size_t debugNumValueSlots() const { return numValueSlots(debugFrameSize()); }
#endif

  Value* valueSlot(size_t slot) const {
#ifndef ENABLE_PORTABLE_BASELINE_INTERP
    // Assert that we're within the frame, but only if the "debug
    // frame size" has been set. Ordinarily if we are in C++ code
    // looking upward at a baseline frame, it will be, because it is
    // set for the *previous* frame when we push an exit frame and
    // call back into C++ from generated baseline code. However, the
    // portable baseline interpreter uses accessors on BaselineFrame
    // directly within the active frame and so the "debug frame size"
    // hasn't been set (and it would be expensive to constantly update
    // it). Because this is only used for assertions, and is not
    // needed for correctness, we can disable this check below when
    // PBL is enabled.
    MOZ_ASSERT(slot < debugNumValueSlots());
#endif
    return (Value*)this - (slot + 1);
  }

  static size_t frameSizeForNumValueSlots(size_t numValueSlots) {
    return BaselineFrame::Size() + numValueSlots * sizeof(Value);
  }

  Value& unaliasedFormal(
      unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) const {
    MOZ_ASSERT(i < numFormalArgs());
    MOZ_ASSERT_IF(checkAliasing, !script()->argsObjAliasesFormals() &&
                                     !script()->formalIsAliased(i));
    return argv()[i];
  }

  Value& unaliasedActual(
      unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) const {
    MOZ_ASSERT(i < numActualArgs());
    MOZ_ASSERT_IF(checkAliasing, !script()->argsObjAliasesFormals());
    MOZ_ASSERT_IF(checkAliasing && i < numFormalArgs(),
                  !script()->formalIsAliased(i));
    return argv()[i];
  }

  Value& unaliasedLocal(uint32_t i) const {
    MOZ_ASSERT(i < script()->nfixed());
    return *valueSlot(i);
  }

  unsigned numActualArgs() const { return framePrefix()->numActualArgs(); }
  unsigned numFormalArgs() const { return script()->function()->nargs(); }
  Value& thisArgument() const {
    MOZ_ASSERT(isFunctionFrame());
    return framePrefix()->thisv();
  }
  Value* argv() const { return framePrefix()->actualArgs(); }

  [[nodiscard]] bool saveGeneratorSlots(JSContext* cx, unsigned nslots,
                                        ArrayObject* dest) const;

 public:
  void prepareForBaselineInterpreterToJitOSR() {
    // Clearing the RUNNING_IN_INTERPRETER flag is sufficient, but we also null
    // out the interpreter fields to ensure we don't use stale values.
    flags_ &= ~RUNNING_IN_INTERPRETER;
    interpreterPC_ = nullptr;
  }

 private:
  bool uninlineIsProfilerSamplingEnabled(JSContext* cx);

 public:
  // Switch a JIT frame on the stack to Interpreter mode. The caller is
  // responsible for patching the return address into this frame to a location
  // in the interpreter code. Also assert profiler sampling has been suppressed
  // so the sampler thread doesn't see an inconsistent state while we are
  // patching frames.
  void switchFromJitToInterpreter(JSContext* cx, jsbytecode* pc) {
    MOZ_ASSERT(!uninlineIsProfilerSamplingEnabled(cx));
    MOZ_ASSERT(!runningInInterpreter());
    flags_ |= RUNNING_IN_INTERPRETER;
    setInterpreterFields(pc);
  }
  void switchFromJitToInterpreterAtPrologue(JSContext* cx) {
    MOZ_ASSERT(!uninlineIsProfilerSamplingEnabled(cx));
    MOZ_ASSERT(!runningInInterpreter());
    flags_ |= RUNNING_IN_INTERPRETER;
    setInterpreterFieldsForPrologue(script());
  }

  // Like switchFromJitToInterpreter, but set the interpreterICEntry_ field to
  // nullptr. Initializing this field requires a binary search on the
  // JitScript's ICEntry list but the exception handler never returns to this
  // pc anyway so we can avoid the overhead.
  void switchFromJitToInterpreterForExceptionHandler(JSContext* cx,
                                                     jsbytecode* pc) {
    MOZ_ASSERT(!uninlineIsProfilerSamplingEnabled(cx));
    MOZ_ASSERT(!runningInInterpreter());
    flags_ |= RUNNING_IN_INTERPRETER;
    interpreterScript_ = script();
    interpreterPC_ = pc;
    interpreterICEntry_ = nullptr;
  }

  bool runningInInterpreter() const { return flags_ & RUNNING_IN_INTERPRETER; }
  bool isRealmIndependent() const { return flags_ & REALM_INDEPENDENT; }

  JSScript* interpreterScript() const {
    MOZ_ASSERT(runningInInterpreter());
    return interpreterScript_;
  }

  jsbytecode* interpreterPC() const {
    MOZ_ASSERT(runningInInterpreter());
    return interpreterPC_;
  }
  jsbytecode*& interpreterPC() {
    MOZ_ASSERT(runningInInterpreter());
    return interpreterPC_;
  }

  ICEntry* interpreterICEntry() const {
    MOZ_ASSERT(runningInInterpreter());
    return interpreterICEntry_;
  }
  ICEntry*& interpreterICEntry() {
    MOZ_ASSERT(runningInInterpreter());
    return interpreterICEntry_;
  }

  void setInterpreterFields(JSScript* script, jsbytecode* pc);

  void setInterpreterFields(jsbytecode* pc) {
    setInterpreterFields(script(), pc);
  }

  // Initialize interpreter fields for resuming in the prologue (before the
  // argument type check ICs).
  void setInterpreterFieldsForPrologue(JSScript* script);

  ICScript* icScript() const { return icScript_; }
  void setICScript(ICScript* icScript) { icScript_ = icScript; }

  // The script that owns the current ICScript.
  JSScript* outerScript() const;

  bool hasReturnValue() const { return flags_ & HAS_RVAL; }
  MutableHandleValue returnValue() {
    if (!hasReturnValue()) {
      addressOfReturnValue()->setUndefined();
    }
    return MutableHandleValue::fromMarkedLocation(addressOfReturnValue());
  }
  void setReturnValue(const Value& v) {
    returnValue().set(v);
    flags_ |= HAS_RVAL;
  }
  inline Value* addressOfReturnValue() {
    return reinterpret_cast<Value*>(&loReturnValue_);
  }

  bool hasInitialEnvironment() const { return flags_ & HAS_INITIAL_ENV; }

  inline CallObject& callObj() const;

  void setFlag(uint32_t flag) { flags_ |= flag; }
  void setFlags(uint32_t flags) { flags_ = flags; }

  [[nodiscard]] inline bool pushLexicalEnvironment(JSContext* cx,
                                                   Handle<LexicalScope*> scope);
  template <bool IsDebuggee>
  [[nodiscard]] inline bool freshenLexicalEnvironment(
      JSContext* cx, const jsbytecode* pc = nullptr);
  template <bool IsDebuggee>
  [[nodiscard]] inline bool recreateLexicalEnvironment(
      JSContext* cx, const jsbytecode* pc = nullptr);

  [[nodiscard]] bool initFunctionEnvironmentObjects(JSContext* cx);
  [[nodiscard]] bool pushClassBodyEnvironment(JSContext* cx,
                                              Handle<ClassBodyScope*> scope);
  [[nodiscard]] bool pushVarEnvironment(JSContext* cx, Handle<Scope*> scope);

  void initArgsObjUnchecked(ArgumentsObject& argsobj) {
    flags_ |= HAS_ARGS_OBJ;
    argsObj_ = &argsobj;
  }
  void initArgsObj(ArgumentsObject& argsobj) {
    MOZ_ASSERT(script()->needsArgsObj());
    initArgsObjUnchecked(argsobj);
  }
  bool hasArgsObj() const { return flags_ & HAS_ARGS_OBJ; }
  ArgumentsObject& argsObj() const {
    MOZ_ASSERT(hasArgsObj());
    MOZ_ASSERT(script()->needsArgsObj());
    return *argsObj_;
  }

  bool prevUpToDate() const { return flags_ & PREV_UP_TO_DATE; }
  void setPrevUpToDate() { flags_ |= PREV_UP_TO_DATE; }
  void unsetPrevUpToDate() { flags_ &= ~PREV_UP_TO_DATE; }

  bool isDebuggee() const { return flags_ & DEBUGGEE; }
  void setIsDebuggee() { flags_ |= DEBUGGEE; }
  inline void unsetIsDebuggee();

  void trace(JSTracer* trc, const JSJitFrameIter& frame);

  bool isGlobalFrame() const { return script()->isGlobalCode(); }
  bool isModuleFrame() const { return script()->isModule(); }
  bool isEvalFrame() const { return script()->isForEval(); }
  bool isFunctionFrame() const {
    return CalleeTokenIsFunction(calleeToken()) && !isModuleFrame();
  }
  bool isDebuggerEvalFrame() const { return false; }

  JitFrameLayout* framePrefix() const {
    uint8_t* fp = (uint8_t*)this + Size();
    return (JitFrameLayout*)fp;
  }

  static size_t Size() { return sizeof(BaselineFrame); }

  // The reverseOffsetOf methods below compute the offset relative to the
  // frame's base pointer. Since the stack grows down, these offsets are
  // negative.

#ifdef DEBUG
  static int reverseOffsetOfDebugFrameSize() {
    return -int(Size()) + offsetof(BaselineFrame, debugFrameSize_);
  }
#endif

  // The scratch value slot can either be used as a Value slot or as two
  // separate 32-bit integer slots.
  static int reverseOffsetOfScratchValueLow32() {
    return -int(Size()) + offsetof(BaselineFrame, loScratchValue_);
  }
  static int reverseOffsetOfScratchValueHigh32() {
    return -int(Size()) + offsetof(BaselineFrame, hiScratchValue_);
  }
  static int reverseOffsetOfScratchValue() {
    return reverseOffsetOfScratchValueLow32();
  }

  static int reverseOffsetOfEnvironmentChain() {
    return -int(Size()) + offsetof(BaselineFrame, envChain_);
  }
  static int reverseOffsetOfArgsObj() {
    return -int(Size()) + offsetof(BaselineFrame, argsObj_);
  }
  static int reverseOffsetOfFlags() {
    return -int(Size()) + offsetof(BaselineFrame, flags_);
  }
  static int reverseOffsetOfReturnValue() {
    return -int(Size()) + offsetof(BaselineFrame, loReturnValue_);
  }
  static int reverseOffsetOfInterpreterScript() {
    return -int(Size()) + offsetof(BaselineFrame, interpreterScript_);
  }
  static int reverseOffsetOfInterpreterPC() {
    return -int(Size()) + offsetof(BaselineFrame, interpreterPC_);
  }
  static int reverseOffsetOfInterpreterICEntry() {
    return -int(Size()) + offsetof(BaselineFrame, interpreterICEntry_);
  }
  static int reverseOffsetOfICScript() {
    return -int(Size()) + offsetof(BaselineFrame, icScript_);
  }
  static int reverseOffsetOfLocal(size_t index) {
    return -int(Size()) - (index + 1) * sizeof(Value);
  }
};

// Ensure the frame is 8-byte aligned (required on ARM).
static_assert((sizeof(BaselineFrame) % 8) == 0, "frame must be 8-byte aligned");

}  // namespace jit
}  // namespace js

#endif /* jit_BaselineFrame_h */
