/* 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_JitFrames_h
#define jit_JitFrames_h

#include "mozilla/Assertions.h"

#include <stddef.h>
#include <stdint.h>

#include "jit/CalleeToken.h"
#include "jit/MachineState.h"
#include "jit/Registers.h"
#include "js/Id.h"
#include "js/TypeDecls.h"
#include "js/Value.h"

namespace js {

namespace wasm {
class Instance;
struct StackTarget;
struct Handlers;
}  // namespace wasm

namespace jit {

enum class FrameType;
enum class VMFunctionId;
class IonScript;
class JitActivation;
class JitFrameLayout;
struct SafepointSlotEntry;
struct VMFunctionData;

/* [SMDOC] The JS ABIs
 *
 * ## Overview
 *
 * When calling from one sequence of jitcode to another, we are free to define
 * our own ABI. We make full use of this. When the caller and callee are fixed,
 * we often use bespoke hand-written ABIs: for example, the ABI for calling into
 * a regexp stub, or the tight coupling between an Ion IC and the corresponding
 * Ion code. However, there are some ABIs that are more broadly shared, and some
 * common principles.
 *
 * 1. As much as possible, our ABIs are architecture-independent. We use the
 *    same ABI on each platform. Some differences are unavoidable: for example,
 *    x86 pushes a return address as part of the call instruction, while most
 *    other platforms use a link register. In these cases, we sync up as quickly
 *    as possible (for example, by immediately pushing the return address on
 *    link register platforms).
 * 2. To simplify profiling and stack walking, we use frame pointers. There are
 *    a handful of exceptions, mostly involving tail calls. For example,
 *    baseline ICs tail-call each other, so they only push a frame pointer when
 *    making a call that could trigger a GC.
 * 3. Immediately after pushing a frame pointer, we require stack alignment to
 *    allow us to spill registers if necessary. This varies by architecture and
 *    is defined by JitStackAlignment. The caller is responsible for alignment
 *    padding.
 * 4. Whenever we make a call to a target that could examine the stack (for
 *    example, collecting stack roots for a GC), we push a frame descriptor
 *    immediately before the call. Combined with the return address and frame
 *    pointer, this is a CommonFrameLayout. The frame descriptor describes the
 *    type of the *caller*, and the number of arguments being passed.
 *
 * The remainder of this comment will focus on many-to-many ABIs with multiple
 * callers/callees.
 *
 * ## JS function ABI:
 *
 * This is the ABI that JS functions expect upon entry: Ion, Baseline,
 * BaselineInterpreter, the C++ interpreter stub, and any other jitcode
 * that could be the target of a script's jitCodeRaw pointer.
 *
 * All arguments are passed on the stack.
 *
 *    .               .
 *    |  (Padding?)   |
 *    +---------------+
 *    |   NewTarget?  | <-- Only for constructors.
 *    +---------------+
 *    |     ArgN      | <-- There must be at least as many arguments as the
 *    +---------------+     callee has formal parameters, but there may be
 *    |     ...       |     more. They can be accessed via `arguments` or a
 *    +---------------+     rest parameter.
 *    |     Arg0      |
 *    +---------------+
 *    |     ThisV     |
 *    +---------------+ <-- Everything above this point is a JS::Value.
 *    |  CalleeToken  |     Everything below is word-sized. Aligning this
 *    +---------------+     point to JitStackAlignment will guarantee correct
 *    |   Descriptor  |     alignment below.
 *    +---------------+
 *    |   ReturnAddr  |
 *    +===============+     After the callee pushes this, the stack should be
 *    |    FramePtr   | <-- aligned to JitStackAlignment (defined per-arch).
 *    +---------------+     If alignment padding is needed, the caller must
 *    |     ...       |     insert it above the last arg / new target.
 *
 *  CalleeToken:
 *    The callee token is either a pointer to the callee JSFunction, or, for
 *    non-functions (top-level scripts, modules, eval), the JSScript.  The token
 *    is low-bit-tagged to indicate a) whether the callee is a script, and b)
 *    whether a function is being invoked as a constructor. NewTarget is only
 *    present if the constructing bit is set. When the callee is a function,
 *    the environment chain is in the callee. When the callee is a non-function,
 *    the environment chain is passed to baseline in `R1.scratchReg()`. This is
 *    the only case where we pass a value in a register to a JS JIT frame.
 *
 *  Descriptor:
 *    The frame descriptor word contains, packed together:
 *    1. A FrameType describing the type of the *caller's* frame. This is pushed
 *       before calling so that code that walks the stack (eg the GC) can easily
 *       tell what kind of frame it's looking at.
 *    2. The number of actual arguments passed by the caller. If the caller did
 *       not pass as many arguments as the callee has formal parameters, then
 *       numActualArgs may be smaller than the number of arguments present on
 *       the stack. Missing arguments must be filled in with `undefined` before
 *       calling. Note that it is valid for numActualArgs to be larger than the
 *       number of formal parameters in the callee.
 *    It also contains flag bits. See the Frame Descriptor Layout SMDOC for
 * more.
 *
 * ## Baseline IC ABI
 *
 * This is the ABI that baseline ICs expect upon entry. Unlike the function ABI,
 * the baseline IC ABI passes some arguments in registers.
 *
 * The baseline compiler is written using three architecture-independent
 * Value-sized registers (each of which is a register pair on 32-bit
 * architectures). When calling a baseline IC, the first two arguments are
 * passed in R0 and R1. Subsequent arguments are passed on the stack. If there
 * is a return value, it is passed in R0. If an argument is smaller than a Value
 * (for example, a JSObject* on a 32-bit architecture), it is passed in the
 * payload register, and the type register is made available for the register
 * allocator. See BaselineCacheIRCompiler::init().
 *
 * Upon entry to a baseline IC, no frame descriptor is pushed:
 *
 *  Stack:
 *    .               .
 *    |               | <-- Top of the expression stack in the calling baseline
 *    +---------------+     frame (ignoring arguments to the IC.
 *    |     Arg2?     |
 *    +---------------+
 *    |  ReturnAddr?  | <-- Not pushed yet on link-register architectures.
 *    +---------------+
 *
 *  Registers:
 *    R0: Arg0
 *    R1: Arg1
 *    ICStubReg: points to active ICStub
 *
 * The stub code is free to push values on the stack.  If a stub guard fails,
 * then the stub code will restore the operands to their initial values, update
 * the ICStubReg to point to the next field of the current stub, and *jump* to
 * that stub's code, performing a tail call.
 *
 * If all stub guards have succeeded, but the stub must perform a call that
 * could GC, we enter a stub frame. This discards any values above the original
 * stack pointer and retroactively rewrites the stack as if we had pushed a
 * frame:
 *
 *  Original Stack:               Entered Stub Frame:
 *    .               .              .               .
 *    |               |              |               |
 *    +---------------+              +---------------+
 *    |     Arg2?     |              |     Arg2?     |
 *    +---------------+              +---------------+
 *    |  ReturnAddr?  | -----        |   Descriptor  |
 *    +---------------+      |       +---------------+
 *                            --->   |   ReturnAddr  |
 *                                   +---------------+
 *                                   |    FramePtr   |
 *                                   +---------------+
 *                                   |   ICStubReg   |
 *                                   +---------------+
 *
 * This lets us avoid setting up and tearing down an unnecessary frame for
 * simple ICs like Int32Add. The diagram on the right corresponds to
 * BaselineStubFrameLayout.
 *
 * ## Entry and exit frames
 *
 * A sequence of JS JIT frames on the stack always begins with an entry frame.
 * Unless it's currently executing, it ends with an exit frame. To walk the
 * stack from C++, an exit frame must exist.
 *
 * When walking the stack from newest to oldest frames, an entry frame marks the
 * point where the frame iterator does something different. There are two types
 * of entry frames: CPPToJSJit and WasmToJSJit.
 * - CPPToJSJit frames begin a JitActivation. There is at most one of them per
 *   JitActivation, which corresponds with a call to the EnterJit or
 *   EnterBaseline trampolines. The trampolines construct a JitFrameLayout for
 *   the callee's frame. When encountering a CPPToJSJit frame, an iterator
 *   can advance to the next JitActivation.
 * - WasmToJSJit frames mark the point within a JitActivation where we switch
 *   from Wasm to JS code. There can be an arbitrary number of such frames
 *   within a single JitActivation. When encountering a WasmToJSJit frame, an
 *   iterator will start walking Wasm frames (see WasmFrameIter).
 *
 * There are many different ExitFrame types. Most of them represent a call into
 * C++, and terminate a JitActivation. There are also two types of exit frames
 * to represent a call from JS into Wasm:
 * - ExitFrameType::DirectWasmJitCall is used for calls from Ion directly into
 *   Wasm, using the Wasm ABI.
 * - ExitFrameType::WasmGenericJitEntry calls a generated JitEntry stub that
 *   uses the JS ABI and internally converts it to the Wasm ABI. This is less
 *   efficient, but allows us to call Wasm functions in all places where we
 *   can call JS functions.
 *
 * [SMDOC] Frame Descriptor Layout
 *
 * A frame descriptor word has the following data:
 *
 *    high bits: [ numActualArgs |
 *                 has-inlined-icscript bit |
 *                 has-cached-saved-frame bit |
 *    low bits:    frame type ]
 *
 *
 * * numActualArgs: for JitFrameLayout, the number of arguments passed by the
 *   caller.
 * * HasInlinedICScript: Set when passing a private ICScript to a trial-inlined
 *   script.
 * * HasCachedSavedFrame: Used to power the LiveSavedFrameCache optimization.
 *   See the comment in Activation.h
 * * Frame Type: BaselineJS, Exit, etc. (jit::FrameType)
 */

class FrameDescriptor {
 public:
  static const uint32_t TypeBits = 4;
  static const uint32_t TypeMask = (1 << TypeBits) - 1;
  static const uint32_t HasCachedSavedFrame = 1 << TypeBits;
  static const uint32_t HasInlinedICScript = 1 << (TypeBits + 1);
  static const uint32_t NumActualArgsShift = TypeBits + 2;

  explicit FrameDescriptor(FrameType type) : raw_(uint32_t(type)) {}
  FrameDescriptor(FrameType type, uint32_t argc, bool hasInlined = false)
      : raw_(argc << NumActualArgsShift | uint32_t(type)) {
    if (hasInlined) {
      setHasInlinedICScript();
    }
    MOZ_ASSERT(numActualArgs() == argc, "argc must fit in descriptor");
  }

  FrameType type() const { return FrameType(raw_ & TypeMask); }
  void changeType(FrameType type) {
    raw_ &= ~TypeMask;
    raw_ |= uintptr_t(type);
  }

  uint32_t numActualArgs() const { return raw_ >> NumActualArgsShift; }

  bool hasCachedSavedFrame() const { return raw_ & HasCachedSavedFrame; }
  void setHasCachedSavedFrame() { raw_ |= HasCachedSavedFrame; }
  void clearHasCachedSavedFrame() { raw_ &= ~HasCachedSavedFrame; }

  bool hasInlinedICScript() const { return raw_ & HasInlinedICScript; }
  void setHasInlinedICScript() { raw_ |= HasInlinedICScript; }

  uint32_t value() const {
    MOZ_ASSERT(raw_ == uint32_t(raw_));
    return raw_;
  }

 private:
  uintptr_t raw_;
};

static inline uint32_t MakeFrameDescriptor(FrameType type) {
  FrameDescriptor descriptor(type);
  return descriptor.value();
}

// For JitFrameLayout, the descriptor also stores the number of arguments passed
// by the caller. Note that |type| is the type of the *older* frame and |argc|
// is the number of arguments passed to the *newer* frame.
static inline uint32_t MakeFrameDescriptorForJitCall(FrameType type,
                                                     uint32_t argc) {
  FrameDescriptor descriptor(type, argc);
  return descriptor.value();
}

struct BaselineBailoutInfo;

enum class ExceptionResumeKind : int32_t {
  // There is no exception handler in this activation.
  // Return from the entry frame.
  EntryFrame,

  // The exception was caught in baseline.
  // Restore state and jump to the catch block.
  Catch,

  // A finally block must be executed in baseline.
  // Stash the exception on the stack and jump to the finally block.
  Finally,

  // We are forcing an early return with a specific return value.
  // This is used by the debugger and when closing generators.
  // Immediately return from the current frame with the given value.
  ForcedReturnBaseline,
  ForcedReturnIon,

  // This frame is currently executing in Ion, but we must bail out
  // to baseline before handling the exception.
  // Jump to the bailout tail stub.
  Bailout,

  // Return to the wasm interpreter entry frame.
  WasmInterpEntry,

  // The exception was caught by a wasm catch handler.
  // Restore state and jump to it.
  WasmCatch
};

// Data needed to recover from an exception.
struct ResumeFromException {
  uint8_t* framePointer;
  uint8_t* stackPointer;
  uint8_t* target;
  ExceptionResumeKind kind;
  wasm::Instance* instance;
#ifdef ENABLE_WASM_JSPI
  const wasm::StackTarget* stackTarget;
  const wasm::Handlers* baseHandlers;
#endif

  // Value to push when resuming into a |finally| block.
  // Also used by Wasm to send the exception object to the throw stub.
  JS::Value exception;

  // Exception stack to push when resuming into a |finally| block.
  JS::Value exceptionStack;

  BaselineBailoutInfo* bailoutInfo;

  static size_t offsetOfFramePointer() {
    return offsetof(ResumeFromException, framePointer);
  }
  static size_t offsetOfStackPointer() {
    return offsetof(ResumeFromException, stackPointer);
  }
  static size_t offsetOfTarget() {
    return offsetof(ResumeFromException, target);
  }
  static size_t offsetOfKind() { return offsetof(ResumeFromException, kind); }
  static size_t offsetOfInstance() {
    return offsetof(ResumeFromException, instance);
  }
#ifdef ENABLE_WASM_JSPI
  static size_t offsetOfStackTarget() {
    return offsetof(ResumeFromException, stackTarget);
  }
  static size_t offsetOfBaseHandlers() {
    return offsetof(ResumeFromException, baseHandlers);
  }
#endif
  static size_t offsetOfException() {
    return offsetof(ResumeFromException, exception);
  }
  static size_t offsetOfExceptionStack() {
    return offsetof(ResumeFromException, exceptionStack);
  }
  static size_t offsetOfBailoutInfo() {
    return offsetof(ResumeFromException, bailoutInfo);
  }
};

#if defined(JS_CODEGEN_ARM64)
static_assert(sizeof(ResumeFromException) % 16 == 0,
              "ResumeFromException should be aligned");
#endif

void HandleException(ResumeFromException* rfe);

void EnsureUnwoundJitExitFrame(JitActivation* act, JitFrameLayout* frame);

void TraceJitFrames(JSTracer* trc, JitActivation* act);

#ifdef ENABLE_WASM_JSPI
void TraceWasmSuspendedContStacks(JSContext* cx, JSTracer* trc);
#endif

// Trace weak pointers in baseline stubs in activations for zones that are
// currently being swept.
void TraceWeakJitActivationsInSweepingZones(JSContext* cx, JSTracer* trc);

void UpdateJitActivationsForMinorGC(JSRuntime* rt);
void UpdateJitActivationsForCompactingGC(JSRuntime* rt);

// Returns the JSScript associated with the topmost JIT frame.
JSScript* GetTopJitJSScript(JSContext* cx);

#if defined(JS_CODEGEN_ARM64)
uint8_t* alignDoubleSpill(uint8_t* pointer);
#else
inline uint8_t* alignDoubleSpill(uint8_t* pointer) {
  // This is a no-op on most platforms.
  return pointer;
}
#endif

// Layout of the frame prefix. This assumes the stack architecture grows down.
// If this is ever not the case, we'll have to refactor.
class CommonFrameLayout {
  uint8_t* callerFramePtr_;
  uint8_t* returnAddress_;
  FrameDescriptor descriptor_;

 public:
  static constexpr size_t offsetOfDescriptor() {
    return offsetof(CommonFrameLayout, descriptor_);
  }
  FrameDescriptor descriptor() const { return descriptor_; }
  static constexpr size_t offsetOfReturnAddress() {
    return offsetof(CommonFrameLayout, returnAddress_);
  }
  FrameType prevType() const { return descriptor_.type(); }
  void changePrevType(FrameType type) { descriptor_.changeType(type); }
  bool hasCachedSavedFrame() const { return descriptor_.hasCachedSavedFrame(); }
  void setHasCachedSavedFrame() { descriptor_.setHasCachedSavedFrame(); }
  void clearHasCachedSavedFrame() { descriptor_.clearHasCachedSavedFrame(); }
  uint8_t* returnAddress() const { return returnAddress_; }
  void setReturnAddress(uint8_t* addr) { returnAddress_ = addr; }

  uint8_t* callerFramePtr() const { return callerFramePtr_; }
  static constexpr size_t offsetOfCallerFramePtr() {
    return offsetof(CommonFrameLayout, callerFramePtr_);
  }
  static constexpr size_t bytesPoppedAfterCall() {
    // The return address and frame pointer are popped by the callee/call.
    return 2 * sizeof(void*);
  }
};

class JitFrameLayout : public CommonFrameLayout {
  CalleeToken calleeToken_;

 public:
  CalleeToken calleeToken() const { return calleeToken_; }
  void replaceCalleeToken(CalleeToken calleeToken) {
    calleeToken_ = calleeToken;
  }

  static constexpr size_t offsetOfCalleeToken() {
    return offsetof(JitFrameLayout, calleeToken_);
  }
  static constexpr size_t offsetOfThis() { return sizeof(JitFrameLayout); }
  static constexpr size_t offsetOfActualArgs() {
    return offsetOfThis() + sizeof(JS::Value);
  }
  static constexpr size_t offsetOfActualArg(size_t arg) {
    return offsetOfActualArgs() + arg * sizeof(JS::Value);
  }

  JS::Value& thisv() {
    MOZ_ASSERT(CalleeTokenIsFunction(calleeToken()));
    return thisAndActualArgs()[0];
  }
  JS::Value* thisAndActualArgs() {
    MOZ_ASSERT(CalleeTokenIsFunction(calleeToken()));
    return (JS::Value*)(this + 1);
  }
  JS::Value* actualArgs() { return thisAndActualArgs() + 1; }
  uintptr_t numActualArgs() const { return descriptor().numActualArgs(); }

  // Computes a reference to a stack or argument slot, where a slot is a
  // distance from the base frame pointer, as would be used for LStackSlot
  // or LArgument.
  uintptr_t* slotRef(SafepointSlotEntry where);

  static inline size_t Size() { return sizeof(JitFrameLayout); }
};

class BaselineInterpreterEntryFrameLayout : public JitFrameLayout {
 public:
  static inline size_t Size() {
    return sizeof(BaselineInterpreterEntryFrameLayout);
  }
};

class TrampolineNativeFrameLayout : public JitFrameLayout {
 public:
  static inline size_t Size() { return sizeof(TrampolineNativeFrameLayout); }

  template <typename T>
  T* getFrameData() {
    uint8_t* raw = reinterpret_cast<uint8_t*>(this) - sizeof(T);
    return reinterpret_cast<T*>(raw);
  }
};

class WasmToJSJitFrameLayout : public JitFrameLayout {
 public:
  static inline size_t Size() { return sizeof(WasmToJSJitFrameLayout); }
};

class IonICCallFrameLayout : public CommonFrameLayout {
 protected:
  // Pointer to root the stub's JitCode.
  JitCode* stubCode_;

 public:
  static constexpr size_t LocallyTracedValueOffset = sizeof(void*);

  JitCode** stubCode() { return &stubCode_; }
  static size_t Size() { return sizeof(IonICCallFrameLayout); }

  inline Value* locallyTracedValuePtr(size_t index) {
    uint8_t* fp = reinterpret_cast<uint8_t*>(this);
    return reinterpret_cast<Value*>(fp - LocallyTracedValueOffset -
                                    index * sizeof(Value));
  }
};

enum class ExitFrameType : uint8_t {
  CallNative = 0x0,
  ConstructNative = 0x1,
  IonDOMGetter = 0x2,
  IonDOMSetter = 0x3,
  IonDOMMethod = 0x4,
  IonOOLNative = 0x5,
  IonOOLProxy = 0x6,
  WasmGenericJitEntry = 0x7,
  DirectWasmJitCall = 0x8,
  UnwoundJit = 0x9,
  InterpreterStub = 0xA,
  LazyLink = 0xB,
  Bare = 0xC,

  // This must be the last value in this enum. See ExitFooterFrame::data_.
  VMFunction = 0xD
};

// GC related data used to keep alive data surrounding the Exit frame.
class ExitFooterFrame {
  // Stores either the ExitFrameType or, for a VMFunction call,
  // `ExitFrameType::VMFunction + VMFunctionId`.
  uintptr_t data_;

#ifdef DEBUG
  void assertValidVMFunctionId() const;
#else
  void assertValidVMFunctionId() const {}
#endif

 public:
  static constexpr size_t Size() { return sizeof(ExitFooterFrame); }
  void setUnwoundJitExitFrame() {
    data_ = uintptr_t(ExitFrameType::UnwoundJit);
  }
  ExitFrameType type() const {
    if (data_ >= uintptr_t(ExitFrameType::VMFunction)) {
      return ExitFrameType::VMFunction;
    }
    return ExitFrameType(data_);
  }
  VMFunctionId functionId() const {
    MOZ_ASSERT(type() == ExitFrameType::VMFunction);
    assertValidVMFunctionId();
    return static_cast<VMFunctionId>(data_ - size_t(ExitFrameType::VMFunction));
  }

  // This should only be called for function()->outParam == Type_Handle
  template <typename T>
  T* outParam() {
    uint8_t* address = reinterpret_cast<uint8_t*>(this);
    return reinterpret_cast<T*>(address - sizeof(T));
  }
};

class NativeExitFrameLayout;
class IonOOLNativeExitFrameLayout;
class IonOOLProxyExitFrameLayout;
class IonDOMExitFrameLayout;

// this is the frame layout when we are exiting ion code, and about to enter
// platform ABI code
class ExitFrameLayout : public CommonFrameLayout {
  inline uint8_t* top() { return reinterpret_cast<uint8_t*>(this + 1); }

 public:
  static constexpr size_t Size() { return sizeof(ExitFrameLayout); }
  static constexpr size_t SizeWithFooter() {
    return Size() + ExitFooterFrame::Size();
  }

  inline ExitFooterFrame* footer() {
    uint8_t* sp = reinterpret_cast<uint8_t*>(this);
    return reinterpret_cast<ExitFooterFrame*>(sp - ExitFooterFrame::Size());
  }

  // argBase targets the point which precedes the exit frame. Arguments of VM
  // each wrapper are pushed before the exit frame.  This correspond exactly
  // to the value of the argBase register of the generateVMWrapper function.
  inline uint8_t* argBase() {
    MOZ_ASSERT(isWrapperExit());
    return top();
  }

  inline bool isWrapperExit() {
    return footer()->type() == ExitFrameType::VMFunction;
  }
  inline bool isBareExit() { return footer()->type() == ExitFrameType::Bare; }
  inline bool isUnwoundJitExit() {
    return footer()->type() == ExitFrameType::UnwoundJit;
  }

  // See the various exit frame layouts below.
  template <typename T>
  inline bool is() {
    return footer()->type() == T::Type();
  }
  template <typename T>
  inline T* as() {
    MOZ_ASSERT(this->is<T>());
    return reinterpret_cast<T*>(footer());
  }
};

// Cannot inherit implementation since we need to extend the top of
// ExitFrameLayout.
class NativeExitFrameLayout {
 protected:  // only to silence a clang warning about unused private fields
  ExitFooterFrame footer_;
  ExitFrameLayout exit_;
  uintptr_t argc_;

  // 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 loCalleeResult_;
  uint32_t hiCalleeResult_;

 public:
  static inline size_t Size() { return sizeof(NativeExitFrameLayout); }

  static size_t offsetOfResult() {
    return offsetof(NativeExitFrameLayout, loCalleeResult_);
  }
  inline JS::Value* vp() {
    return reinterpret_cast<JS::Value*>(&loCalleeResult_);
  }
  inline uintptr_t argc() const { return argc_; }
};

class CallNativeExitFrameLayout : public NativeExitFrameLayout {
 public:
  static ExitFrameType Type() { return ExitFrameType::CallNative; }
};

class ConstructNativeExitFrameLayout : public NativeExitFrameLayout {
 public:
  static ExitFrameType Type() { return ExitFrameType::ConstructNative; }
};

template <>
inline bool ExitFrameLayout::is<NativeExitFrameLayout>() {
  return is<CallNativeExitFrameLayout>() ||
         is<ConstructNativeExitFrameLayout>();
}

class IonOOLNativeExitFrameLayout {
 protected:  // only to silence a clang warning about unused private fields
  ExitFooterFrame footer_;
  ExitFrameLayout exit_;

  // pointer to root the stub's JitCode
  JitCode* stubCode_;

  uintptr_t argc_;

  // 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 loCalleeResult_;
  uint32_t hiCalleeResult_;

  // Split Value for |this| and args above.
  uint32_t loThis_;
  uint32_t hiThis_;

 public:
  static ExitFrameType Type() { return ExitFrameType::IonOOLNative; }

  static inline size_t Size(size_t argc) {
    // The frame accounts for the callee/result and |this|, so we only need
    // args.
    return sizeof(IonOOLNativeExitFrameLayout) + (argc * sizeof(JS::Value));
  }

  static size_t offsetOfResult() {
    return offsetof(IonOOLNativeExitFrameLayout, loCalleeResult_);
  }

  inline JitCode** stubCode() { return &stubCode_; }
  inline JS::Value* vp() {
    return reinterpret_cast<JS::Value*>(&loCalleeResult_);
  }
  inline JS::Value* thisp() { return reinterpret_cast<JS::Value*>(&loThis_); }
  inline uintptr_t argc() const { return argc_; }
};

// ProxyGetProperty(JSContext* cx, HandleObject proxy, HandleId id,
//                  MutableHandleValue vp)
// ProxyCallProperty(JSContext* cx, HandleObject proxy, HandleId id,
//                   MutableHandleValue vp)
// ProxySetProperty(JSContext* cx, HandleObject proxy, HandleId id,
//                  MutableHandleValue vp, bool strict)
class IonOOLProxyExitFrameLayout {
 protected:  // only to silence a clang warning about unused private fields
  ExitFooterFrame footer_;
  ExitFrameLayout exit_;

  // The proxy object.
  JSObject* proxy_;

  // id for HandleId
  jsid id_;

  // space for MutableHandleValue result
  // use two uint32_t so compiler doesn't align.
  uint32_t vp0_;
  uint32_t vp1_;

  // pointer to root the stub's JitCode
  JitCode* stubCode_;

 public:
  static ExitFrameType Type() { return ExitFrameType::IonOOLProxy; }

  static inline size_t Size() { return sizeof(IonOOLProxyExitFrameLayout); }

  static size_t offsetOfResult() {
    return offsetof(IonOOLProxyExitFrameLayout, vp0_);
  }

  inline JitCode** stubCode() { return &stubCode_; }
  inline JS::Value* vp() { return reinterpret_cast<JS::Value*>(&vp0_); }
  inline jsid* id() { return &id_; }
  inline JSObject** proxy() { return &proxy_; }
};

class IonDOMExitFrameLayout {
 protected:  // only to silence a clang warning about unused private fields
  ExitFooterFrame footer_;
  ExitFrameLayout exit_;
  JSObject* thisObj;

  // 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 loCalleeResult_;
  uint32_t hiCalleeResult_;

 public:
  static ExitFrameType GetterType() { return ExitFrameType::IonDOMGetter; }
  static ExitFrameType SetterType() { return ExitFrameType::IonDOMSetter; }

  static inline size_t Size() { return sizeof(IonDOMExitFrameLayout); }

  static size_t offsetOfResult() {
    return offsetof(IonDOMExitFrameLayout, loCalleeResult_);
  }
  inline JS::Value* vp() {
    return reinterpret_cast<JS::Value*>(&loCalleeResult_);
  }
  inline JSObject** thisObjAddress() { return &thisObj; }
  inline bool isMethodFrame();
};

struct IonDOMMethodExitFrameLayoutTraits;

class IonDOMMethodExitFrameLayout {
 protected:  // only to silence a clang warning about unused private fields
  ExitFooterFrame footer_;
  ExitFrameLayout exit_;
  // This must be the last thing pushed, so as to stay common with
  // IonDOMExitFrameLayout.
  JSObject* thisObj_;
  JS::Value* argv_;
  uintptr_t argc_;

  // 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 loCalleeResult_;
  uint32_t hiCalleeResult_;

  friend struct IonDOMMethodExitFrameLayoutTraits;

 public:
  static ExitFrameType Type() { return ExitFrameType::IonDOMMethod; }

  static inline size_t Size() { return sizeof(IonDOMMethodExitFrameLayout); }

  static size_t offsetOfResult() {
    return offsetof(IonDOMMethodExitFrameLayout, loCalleeResult_);
  }

  inline JS::Value* vp() {
    // The code in visitCallDOMNative depends on this static assert holding
    static_assert(
        offsetof(IonDOMMethodExitFrameLayout, loCalleeResult_) ==
        (offsetof(IonDOMMethodExitFrameLayout, argc_) + sizeof(uintptr_t)));
    return reinterpret_cast<JS::Value*>(&loCalleeResult_);
  }
  inline JSObject** thisObjAddress() { return &thisObj_; }
  inline uintptr_t argc() { return argc_; }
};

inline bool IonDOMExitFrameLayout::isMethodFrame() {
  return footer_.type() == IonDOMMethodExitFrameLayout::Type();
}

template <>
inline bool ExitFrameLayout::is<IonDOMExitFrameLayout>() {
  ExitFrameType type = footer()->type();
  return type == IonDOMExitFrameLayout::GetterType() ||
         type == IonDOMExitFrameLayout::SetterType() ||
         type == IonDOMMethodExitFrameLayout::Type();
}

template <>
inline IonDOMExitFrameLayout* ExitFrameLayout::as<IonDOMExitFrameLayout>() {
  MOZ_ASSERT(is<IonDOMExitFrameLayout>());
  return reinterpret_cast<IonDOMExitFrameLayout*>(footer());
}

struct IonDOMMethodExitFrameLayoutTraits {
  static const size_t offsetOfArgcFromArgv =
      offsetof(IonDOMMethodExitFrameLayout, argc_) -
      offsetof(IonDOMMethodExitFrameLayout, argv_);
};

// Cannot inherit implementation since we need to extend the top of
// ExitFrameLayout.
class CalledFromJitExitFrameLayout {
 protected:  // silence clang warning about unused private fields
  ExitFooterFrame footer_;
  JitFrameLayout exit_;

 public:
  static inline size_t Size() { return sizeof(CalledFromJitExitFrameLayout); }
  inline JitFrameLayout* jsFrame() { return &exit_; }
  static size_t offsetOfExitFrame() {
    return offsetof(CalledFromJitExitFrameLayout, exit_);
  }
};

class LazyLinkExitFrameLayout : public CalledFromJitExitFrameLayout {
 public:
  static ExitFrameType Type() { return ExitFrameType::LazyLink; }
};

class InterpreterStubExitFrameLayout : public CalledFromJitExitFrameLayout {
 public:
  static ExitFrameType Type() { return ExitFrameType::InterpreterStub; }
};

class WasmGenericJitEntryFrameLayout : CalledFromJitExitFrameLayout {
 public:
  static ExitFrameType Type() { return ExitFrameType::WasmGenericJitEntry; }
};

template <>
inline bool ExitFrameLayout::is<CalledFromJitExitFrameLayout>() {
  return is<InterpreterStubExitFrameLayout>() ||
         is<LazyLinkExitFrameLayout>() || is<WasmGenericJitEntryFrameLayout>();
}

template <>
inline CalledFromJitExitFrameLayout*
ExitFrameLayout::as<CalledFromJitExitFrameLayout>() {
  MOZ_ASSERT(is<CalledFromJitExitFrameLayout>());
  uint8_t* sp = reinterpret_cast<uint8_t*>(this);
  sp -= CalledFromJitExitFrameLayout::offsetOfExitFrame();
  return reinterpret_cast<CalledFromJitExitFrameLayout*>(sp);
}

class DirectWasmJitCallFrameLayout {
 protected:  // silence clang warning about unused private fields
  ExitFooterFrame footer_;
  ExitFrameLayout exit_;

 public:
  static ExitFrameType Type() { return ExitFrameType::DirectWasmJitCall; }
};

class ICStub;

class BaselineStubFrameLayout : public CommonFrameLayout {
  // Info on the stack
  //
  // +-------------------------------------------+
  // |BaselineStubFrameLayout                    |
  // +-------------------------------------------+
  // | - Descriptor                              | <= Marks end of
  // | - Return address                          |    FrameType::BaselineJS
  // | - CallerFramePtr                          | <= Frame pointer points here
  // +-------------------------------------------+
  // | - StubPtr                                 | <= Technically these fields
  // | - InlinedICScript or LocallyTracedValue   |    precede the FrameLayout in
  // |                      LocallyTracedValue...|    memory.
  // +-------------------------------------------+
  //
  // StubPtr is always present (but can be null; see generateDebugTrapHandler).
  // InlinedICScript is only present if the HasInlinedICScript flag is set in
  // the callee's frame descriptor (not shown in this diagram). Alternatively,
  // we support up to 255 locally traced values (with the count stored in
  // stub->jitCode()->localTracingSlots()).
 public:
  static constexpr size_t ICStubOffset = sizeof(void*);
  static constexpr int ICStubOffsetFromFP = -int(ICStubOffset);
  static constexpr int InlinedICScriptOffsetFromFP = 2 * -int(sizeof(void*));
  static constexpr size_t LocallyTracedValueOffset = 2 * sizeof(void*);

  static inline size_t Size() { return sizeof(BaselineStubFrameLayout); }

  ICStub* maybeStubPtr() {
    uint8_t* fp = reinterpret_cast<uint8_t*>(this);
    return *reinterpret_cast<ICStub**>(fp - ICStubOffset);
  }
  void setStubPtr(ICStub* stub) {
    MOZ_ASSERT(stub);
    uint8_t* fp = reinterpret_cast<uint8_t*>(this);
    *reinterpret_cast<ICStub**>(fp - ICStubOffset) = stub;
  }

  inline Value* locallyTracedValuePtr(size_t index) {
    uint8_t* fp = reinterpret_cast<uint8_t*>(this);
    return reinterpret_cast<Value*>(fp - LocallyTracedValueOffset -
                                    index * sizeof(Value));
  }
};

// An invalidation bailout stack is at the stack pointer for the callee frame.
class InvalidationBailoutStack {
  RegisterDump::FPUArray fpregs_;
  RegisterDump::GPRArray regs_;
  IonScript* ionScript_;
  uint8_t* osiPointReturnAddress_;

 public:
  uint8_t* sp() const {
    return (uint8_t*)this + sizeof(InvalidationBailoutStack);
  }
  JitFrameLayout* fp() const;
  MachineState machine() { return MachineState::FromBailout(regs_, fpregs_); }

  IonScript* ionScript() const { return ionScript_; }
  uint8_t* osiPointReturnAddress() const { return osiPointReturnAddress_; }
  static size_t offsetOfFpRegs() {
    return offsetof(InvalidationBailoutStack, fpregs_);
  }
  static size_t offsetOfRegs() {
    return offsetof(InvalidationBailoutStack, regs_);
  }

  void checkInvariants() const;
};

// Baseline requires one slot for this/argument type checks.
static const uint32_t MinJITStackSize = 1;

} /* namespace jit */
} /* namespace js */

#endif /* jit_JitFrames_h */
