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

#include "vm/Interpreter.h"

#include "mozilla/CheckedArithmetic.h"

#include "builtin/Math.h"
#include "builtin/Number.h"
#include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
#include "util/PortableMath.h"
#include "vm/BigIntType.h"
#include "vm/BytecodeUtil.h"  // JSDVG_SEARCH_STACK
#include "vm/JSAtomUtils.h"   // AtomizeString
#include "vm/Realm.h"
#include "vm/StaticStrings.h"
#include "vm/ThrowMsgKind.h"
#include "vm/Watchtower.h"

#include "vm/GlobalObject-inl.h"
#include "vm/JSAtomUtils-inl.h"  // PrimitiveValueToId, TypeName
#include "vm/JSContext-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/ObjectOperations-inl.h"
#include "vm/StringType-inl.h"

namespace js {

/*
 * Per ES6, lexical declarations may not be accessed in any fashion until they
 * are initialized (i.e., until the actual declaring statement is
 * executed). The various LEXICAL opcodes need to check if the slot is an
 * uninitialized let declaration, represented by the magic value
 * JS_UNINITIALIZED_LEXICAL.
 */
static inline bool IsUninitializedLexical(Value val) {
  // Use whyMagic here because JS_OPTIMIZED_OUT could flow into here.
  return val.isMagic() && val.whyMagic() == JS_UNINITIALIZED_LEXICAL;
}

static inline bool IsUninitializedLexicalSlot(HandleObject obj,
                                              const PropertyResult& prop) {
  MOZ_ASSERT(prop.isFound());
  if (obj->is<WithEnvironmentObject>()) {
    return false;
  }

  // Proxy hooks may return a non-native property.
  if (prop.isNonNativeProperty()) {
    return false;
  }

  PropertyInfo propInfo = prop.propertyInfo();
  if (!propInfo.isDataProperty()) {
    return false;
  }

  return IsUninitializedLexical(
      obj->as<NativeObject>().getSlot(propInfo.slot()));
}

static inline bool CheckUninitializedLexical(JSContext* cx,
                                             Handle<PropertyName*> name,
                                             HandleValue val) {
  if (IsUninitializedLexical(val)) {
    ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, name);
    return false;
  }
  return true;
}

enum class GetNameMode { Normal, TypeOf };

template <GetNameMode mode>
inline bool FetchName(JSContext* cx, HandleObject receiver, HandleObject holder,
                      Handle<PropertyName*> name, const PropertyResult& prop,
                      MutableHandleValue vp) {
  if (prop.isNotFound()) {
    switch (mode) {
      case GetNameMode::Normal:
        ReportIsNotDefined(cx, name);
        return false;
      case GetNameMode::TypeOf:
        vp.setUndefined();
        return true;
    }
  }

  /* Take the slow path if shape was not found in a native object. */
  if (!receiver->is<NativeObject>() || !holder->is<NativeObject>() ||
      (receiver->is<WithEnvironmentObject>() &&
       receiver->as<WithEnvironmentObject>().supportUnscopables())) {
    Rooted<jsid> id(cx, NameToId(name));
    if (!GetProperty(cx, receiver, receiver, id, vp)) {
      return false;
    }
  } else {
    PropertyInfo propInfo = prop.propertyInfo();
    if (propInfo.isDataProperty()) {
      /* Fast path for Object instance properties. */
      vp.set(holder->as<NativeObject>().getSlot(propInfo.slot()));
    } else {
      // Unwrap 'with' environments for reasons given in
      // GetNameBoundInEnvironment.
      RootedObject normalized(cx, MaybeUnwrapWithEnvironment(receiver));
      RootedId id(cx, NameToId(name));
      if (!NativeGetExistingProperty(cx, normalized, holder.as<NativeObject>(),
                                     id, propInfo, vp)) {
        return false;
      }
    }
  }

  // We do our own explicit checking for |this|
  if (name == cx->names().dot_this_) {
    return true;
  }

  // NAME operations are the slow paths already, so unconditionally check
  // for uninitialized lets.
  return CheckUninitializedLexical(cx, name, vp);
}

inline bool FetchNameNoGC(NativeObject* pobj, PropertyResult prop, Value* vp) {
  if (prop.isNotFound()) {
    return false;
  }

  PropertyInfo propInfo = prop.propertyInfo();
  if (!propInfo.isDataProperty()) {
    return false;
  }

  *vp = pobj->getSlot(propInfo.slot());
  return !IsUninitializedLexical(*vp);
}

template <js::GetNameMode mode>
inline bool GetEnvironmentName(JSContext* cx, HandleObject envChain,
                               Handle<PropertyName*> name,
                               MutableHandleValue vp) {
  {
    PropertyResult prop;
    NativeObject* pobj = nullptr;
    if (LookupNameNoGC(cx, name, envChain, &pobj, &prop)) {
      if (FetchNameNoGC(pobj, prop, vp.address())) {
        return true;
      }
    }
  }

  PropertyResult prop;
  RootedObject obj(cx), pobj(cx);
  if (!LookupName(cx, name, envChain, &obj, &pobj, &prop)) {
    return false;
  }

  return FetchName<mode>(cx, obj, pobj, name, prop, vp);
}

inline bool HasOwnProperty(JSContext* cx, HandleValue val, HandleValue idValue,
                           bool* result) {
  // As an optimization, provide a fast path when rooting is not necessary and
  // we can safely retrieve the object's shape.
  jsid id;
  if (val.isObject() && idValue.isPrimitive() &&
      PrimitiveValueToId<NoGC>(cx, idValue, &id)) {
    JSObject* obj = &val.toObject();
    PropertyResult prop;
    if (obj->is<NativeObject>() &&
        NativeLookupOwnProperty<NoGC>(cx, &obj->as<NativeObject>(), id,
                                      &prop)) {
      *result = prop.isFound();
      return true;
    }
  }

  // Step 1.
  RootedId key(cx);
  if (!ToPropertyKey(cx, idValue, &key)) {
    return false;
  }

  // Step 2.
  RootedObject obj(cx, ToObject(cx, val));
  if (!obj) {
    return false;
  }

  // Step 3.
  return HasOwnProperty(cx, obj, key, result);
}

inline bool GetIntrinsicOperation(JSContext* cx, HandleScript script,
                                  jsbytecode* pc, MutableHandleValue vp) {
  Rooted<PropertyName*> name(cx, script->getName(pc));
  return GlobalObject::getIntrinsicValue(cx, cx->global(), name, vp);
}

inline bool SetIntrinsicOperation(JSContext* cx, JSScript* script,
                                  jsbytecode* pc, HandleValue val) {
  Rooted<PropertyName*> name(cx, script->getName(pc));
  return GlobalObject::setIntrinsicValue(cx, cx->global(), name, val);
}

inline bool SetNameOperation(JSContext* cx, JSScript* script, jsbytecode* pc,
                             HandleObject env, HandleValue val) {
  MOZ_ASSERT(JSOp(*pc) == JSOp::SetName || JSOp(*pc) == JSOp::StrictSetName ||
             JSOp(*pc) == JSOp::SetGName || JSOp(*pc) == JSOp::StrictSetGName);
  MOZ_ASSERT_IF(
      JSOp(*pc) == JSOp::SetGName || JSOp(*pc) == JSOp::StrictSetGName,
      !script->hasNonSyntacticScope());
  MOZ_ASSERT_IF(
      JSOp(*pc) == JSOp::SetGName || JSOp(*pc) == JSOp::StrictSetGName,
      env == cx->global() || env == &cx->global()->lexicalEnvironment() ||
          env->is<RuntimeLexicalErrorObject>());

  bool strict =
      JSOp(*pc) == JSOp::StrictSetName || JSOp(*pc) == JSOp::StrictSetGName;
  Rooted<PropertyName*> name(cx, script->getName(pc));

  // In strict mode, assigning to an undeclared global variable is an
  // error. To detect this, we call NativeSetProperty directly and pass
  // Unqualified. It stores the error, if any, in |result|.
  bool ok;
  ObjectOpResult result;
  RootedId id(cx, NameToId(name));
  RootedValue receiver(cx, ObjectValue(*env));
  if (env->isUnqualifiedVarObj()) {
    Rooted<NativeObject*> varobj(cx);
    if (env->is<DebugEnvironmentProxy>()) {
      varobj =
          &env->as<DebugEnvironmentProxy>().environment().as<NativeObject>();
    } else {
      varobj = &env->as<NativeObject>();
    }
    MOZ_ASSERT(!varobj->getOpsSetProperty());
    ok = NativeSetProperty<Unqualified>(cx, varobj, id, val, receiver, result);
  } else {
    ok = SetProperty(cx, env, id, val, receiver, result);
  }
  return ok && result.checkStrictModeError(cx, env, id, strict);
}

inline void InitGlobalLexicalOperation(
    JSContext* cx, ExtensibleLexicalEnvironmentObject* lexicalEnv,
    JSScript* script, jsbytecode* pc, HandleValue value) {
  MOZ_ASSERT_IF(!script->hasNonSyntacticScope(),
                lexicalEnv == &cx->global()->lexicalEnvironment());
  MOZ_ASSERT(JSOp(*pc) == JSOp::InitGLexical);

  PropertyName* name = script->getName(pc);
  mozilla::Maybe<PropertyInfo> prop = lexicalEnv->lookup(cx, name);
  MOZ_ASSERT(prop.isSome());

  // We usually don't have to call Watchtower::watchPropertyValueChange because
  // this is an initialization instead of a mutation, and we don't optimize
  // loads of uninitialized lexicals in the JIT.
  //
  // The Debugger API allows initializing lexical bindings using
  // forceLexicalInitializationByName before we get here, so we use a slow path
  // if that feature has been used.
  if (MOZ_UNLIKELY(cx->hasDebuggerForcedLexicalInit)) {
    if (!IsUninitializedLexical(lexicalEnv->getSlot(prop->slot()))) {
      Watchtower::watchPropertyValueChange<AllowGC::NoGC>(
          cx, lexicalEnv, NameToId(name), value, *prop);
    }
  } else {
    MOZ_ASSERT(IsUninitializedLexical(lexicalEnv->getSlot(prop->slot())));
  }
  lexicalEnv->setSlot(prop->slot(), value);
}

inline bool InitPropertyOperation(JSContext* cx, jsbytecode* pc,
                                  HandleObject obj, Handle<PropertyName*> name,
                                  HandleValue rhs) {
  unsigned propAttrs = GetInitDataPropAttrs(JSOp(*pc));
  return DefineDataProperty(cx, obj, name, rhs, propAttrs);
}

static MOZ_ALWAYS_INLINE bool NegOperation(JSContext* cx,
                                           MutableHandleValue val,
                                           MutableHandleValue res) {
  /*
   * When the operand is int jsval, INT32_FITS_IN_JSVAL(i) implies
   * INT32_FITS_IN_JSVAL(-i) unless i is 0 or INT32_MIN when the
   * results, -0.0 or INT32_MAX + 1, are double values.
   */
  int32_t i;
  if (val.isInt32() && (i = val.toInt32()) != 0 && i != INT32_MIN) {
    res.setInt32(-i);
    return true;
  }

  if (!ToNumeric(cx, val)) {
    return false;
  }

  if (val.isBigInt()) {
    return BigInt::negValue(cx, val, res);
  }

  res.setNumberAssumeCanonicalNaN(-val.toNumber());
  return true;
}

static MOZ_ALWAYS_INLINE bool IncOperation(JSContext* cx, HandleValue val,
                                           MutableHandleValue res) {
  int32_t i;
  if (val.isInt32() && (i = val.toInt32()) != INT32_MAX) {
    res.setInt32(i + 1);
    return true;
  }

  if (val.isNumber()) {
    res.setNumberAssumeCanonicalNaN(val.toNumber() + 1);
    return true;
  }

  MOZ_ASSERT(val.isBigInt(), "+1 only callable on result of JSOp::ToNumeric");
  return BigInt::incValue(cx, val, res);
}

static MOZ_ALWAYS_INLINE bool DecOperation(JSContext* cx, HandleValue val,
                                           MutableHandleValue res) {
  int32_t i;
  if (val.isInt32() && (i = val.toInt32()) != INT32_MIN) {
    res.setInt32(i - 1);
    return true;
  }

  if (val.isNumber()) {
    res.setNumberAssumeCanonicalNaN(val.toNumber() - 1);
    return true;
  }

  MOZ_ASSERT(val.isBigInt(), "-1 only callable on result of JSOp::ToNumeric");
  return BigInt::decValue(cx, val, res);
}

static MOZ_ALWAYS_INLINE bool ToPropertyKeyOperation(JSContext* cx,
                                                     HandleValue idval,
                                                     MutableHandleValue res) {
  if (idval.isInt32()) {
    res.set(idval);
    return true;
  }

  RootedId id(cx);
  if (!ToPropertyKey(cx, idval, &id)) {
    return false;
  }

  res.set(IdToValue(id));
  return true;
}

static MOZ_ALWAYS_INLINE bool GetObjectElementOperation(
    JSContext* cx, JSOp op, JS::HandleObject obj, JS::HandleValue receiver,
    HandleValue key, MutableHandleValue res) {
  MOZ_ASSERT(op == JSOp::GetElem || op == JSOp::GetElemSuper);
  MOZ_ASSERT_IF(op == JSOp::GetElem, obj == &receiver.toObject());

  do {
    uint32_t index;
    if (IsDefinitelyIndex(key, &index)) {
      if (GetElementNoGC(cx, obj, receiver, index, res.address())) {
        break;
      }

      if (!GetElement(cx, obj, receiver, index, res)) {
        return false;
      }
      break;
    }

    if (key.isString()) {
      JSAtom* name = AtomizeString(cx, key.toString());
      if (!name) {
        return false;
      }
      if (name->isIndex(&index)) {
        if (GetElementNoGC(cx, obj, receiver, index, res.address())) {
          break;
        }
      } else {
        if (GetPropertyNoGC(cx, obj, receiver, name->asPropertyName(),
                            res.address())) {
          break;
        }
      }
    }

    RootedId id(cx);
    if (!ToPropertyKey(cx, key, &id)) {
      return false;
    }
    if (!GetProperty(cx, obj, receiver, id, res)) {
      return false;
    }
  } while (false);

  cx->debugOnlyCheck(res);
  return true;
}

static MOZ_ALWAYS_INLINE bool GetPrimitiveElementOperation(
    JSContext* cx, JS::HandleValue receiver, int receiverIndex, HandleValue key,
    MutableHandleValue res) {
  // FIXME: Bug 1234324 We shouldn't be boxing here.
  RootedObject boxed(
      cx, ToObjectFromStackForPropertyAccess(cx, receiver, receiverIndex, key));
  if (!boxed) {
    return false;
  }

  do {
    uint32_t index;
    if (IsDefinitelyIndex(key, &index)) {
      if (GetElementNoGC(cx, boxed, receiver, index, res.address())) {
        break;
      }

      if (!GetElement(cx, boxed, receiver, index, res)) {
        return false;
      }
      break;
    }

    if (key.isString()) {
      JSAtom* name = AtomizeString(cx, key.toString());
      if (!name) {
        return false;
      }
      if (name->isIndex(&index)) {
        if (GetElementNoGC(cx, boxed, receiver, index, res.address())) {
          break;
        }
      } else {
        if (GetPropertyNoGC(cx, boxed, receiver, name->asPropertyName(),
                            res.address())) {
          break;
        }
      }
    }

    RootedId id(cx);
    if (!ToPropertyKey(cx, key, &id)) {
      return false;
    }
    if (!GetProperty(cx, boxed, receiver, id, res)) {
      return false;
    }
  } while (false);

  cx->debugOnlyCheck(res);
  return true;
}

static MOZ_ALWAYS_INLINE bool GetElementOperationWithStackIndex(
    JSContext* cx, HandleValue lref, int lrefIndex, HandleValue rref,
    MutableHandleValue res) {
  uint32_t index;
  if (lref.isString() && IsDefinitelyIndex(rref, &index)) {
    JSString* str = lref.toString();
    if (index < str->length()) {
      str = cx->staticStrings().getUnitStringForElement(cx, str, index);
      if (!str) {
        return false;
      }
      res.setString(str);
      return true;
    }
  }

  if (lref.isPrimitive()) {
    return GetPrimitiveElementOperation(cx, lref, lrefIndex, rref, res);
  }

  RootedObject obj(cx, &lref.toObject());
  return GetObjectElementOperation(cx, JSOp::GetElem, obj, lref, rref, res);
}

// Wrapper for callVM from JIT.
static MOZ_ALWAYS_INLINE bool GetElementOperation(JSContext* cx,
                                                  HandleValue lref,
                                                  HandleValue rref,
                                                  MutableHandleValue res) {
  return GetElementOperationWithStackIndex(cx, lref, JSDVG_SEARCH_STACK, rref,
                                           res);
}

static MOZ_ALWAYS_INLINE JSString* TypeOfOperation(const Value& v,
                                                   JSRuntime* rt) {
  JSType type = js::TypeOfValue(v);
  return TypeName(type, *rt->commonNames);
}

static MOZ_ALWAYS_INLINE bool InitElemOperation(JSContext* cx, jsbytecode* pc,
                                                HandleObject obj,
                                                HandleValue idval,
                                                HandleValue val) {
  MOZ_ASSERT(!val.isMagic(JS_ELEMENTS_HOLE));

  RootedId id(cx);
  if (!ToPropertyKey(cx, idval, &id)) {
    return false;
  }

  unsigned flags = GetInitDataPropAttrs(JSOp(*pc));
  return DefineDataProperty(cx, obj, id, val, flags);
}

static MOZ_ALWAYS_INLINE bool CheckPrivateFieldOperation(JSContext* cx,
                                                         jsbytecode* pc,
                                                         HandleValue val,
                                                         HandleValue idval,
                                                         bool* result) {
  MOZ_ASSERT(idval.isSymbol());
  MOZ_ASSERT(idval.toSymbol()->isPrivateName());

  // Result had better not be a nullptr.
  MOZ_ASSERT(result);

  ThrowCondition condition;
  ThrowMsgKind msgKind;
  GetCheckPrivateFieldOperands(pc, &condition, &msgKind);

  // When we are using OnlyCheckRhs, we are implementing PrivateInExpr
  // This requires we throw if the rhs is not an object;
  //
  // The InlineCache for CheckPrivateField already checks for a
  // non-object rhs and refuses to attach in that circumstance.
  if (condition == ThrowCondition::OnlyCheckRhs) {
    if (!val.isObject()) {
      ReportInNotObjectError(cx, idval, val);
      return false;
    }
  }

  // Invoke the HostEnsureCanAddPrivateElement ( O ) host hook here
  // if the code is attempting to attach a new private element (which
  // corresponds to the ThrowHas Throw Condition).
  if (condition == ThrowCondition::ThrowHas) {
    if (JS::EnsureCanAddPrivateElementOp op =
            cx->runtime()->canAddPrivateElement) {
      if (!op(cx, val)) {
        return false;
      }
    }
  }

  if (!HasOwnProperty(cx, val, idval, result)) {
    return false;
  }

  if (!CheckPrivateFieldWillThrow(condition, *result)) {
    return true;
  }

  // Throw!
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            ThrowMsgKindToErrNum(msgKind));
  return false;
}

static inline JS::Symbol* NewPrivateName(JSContext* cx, Handle<JSAtom*> name) {
  return JS::Symbol::new_(cx, JS::SymbolCode::PrivateNameSymbol, name);
}

inline bool InitElemIncOperation(JSContext* cx, Handle<ArrayObject*> arr,
                                 uint32_t index, HandleValue val) {
  if (index == INT32_MAX) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_SPREAD_TOO_LARGE);
    return false;
  }

  // If val is a hole, do not call DefineDataElement.
  if (val.isMagic(JS_ELEMENTS_HOLE)) {
    // Always call SetLengthProperty even if this is not the last element
    // initialiser, because this may be followed by a SpreadElement loop,
    // which will not set the array length if nothing is spread.
    return SetLengthProperty(cx, arr, index + 1);
  }

  return DefineDataElement(cx, arr, index, val, JSPROP_ENUMERATE);
}

inline JSFunction* ReportIfNotFunction(
    JSContext* cx, HandleValue v, MaybeConstruct construct = NO_CONSTRUCT) {
  if (v.isObject() && v.toObject().is<JSFunction>()) {
    return &v.toObject().as<JSFunction>();
  }

  ReportIsNotFunction(cx, v, -1, construct);
  return nullptr;
}

static inline JSObject* SuperFunOperation(JSObject* callee) {
  MOZ_ASSERT(callee->as<JSFunction>().isClassConstructor());
  MOZ_ASSERT(
      callee->as<JSFunction>().baseScript()->isDerivedClassConstructor());

  return callee->as<JSFunction>().staticPrototype();
}

static inline JSObject* HomeObjectSuperBase(JSObject* homeObj) {
  MOZ_ASSERT(homeObj->is<PlainObject>() || homeObj->is<JSFunction>());

  return homeObj->staticPrototype();
}

static MOZ_ALWAYS_INLINE bool AddOperation(JSContext* cx,
                                           MutableHandleValue lhs,
                                           MutableHandleValue rhs,
                                           MutableHandleValue res) {
  if (lhs.isInt32() && rhs.isInt32()) {
    int32_t l = lhs.toInt32(), r = rhs.toInt32();
    int32_t t;
    if (MOZ_LIKELY(mozilla::SafeAdd(l, r, &t))) {
      res.setInt32(t);
      return true;
    }
  }

  if (!ToPrimitive(cx, lhs)) {
    return false;
  }
  if (!ToPrimitive(cx, rhs)) {
    return false;
  }

  bool lIsString = lhs.isString();
  bool rIsString = rhs.isString();
  if (lIsString || rIsString) {
    JSString* lstr;
    if (lIsString) {
      lstr = lhs.toString();
    } else {
      lstr = ToString<CanGC>(cx, lhs);
      if (!lstr) {
        return false;
      }
    }

    JSString* rstr;
    if (rIsString) {
      rstr = rhs.toString();
    } else {
      // Save/restore lstr in case of GC activity under ToString.
      lhs.setString(lstr);
      rstr = ToString<CanGC>(cx, rhs);
      if (!rstr) {
        return false;
      }
      lstr = lhs.toString();
    }
    JSString* str = ConcatStrings<NoGC>(cx, lstr, rstr);
    if (!str) {
      RootedString nlstr(cx, lstr), nrstr(cx, rstr);
      str = ConcatStrings<CanGC>(cx, nlstr, nrstr);
      if (!str) {
        return false;
      }
    }
    res.setString(str);
    return true;
  }

  if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) {
    return false;
  }

  if (lhs.isBigInt() || rhs.isBigInt()) {
    return BigInt::addValue(cx, lhs, rhs, res);
  }

  res.setNumberAssumeCanonicalNaN(lhs.toNumber() + rhs.toNumber());
  return true;
}

static MOZ_ALWAYS_INLINE bool SubOperation(JSContext* cx,
                                           MutableHandleValue lhs,
                                           MutableHandleValue rhs,
                                           MutableHandleValue res) {
  if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) {
    return false;
  }

  if (lhs.isBigInt() || rhs.isBigInt()) {
    return BigInt::subValue(cx, lhs, rhs, res);
  }

  res.setNumberAssumeCanonicalNaN(lhs.toNumber() - rhs.toNumber());
  return true;
}

static MOZ_ALWAYS_INLINE bool MulOperation(JSContext* cx,
                                           MutableHandleValue lhs,
                                           MutableHandleValue rhs,
                                           MutableHandleValue res) {
  if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) {
    return false;
  }

  if (lhs.isBigInt() || rhs.isBigInt()) {
    return BigInt::mulValue(cx, lhs, rhs, res);
  }

  res.setNumberAssumeCanonicalNaN(lhs.toNumber() * rhs.toNumber());
  return true;
}

static MOZ_ALWAYS_INLINE bool DivOperation(JSContext* cx,
                                           MutableHandleValue lhs,
                                           MutableHandleValue rhs,
                                           MutableHandleValue res) {
  if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) {
    return false;
  }

  if (lhs.isBigInt() || rhs.isBigInt()) {
    return BigInt::divValue(cx, lhs, rhs, res);
  }

  res.setNumberAssumeCanonicalNaN(NumberDiv(lhs.toNumber(), rhs.toNumber()));
  return true;
}

static MOZ_ALWAYS_INLINE bool ModOperation(JSContext* cx,
                                           MutableHandleValue lhs,
                                           MutableHandleValue rhs,
                                           MutableHandleValue res) {
  int32_t l, r;
  if (lhs.isInt32() && rhs.isInt32() && (l = lhs.toInt32()) >= 0 &&
      (r = rhs.toInt32()) > 0) {
    int32_t mod = l % r;
    res.setInt32(mod);
    return true;
  }

  if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) {
    return false;
  }

  if (lhs.isBigInt() || rhs.isBigInt()) {
    return BigInt::modValue(cx, lhs, rhs, res);
  }

  res.setNumberAssumeCanonicalNaN(NumberMod(lhs.toNumber(), rhs.toNumber()));
  return true;
}

static MOZ_ALWAYS_INLINE bool PowOperation(JSContext* cx,
                                           MutableHandleValue lhs,
                                           MutableHandleValue rhs,
                                           MutableHandleValue res) {
  if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) {
    return false;
  }

  if (lhs.isBigInt() || rhs.isBigInt()) {
    return BigInt::powValue(cx, lhs, rhs, res);
  }

  res.setNumberAssumeCanonicalNaN(ecmaPow(lhs.toNumber(), rhs.toNumber()));
  return true;
}

static MOZ_ALWAYS_INLINE bool BitNotOperation(JSContext* cx,
                                              MutableHandleValue in,
                                              MutableHandleValue out) {
  if (!ToInt32OrBigInt(cx, in)) {
    return false;
  }

  if (in.isBigInt()) {
    return BigInt::bitNotValue(cx, in, out);
  }

  out.setInt32(~in.toInt32());
  return true;
}

static MOZ_ALWAYS_INLINE bool BitXorOperation(JSContext* cx,
                                              MutableHandleValue lhs,
                                              MutableHandleValue rhs,
                                              MutableHandleValue out) {
  if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) {
    return false;
  }

  if (lhs.isBigInt() || rhs.isBigInt()) {
    return BigInt::bitXorValue(cx, lhs, rhs, out);
  }

  out.setInt32(lhs.toInt32() ^ rhs.toInt32());
  return true;
}

static MOZ_ALWAYS_INLINE bool BitOrOperation(JSContext* cx,
                                             MutableHandleValue lhs,
                                             MutableHandleValue rhs,
                                             MutableHandleValue out) {
  if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) {
    return false;
  }

  if (lhs.isBigInt() || rhs.isBigInt()) {
    return BigInt::bitOrValue(cx, lhs, rhs, out);
  }

  out.setInt32(lhs.toInt32() | rhs.toInt32());
  return true;
}

static MOZ_ALWAYS_INLINE bool BitAndOperation(JSContext* cx,
                                              MutableHandleValue lhs,
                                              MutableHandleValue rhs,
                                              MutableHandleValue out) {
  if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) {
    return false;
  }

  if (lhs.isBigInt() || rhs.isBigInt()) {
    return BigInt::bitAndValue(cx, lhs, rhs, out);
  }

  out.setInt32(lhs.toInt32() & rhs.toInt32());
  return true;
}

static MOZ_ALWAYS_INLINE bool BitLshOperation(JSContext* cx,
                                              MutableHandleValue lhs,
                                              MutableHandleValue rhs,
                                              MutableHandleValue out) {
  if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) {
    return false;
  }

  if (lhs.isBigInt() || rhs.isBigInt()) {
    return BigInt::lshValue(cx, lhs, rhs, out);
  }

  // Signed left-shift is undefined on overflow, so |lhs << (rhs & 31)| won't
  // work.  Instead, convert to unsigned space (where overflow is treated
  // modularly), perform the operation there, then convert back.
  uint32_t left = static_cast<uint32_t>(lhs.toInt32());
  uint8_t right = rhs.toInt32() & 31;
  out.setInt32(mozilla::WrapToSigned(left << right));
  return true;
}

static MOZ_ALWAYS_INLINE bool BitRshOperation(JSContext* cx,
                                              MutableHandleValue lhs,
                                              MutableHandleValue rhs,
                                              MutableHandleValue out) {
  if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) {
    return false;
  }

  if (lhs.isBigInt() || rhs.isBigInt()) {
    return BigInt::rshValue(cx, lhs, rhs, out);
  }

  out.setInt32(lhs.toInt32() >> (rhs.toInt32() & 31));
  return true;
}

static MOZ_ALWAYS_INLINE bool UrshOperation(JSContext* cx,
                                            MutableHandleValue lhs,
                                            MutableHandleValue rhs,
                                            MutableHandleValue out) {
  if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) {
    return false;
  }

  if (lhs.isBigInt() || rhs.isBigInt()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_BIGINT_TO_NUMBER);
    return false;
  }

  uint32_t left;
  int32_t right;
  if (!ToUint32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) {
    return false;
  }
  left >>= right & 31;
  out.setNumber(uint32_t(left));
  return true;
}

static MOZ_ALWAYS_INLINE void InitElemArrayOperation(JSContext* cx,
                                                     jsbytecode* pc,
                                                     Handle<ArrayObject*> arr,
                                                     HandleValue val) {
  MOZ_ASSERT(JSOp(*pc) == JSOp::InitElemArray);

  // The dense elements must have been initialized up to this index. The JIT
  // implementation also depends on this.
  uint32_t index = GET_UINT32(pc);
  MOZ_ASSERT(index < arr->getDenseCapacity());
  MOZ_ASSERT(index == arr->getDenseInitializedLength());

  // Bump the initialized length even for hole values to ensure the
  // index == initLength invariant holds for later InitElemArray ops.
  arr->setDenseInitializedLength(index + 1);

  if (val.isMagic(JS_ELEMENTS_HOLE)) {
    arr->initDenseElementHole(index);
  } else {
    arr->initDenseElement(index, val);
  }
}

/*
 * As an optimization, the interpreter creates a handful of reserved rooted
 * variables at the beginning, thus inserting them into the Rooted list once
 * upon entry. ReservedRooted "borrows" a reserved Rooted variable and uses it
 * within a local scope, resetting the value to nullptr (or the appropriate
 * equivalent for T) at scope end. This avoids inserting/removing the Rooted
 * from the rooter list, while preventing stale values from being kept alive
 * unnecessarily.
 */
template <typename T>
class ReservedRooted : public RootedOperations<T, ReservedRooted<T>> {
  MutableHandle<T> savedRoot;

 public:
  ReservedRooted(MutableHandle<T> root, const T& ptr) : savedRoot(root) {
    root.set(ptr);
  }

  explicit ReservedRooted(MutableHandle<T> root) : savedRoot(root) { clear(); }

  ~ReservedRooted() { clear(); }

  void clear() { savedRoot.set(JS::SafelyInitialized<T>::create()); }
  void set(const T& p) { savedRoot.set(p); }
  operator Handle<T>() { return savedRoot; }
  MutableHandle<T> operator&() { return savedRoot; }

  DECLARE_NONPOINTER_ACCESSOR_METHODS(savedRoot.get())
  DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(savedRoot.get())
  DECLARE_POINTER_CONSTREF_OPS(T)
  DECLARE_POINTER_ASSIGN_OPS(ReservedRooted, T)
};

} /* namespace js */

#endif /* vm_Interpreter_inl_h */
