/* -*- 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_BaselineIC_h
#define jit_BaselineIC_h

#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"

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

#include "jit/ICState.h"
#include "jit/JitCode.h"
#include "jit/shared/Assembler-shared.h"
#include "jit/TypeData.h"
#include "js/TypeDecls.h"

class JS_PUBLIC_API JSTracer;

enum class JSOp : uint8_t;

namespace js {

MOZ_COLD void ReportOutOfMemory(JSContext* cx);

namespace jit {

class BaselineFrame;
class CacheIRStubInfo;
class ICScript;
class ICStubSpace;

enum class VMFunctionId;

// [SMDOC] JIT Inline Caches (ICs)
//
// Baseline Inline Caches are polymorphic caches that aggressively
// share their stub code.
//
// Every polymorphic site contains a linked list of stubs which are
// specific to that site.  These stubs are composed of a |StubData|
// structure that stores parametrization information (e.g.
// the shape pointer for a shape-check-and-property-get stub), any
// dynamic information (e.g. warm-up counters), a pointer to the stub code,
// and a pointer to the next stub state in the linked list.
//
// Every BaselineScript keeps an table of |CacheDescriptor| data
// structures, which store the following:
//      A pointer to the first StubData in the cache.
//      The bytecode PC of the relevant IC.
//      The machine-code PC where the call to the stubcode returns.
//
// A diagram:
//
//        Control flow                  Pointers
//      =======#                     ----.     .---->
//             #                         |     |
//             #======>                  \-----/
//
//
//                                   .---------------------------------------.
//                                   |         .-------------------------.   |
//                                   |         |         .----.          |   |
//         Baseline                  |         |         |    |          |   |
//         JIT Code              0   ^     1   ^     2   ^    |          |   |
//     +--------------+    .-->+-----+   +-----+   +-----+    |          |   |
//     |              |  #=|==>|     |==>|     |==>| FB  |    |          |   |
//     |              |  # |   +-----+   +-----+   +-----+    |          |   |
//     |              |  # |      #         #         #       |          |   |
//     |==============|==# |      #         #         #       |          |   |
//     |=== IC =======|    |      #         #         #       |          |   |
//  .->|==============|<===|======#=========#=========#       |          |   |
//  |  |              |    |                                  |          |   |
//  |  |              |    |                                  |          |   |
//  |  |              |    |                                  |          |   |
//  |  |              |    |                                  v          |   |
//  |  |              |    |                              +---------+    |   |
//  |  |              |    |                              | Fallback|    |   |
//  |  |              |    |                              | Stub    |    |   |
//  |  |              |    |                              | Code    |    |   |
//  |  |              |    |                              +---------+    |   |
//  |  +--------------+    |                                             |   |
//  |         |_______     |                              +---------+    |   |
//  |                |     |                              | Stub    |<---/   |
//  |        IC      |     \--.                           | Code    |        |
//  |    Descriptor  |        |                           +---------+        |
//  |      Table     v        |                                              |
//  |  +-----------------+    |                           +---------+        |
//  \--| Ins | PC | Stub |----/                           | Stub    |<-------/
//     +-----------------+                                | Code    |
//     |       ...       |                                +---------+
//     +-----------------+
//                                                          Shared
//                                                          Stub Code
//

class ICStub;
class ICCacheIRStub;
class ICFallbackStub;

#ifdef JS_JITSPEW
void FallbackICSpew(JSContext* cx, ICFallbackStub* stub, const char* fmt, ...)
    MOZ_FORMAT_PRINTF(3, 4);
#else
#  define FallbackICSpew(...)
#endif

// An entry in the ICScript IC table. There's one ICEntry per IC.
class ICEntry {
  // A pointer to the first IC stub for this instruction.
  ICStub* firstStub_;

 public:
  explicit ICEntry(ICStub* firstStub) : firstStub_(firstStub) {}

  ICStub* firstStub() const {
    MOZ_ASSERT(firstStub_);
    return firstStub_;
  }

  void setFirstStub(ICStub* stub) { firstStub_ = stub; }

  static constexpr size_t offsetOfFirstStub() {
    return offsetof(ICEntry, firstStub_);
  }

  void trace(JSTracer* trc);
  bool traceWeak(JSTracer* trc);
};

//
// Base class for all IC stubs.
//
class ICStub {
  friend class ICFallbackStub;

 protected:
  // The raw jitcode to call for this stub.
  uint8_t* stubCode_;

  // Counts the number of times the stub was entered
  //
  // See Bug 1494473 comment 6 for a mechanism to handle overflow if overflow
  // becomes a concern.
  uint32_t enteredCount_ = 0;

  // Tracks input types for some CacheIR stubs, to help optimize
  // polymorphic cases. Stored in the base class to make use of
  // padding bytes.
  TypeData typeData_;

  // Whether this is an ICFallbackStub or an ICCacheIRStub.
  bool isFallback_;

  ICStub(uint8_t* stubCode, bool isFallback)
      : stubCode_(stubCode), isFallback_(isFallback) {
#ifndef ENABLE_PORTABLE_BASELINE_INTERP
    MOZ_ASSERT(stubCode != nullptr);
#endif  // !ENABLE_PORTABLE_BASELINE_INTERP
  }

 public:
  inline bool isFallback() const { return isFallback_; }

  inline ICStub* maybeNext() const;

  inline const ICFallbackStub* toFallbackStub() const {
    MOZ_ASSERT(isFallback());
    return reinterpret_cast<const ICFallbackStub*>(this);
  }

  inline ICFallbackStub* toFallbackStub() {
    MOZ_ASSERT(isFallback());
    return reinterpret_cast<ICFallbackStub*>(this);
  }

  ICCacheIRStub* toCacheIRStub() {
    MOZ_ASSERT(!isFallback());
    return reinterpret_cast<ICCacheIRStub*>(this);
  }
  const ICCacheIRStub* toCacheIRStub() const {
    MOZ_ASSERT(!isFallback());
    return reinterpret_cast<const ICCacheIRStub*>(this);
  }

  bool usesTrampolineCode() const {
    // All fallback code is stored in a single JitCode instance, so we can't
    // call JitCode::FromExecutable on the raw pointer.
    return isFallback();
  }

#ifndef ENABLE_PORTABLE_BASELINE_INTERP
  JitCode* jitCode() {
    MOZ_ASSERT(!usesTrampolineCode());
    return JitCode::FromExecutable(stubCode_);
  }
  bool hasJitCode() { return !!stubCode_; }
#else  // !ENABLE_PORTABLE_BASELINE_INTERP
  JitCode* jitCode() { return nullptr; }
  bool hasJitCode() { return false; }
  uint8_t* rawJitCode() const { return stubCode_; }
  void updateRawJitCode(uint8_t* ptr) { stubCode_ = ptr; }
#endif

  uint32_t enteredCount() const { return enteredCount_; }
  inline void incrementEnteredCount() { enteredCount_++; }
  void resetEnteredCount() { enteredCount_ = 0; }

  static constexpr size_t offsetOfStubCode() {
    return offsetof(ICStub, stubCode_);
  }
  static constexpr size_t offsetOfEnteredCount() {
    return offsetof(ICStub, enteredCount_);
  }
};

class ICFallbackStub final : public ICStub {
  friend class ICStubConstIterator;

 protected:
  // The PC offset of this IC's bytecode op within the JSScript.
  uint32_t pcOffset_;

  // The state of this IC.
  ICState state_{};

 public:
  explicit ICFallbackStub(uint32_t pcOffset, TrampolinePtr stubCode)
      : ICStub(stubCode.value, /* isFallback = */ true), pcOffset_(pcOffset) {}

  inline size_t numOptimizedStubs() const { return state_.numOptimizedStubs(); }

  bool newStubIsFirstStub() const { return state_.newStubIsFirstStub(); }

  ICState& state() { return state_; }

  uint32_t pcOffset() const { return pcOffset_; }

  // Add a new stub to the IC chain terminated by this fallback stub.
  inline void addNewStub(ICEntry* icEntry, ICCacheIRStub* stub);

  void discardStubs(Zone* zone, ICEntry* icEntry);

  void clearUsedByTranspiler() { state_.clearUsedByTranspiler(); }
  void setUsedByTranspiler() { state_.setUsedByTranspiler(); }
  bool usedByTranspiler() const { return state_.usedByTranspiler(); }

  void clearMayHaveFoldedStub() { state_.clearMayHaveFoldedStub(); }
  void setMayHaveFoldedStub() { state_.setMayHaveFoldedStub(); }
  bool mayHaveFoldedStub() const { return state_.mayHaveFoldedStub(); }

  TrialInliningState trialInliningState() const {
    return state_.trialInliningState();
  }
  void setTrialInliningState(TrialInliningState state) {
    state_.setTrialInliningState(state);
  }

  void trackNotAttached();

  void unlinkStub(Zone* zone, ICEntry* icEntry, ICCacheIRStub* prev,
                  ICCacheIRStub* stub);
  void unlinkStubUnbarriered(ICEntry* icEntry, ICCacheIRStub* prev,
                             ICCacheIRStub* stub);
};

class ICCacheIRStub final : public ICStub {
  // Pointer to next IC stub.
  ICStub* next_ = nullptr;

  const CacheIRStubInfo* stubInfo_;

#ifndef JS_64BIT
  // Ensure stub data is 8-byte aligned on 32-bit.
  uintptr_t padding_ = 0;
#endif

 public:
  ICCacheIRStub(JitCode* stubCode, const CacheIRStubInfo* stubInfo)
      : ICStub(stubCode ? stubCode->raw() : nullptr, /* isFallback = */ false),
        stubInfo_(stubInfo) {
    MOZ_ASSERT_IF(!IsPortableBaselineInterpreterEnabled(), stubCode);
  }

  ICStub* next() const { return next_; }
  void setNext(ICStub* stub) { next_ = stub; }

  ICCacheIRStub* nextCacheIR() const {
    return next_->isFallback() ? nullptr : next_->toCacheIRStub();
  }

  const CacheIRStubInfo* stubInfo() const { return stubInfo_; }
  uint8_t* stubDataStart();

  void trace(JSTracer* trc);
  bool traceWeak(JSTracer* trc);

  enum class ICScriptHandling { MarkActive, AssertActive };
  ICCacheIRStub* clone(JSRuntime* rt, ICStubSpace& newSpace,
                       ICScriptHandling icScriptHandling);

  // Returns true if this stub can call JS or VM code that can trigger a GC.
  bool makesGCCalls() const;

  static constexpr size_t offsetOfNext() {
    return offsetof(ICCacheIRStub, next_);
  }

  void setTypeData(TypeData data) { typeData_ = data; }
  TypeData typeData() const { return typeData_; }
};

// Assert stub size is what we expect to catch regressions.
#ifdef JS_64BIT
static_assert(sizeof(ICFallbackStub) == 3 * sizeof(uintptr_t));
static_assert(sizeof(ICCacheIRStub) == 4 * sizeof(uintptr_t));
#else
static_assert(sizeof(ICFallbackStub) == 5 * sizeof(uintptr_t));
static_assert(sizeof(ICCacheIRStub) == 6 * sizeof(uintptr_t));
#endif

inline ICStub* ICStub::maybeNext() const {
  return isFallback() ? nullptr : toCacheIRStub()->next();
}

inline void ICFallbackStub::addNewStub(ICEntry* icEntry, ICCacheIRStub* stub) {
  MOZ_ASSERT(stub->next() == nullptr);
  stub->setNext(icEntry->firstStub());
  icEntry->setFirstStub(stub);
  state_.trackAttached();
}

AllocatableGeneralRegisterSet BaselineICAvailableGeneralRegs(size_t numInputs);

bool ICSupportsPolymorphicTypeData(JSOp op);

struct IonOsrTempData;

extern bool DoCallFallback(JSContext* cx, BaselineFrame* frame,
                           ICFallbackStub* stub, uint32_t argc, Value* vp,
                           MutableHandleValue res);

extern bool DoSpreadCallFallback(JSContext* cx, BaselineFrame* frame,
                                 ICFallbackStub* stub, Value* vp,
                                 MutableHandleValue res);

extern bool DoToBoolFallback(JSContext* cx, BaselineFrame* frame,
                             ICFallbackStub* stub, HandleValue arg,
                             MutableHandleValue ret);

extern bool DoGetElemSuperFallback(JSContext* cx, BaselineFrame* frame,
                                   ICFallbackStub* stub, HandleValue lhs,
                                   HandleValue rhs, HandleValue receiver,
                                   MutableHandleValue res);

extern bool DoGetElemFallback(JSContext* cx, BaselineFrame* frame,
                              ICFallbackStub* stub, HandleValue lhs,
                              HandleValue rhs, MutableHandleValue res);

extern bool DoSetElemFallback(JSContext* cx, BaselineFrame* frame,
                              ICFallbackStub* stub, Value* stack,
                              HandleValue objv, HandleValue index,
                              HandleValue rhs);

extern bool DoInFallback(JSContext* cx, BaselineFrame* frame,
                         ICFallbackStub* stub, HandleValue key,
                         HandleValue objValue, MutableHandleValue res);

extern bool DoHasOwnFallback(JSContext* cx, BaselineFrame* frame,
                             ICFallbackStub* stub, HandleValue keyValue,
                             HandleValue objValue, MutableHandleValue res);

extern bool DoCheckPrivateFieldFallback(JSContext* cx, BaselineFrame* frame,
                                        ICFallbackStub* stub,
                                        HandleValue objValue,
                                        HandleValue keyValue,
                                        MutableHandleValue res);

extern bool DoGetNameFallback(JSContext* cx, BaselineFrame* frame,
                              ICFallbackStub* stub, HandleObject envChain,
                              MutableHandleValue res);

extern bool DoBindNameFallback(JSContext* cx, BaselineFrame* frame,
                               ICFallbackStub* stub, HandleObject envChain,
                               MutableHandleValue res);

extern bool DoLazyConstantFallback(JSContext* cx, BaselineFrame* frame,
                                   ICFallbackStub* stub,
                                   MutableHandleValue res);

extern bool DoGetPropFallback(JSContext* cx, BaselineFrame* frame,
                              ICFallbackStub* stub, MutableHandleValue val,
                              MutableHandleValue res);

extern bool DoGetPropSuperFallback(JSContext* cx, BaselineFrame* frame,
                                   ICFallbackStub* stub, HandleValue receiver,
                                   MutableHandleValue val,
                                   MutableHandleValue res);

extern bool DoSetPropFallback(JSContext* cx, BaselineFrame* frame,
                              ICFallbackStub* stub, Value* stack,
                              HandleValue lhs, HandleValue rhs);

extern bool DoGetIteratorFallback(JSContext* cx, BaselineFrame* frame,
                                  ICFallbackStub* stub, HandleValue value,
                                  MutableHandleValue res);

extern bool DoOptimizeSpreadCallFallback(JSContext* cx, BaselineFrame* frame,
                                         ICFallbackStub* stub,
                                         HandleValue value,
                                         MutableHandleValue res);

extern bool DoInstanceOfFallback(JSContext* cx, BaselineFrame* frame,
                                 ICFallbackStub* stub, HandleValue lhs,
                                 HandleValue rhs, MutableHandleValue res);

extern bool DoTypeOfFallback(JSContext* cx, BaselineFrame* frame,
                             ICFallbackStub* stub, HandleValue val,
                             MutableHandleValue res);

extern bool DoTypeOfEqFallback(JSContext* cx, BaselineFrame* frame,
                               ICFallbackStub* stub, HandleValue val,
                               MutableHandleValue res);

extern bool DoToPropertyKeyFallback(JSContext* cx, BaselineFrame* frame,
                                    ICFallbackStub* stub, HandleValue val,
                                    MutableHandleValue res);

extern bool DoRestFallback(JSContext* cx, BaselineFrame* frame,
                           ICFallbackStub* stub, MutableHandleValue res);

extern bool DoUnaryArithFallback(JSContext* cx, BaselineFrame* frame,
                                 ICFallbackStub* stub, HandleValue val,
                                 MutableHandleValue res);

extern bool DoBinaryArithFallback(JSContext* cx, BaselineFrame* frame,
                                  ICFallbackStub* stub, HandleValue lhs,
                                  HandleValue rhs, MutableHandleValue ret);

extern bool DoNewArrayFallback(JSContext* cx, BaselineFrame* frame,
                               ICFallbackStub* stub, MutableHandleValue res);

extern bool DoNewObjectFallback(JSContext* cx, BaselineFrame* frame,
                                ICFallbackStub* stub, MutableHandleValue res);

extern bool DoLambdaFallback(JSContext* cx, BaselineFrame* frame,
                             ICFallbackStub* stub, MutableHandleValue res);

extern bool DoCompareFallback(JSContext* cx, BaselineFrame* frame,
                              ICFallbackStub* stub, HandleValue lhs,
                              HandleValue rhs, MutableHandleValue ret);

extern bool DoCloseIterFallback(JSContext* cx, BaselineFrame* frame,
                                ICFallbackStub* stub, HandleObject iter);

extern bool DoOptimizeGetIteratorFallback(JSContext* cx, BaselineFrame* frame,
                                          ICFallbackStub* stub,
                                          HandleValue value,
                                          MutableHandleValue res);
extern bool DoGetImportFallback(JSContext* cx, BaselineFrame* frame,
                                ICFallbackStub* stub, MutableHandleValue res);

}  // namespace jit
}  // namespace js

#endif /* jit_BaselineIC_h */
