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

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

#include <stddef.h>  // size_t
#include <stdint.h>  // uint16_t, uint32_t

#include "js/CallArgs.h"    // JS::CallArgs, JS::detail::CallArgsBase, JSNative
#include "js/RootingAPI.h"  // JS::{,Mutable}Handle, JS::Rooted
#include "js/Value.h"       // JS::Value, JSValueType

namespace js {

namespace jit {

enum class InlinableNative : uint16_t;
enum class TrampolineNative : uint16_t;

}  // namespace jit

}  // namespace js

/**
 * A class, expected to be passed by value, which represents the CallArgs for a
 * JSJitGetterOp.
 */
class JSJitGetterCallArgs : protected JS::MutableHandle<JS::Value> {
 public:
  explicit JSJitGetterCallArgs(const JS::CallArgs& args)
      : JS::MutableHandle<JS::Value>(args.rval()) {}

  explicit JSJitGetterCallArgs(JS::Rooted<JS::Value>* rooted)
      : JS::MutableHandle<JS::Value>(rooted) {}

  explicit JSJitGetterCallArgs(JS::MutableHandle<JS::Value> handle)
      : JS::MutableHandle<JS::Value>(handle) {}

  JS::MutableHandle<JS::Value> rval() { return *this; }
};

/**
 * A class, expected to be passed by value, which represents the CallArgs for a
 * JSJitSetterOp.
 */
class JSJitSetterCallArgs : protected JS::MutableHandle<JS::Value> {
 public:
  explicit JSJitSetterCallArgs(const JS::CallArgs& args)
      : JS::MutableHandle<JS::Value>(args[0]) {}

  explicit JSJitSetterCallArgs(JS::Rooted<JS::Value>* rooted)
      : JS::MutableHandle<JS::Value>(rooted) {}

  JS::MutableHandle<JS::Value> operator[](unsigned i) {
    MOZ_ASSERT(i == 0);
    return *this;
  }

  unsigned length() const { return 1; }

  // Add get() or maybe hasDefined() as needed
};

struct JSJitMethodCallArgsTraits;

/**
 * A class, expected to be passed by reference, which represents the CallArgs
 * for a JSJitMethodOp.
 */
class MOZ_NON_PARAM JSJitMethodCallArgs
    : protected JS::detail::CallArgsBase<JS::detail::NoUsedRval> {
 private:
  using Base = JS::detail::CallArgsBase<JS::detail::NoUsedRval>;
  friend struct JSJitMethodCallArgsTraits;

 public:
  explicit JSJitMethodCallArgs(const JS::CallArgs& args) {
    argv_ = args.array();
    argc_ = args.length();
  }

  JS::MutableHandle<JS::Value> rval() const { return Base::rval(); }

  unsigned length() const { return Base::length(); }

  JS::MutableHandle<JS::Value> operator[](unsigned i) const {
    return Base::operator[](i);
  }

  bool hasDefined(unsigned i) const { return Base::hasDefined(i); }

  JSObject& callee() const {
    // We can't use Base::callee() because that will try to poke at
    // this->usedRval_, which we don't have.
    return argv_[-2].toObject();
  }

  JS::Handle<JS::Value> get(unsigned i) const { return Base::get(i); }

  bool requireAtLeast(JSContext* cx, const char* fnname,
                      unsigned required) const {
    // Can just forward to Base, since it only needs the length and we
    // forward that already.
    return Base::requireAtLeast(cx, fnname, required);
  }
};

struct JSJitMethodCallArgsTraits {
  static constexpr size_t offsetOfArgv = offsetof(JSJitMethodCallArgs, argv_);
  static constexpr size_t offsetOfArgc = offsetof(JSJitMethodCallArgs, argc_);
};

using JSJitGetterOp = bool (*)(JSContext*, JS::Handle<JSObject*>, void*,
                               JSJitGetterCallArgs);
using JSJitSetterOp = bool (*)(JSContext*, JS::Handle<JSObject*>, void*,
                               JSJitSetterCallArgs);
using JSJitMethodOp = bool (*)(JSContext*, JS::Handle<JSObject*>, void*,
                               const JSJitMethodCallArgs&);

/**
 * This struct contains metadata passed from the DOM to the JS Engine for JIT
 * optimizations on DOM property accessors.
 *
 * Eventually, this should be made available to general JSAPI users as *not*
 * experimental and *not* a friend API, but we're not ready to do so yet.
 */
class JSJitInfo {
 public:
  enum OpType {
    Getter,
    Setter,
    Method,
    StaticMethod,
    InlinableNative,
    TrampolineNative,
    IgnoresReturnValueNative,
    // Must be last
    OpTypeCount
  };

  enum ArgType {
    // Basic types
    String = (1 << 0),
    Integer = (1 << 1),  // Only 32-bit or less
    Double = (1 << 2),   // Maybe we want to add Float sometime too
    Boolean = (1 << 3),
    Object = (1 << 4),
    Null = (1 << 5),

    // And derived types
    Numeric = Integer | Double,
    // Should "Primitive" use the WebIDL definition, which
    // excludes string and null, or the typical JS one that includes them?
    Primitive = Numeric | Boolean | Null | String,
    ObjectOrNull = Object | Null,
    Any = ObjectOrNull | Primitive,

    // Our sentinel value.
    ArgTypeListEnd = (1 << 31)
  };

  static_assert(Any & String, "Any must include String");
  static_assert(Any & Integer, "Any must include Integer");
  static_assert(Any & Double, "Any must include Double");
  static_assert(Any & Boolean, "Any must include Boolean");
  static_assert(Any & Object, "Any must include Object");
  static_assert(Any & Null, "Any must include Null");

  /**
   * An enum that describes what this getter/setter/method aliases.  This
   * determines what things can be hoisted past this call, and if this
   * call is movable what it can be hoisted past.
   */
  enum AliasSet {
    /**
     * Alias nothing: a constant value, getting it can't affect any other
     * values, nothing can affect it.
     */
    AliasNone,

    /**
     * Alias things that can modify the DOM but nothing else.  Doing the
     * call can't affect the behavior of any other function.
     */
    AliasDOMSets,

    /**
     * Alias the world.  Calling this can change arbitrary values anywhere
     * in the system.  Most things fall in this bucket.
     */
    AliasEverything,

    /** Must be last. */
    AliasSetCount
  };

  bool needsOuterizedThisObject() const {
    return type() != Getter && type() != Setter;
  }

  bool isTypedMethodJitInfo() const { return isTypedMethod; }

  OpType type() const { return OpType(type_); }

  AliasSet aliasSet() const { return AliasSet(aliasSet_); }

  JSValueType returnType() const { return JSValueType(returnType_); }

  union {
    JSJitGetterOp getter;
    JSJitSetterOp setter;
    JSJitMethodOp method;
    /** A DOM static method, used for Promise wrappers */
    JSNative staticMethod;
    JSNative ignoresReturnValueMethod;
  };

  static unsigned offsetOfIgnoresReturnValueNative() {
    return offsetof(JSJitInfo, ignoresReturnValueMethod);
  }

  union {
    uint16_t protoID;
    js::jit::InlinableNative inlinableNative;
    js::jit::TrampolineNative trampolineNative;
  };

  union {
    uint16_t depth;

    // Additional opcode for some InlinableNative functions.
    uint16_t nativeOp;
  };

  // These fields are carefully packed to take up 4 bytes.  If you need more
  // bits for whatever reason, please see if you can steal bits from existing
  // fields before adding more members to this structure.
  static constexpr size_t OpTypeBits = 4;
  static constexpr size_t AliasSetBits = 4;
  static constexpr size_t ReturnTypeBits = 8;
  static constexpr size_t SlotIndexBits = 10;

  /** The OpType that says what sort of function we are. */
  uint32_t type_ : OpTypeBits;

  /**
   * The alias set for this op.  This is a _minimal_ alias set; in
   * particular for a method it does not include whatever argument
   * conversions might do.  That's covered by argTypes and runtime
   * analysis of the actual argument types being passed in.
   */
  uint32_t aliasSet_ : AliasSetBits;

  /** The return type tag.  Might be JSVAL_TYPE_UNKNOWN. */
  uint32_t returnType_ : ReturnTypeBits;

  static_assert(OpTypeCount <= (1 << OpTypeBits),
                "Not enough space for OpType");
  static_assert(AliasSetCount <= (1 << AliasSetBits),
                "Not enough space for AliasSet");
  static_assert((sizeof(JSValueType) * 8) <= ReturnTypeBits,
                "Not enough space for JSValueType");

  /** Is op fallible? False in setters. */
  uint32_t isInfallible : 1;

  /**
   * Is op movable?  To be movable the op must
   * not AliasEverything, but even that might
   * not be enough (e.g. in cases when it can
   * throw or is explicitly not movable).
   */
  uint32_t isMovable : 1;

  /**
   * Can op be dead-code eliminated? Again, this
   * depends on whether the op can throw, in
   * addition to the alias set.
   */
  uint32_t isEliminatable : 1;

  // XXXbz should we have a JSValueType for the type of the member?
  /**
   * True if this is a getter that can always
   * get the value from a slot of the "this" object.
   */
  uint32_t isAlwaysInSlot : 1;

  /**
   * True if this is a getter that can sometimes (if the slot doesn't contain
   * UndefinedValue()) get the value from a slot of the "this" object.
   */
  uint32_t isLazilyCachedInSlot : 1;

  /** True if this is an instance of JSTypedMethodJitInfo. */
  uint32_t isTypedMethod : 1;

  /**
   * If isAlwaysInSlot or isSometimesInSlot is true,
   * the index of the slot to get the value from.
   * Otherwise 0.
   */
  uint32_t slotIndex : SlotIndexBits;

  static constexpr size_t maxSlotIndex = (1 << SlotIndexBits) - 1;
};

static_assert(sizeof(JSJitInfo) == (sizeof(void*) + 2 * sizeof(uint32_t)),
              "There are several thousand instances of JSJitInfo stored in "
              "a binary. Please don't increase its space requirements without "
              "verifying that there is no other way forward (better packing, "
              "smaller datatypes for fields, subclassing, etc.).");

struct JSTypedMethodJitInfo {
  // We use C-style inheritance here, rather than C++ style inheritance
  // because not all compilers support brace-initialization for non-aggregate
  // classes. Using C++ style inheritance and constructors instead of
  // brace-initialization would also force the creation of static
  // constructors (on some compilers) when JSJitInfo and JSTypedMethodJitInfo
  // structures are declared. Since there can be several thousand of these
  // structures present and we want to have roughly equivalent performance
  // across a range of compilers, we do things manually.
  JSJitInfo base;

  const JSJitInfo::ArgType* const argTypes; /* For a method, a list of sets of
                                               types that the function
                                               expects.  This can be used,
                                               for example, to figure out
                                               when argument coercions can
                                               have side-effects. */
};

#endif  // js_experimental_JitInfo_h
