summaryrefslogtreecommitdiffstats
path: root/js/src/vm/Interpreter-inl.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/Interpreter-inl.h')
-rw-r--r--js/src/vm/Interpreter-inl.h998
1 files changed, 998 insertions, 0 deletions
diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h
new file mode 100644
index 0000000000..fce9e9c2cb
--- /dev/null
+++ b/js/src/vm/Interpreter-inl.h
@@ -0,0 +1,998 @@
+/* -*- 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 vm_Interpreter_inl_h
+#define vm_Interpreter_inl_h
+
+#include "vm/Interpreter.h"
+
+#include "jslibmath.h"
+#include "jsmath.h"
+#include "jsnum.h"
+
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "util/CheckedArithmetic.h"
+#include "vm/ArgumentsObject.h"
+#include "vm/BigIntType.h"
+#include "vm/BytecodeUtil.h" // JSDVG_SEARCH_STACK
+#include "vm/JSAtomUtils.h" // AtomizeString
+#include "vm/Realm.h"
+#include "vm/SharedStencil.h" // GCThingIndex
+#include "vm/StaticStrings.h"
+#include "vm/ThrowMsgKind.h"
+#ifdef ENABLE_RECORD_TUPLE
+# include "vm/RecordTupleShared.h"
+#endif
+
+#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(const 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, PropertyName* name_,
+ HandleValue val) {
+ if (IsUninitializedLexical(val)) {
+ Rooted<PropertyName*> name(cx, name_);
+ ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, name);
+ return false;
+ }
+ return true;
+}
+
+inline bool GetLengthProperty(const Value& lval, MutableHandleValue vp) {
+ /* Optimize length accesses on strings, arrays, and arguments. */
+ if (lval.isString()) {
+ vp.setInt32(lval.toString()->length());
+ return true;
+ }
+ if (lval.isObject()) {
+ JSObject* obj = &lval.toObject();
+ if (obj->is<ArrayObject>()) {
+ vp.setNumber(obj->as<ArrayObject>().length());
+ return true;
+ }
+
+ if (obj->is<ArgumentsObject>()) {
+ ArgumentsObject* argsobj = &obj->as<ArgumentsObject>();
+ if (!argsobj->hasOverriddenLength()) {
+ uint32_t length = argsobj->initialLength();
+ MOZ_ASSERT(length < INT32_MAX);
+ vp.setInt32(int32_t(length));
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+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>()) {
+ 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;
+ JSObject* obj = nullptr;
+ NativeObject* pobj = nullptr;
+ if (LookupNameNoGC(cx, name, envChain, &obj, &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);
+
+ mozilla::Maybe<PropertyInfo> prop =
+ lexicalEnv->lookup(cx, script->getName(pc));
+ MOZ_ASSERT(prop.isSome());
+ 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.setNumber(-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.setNumber(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.setNumber(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()) {
+ JSString* str = key.toString();
+ JSAtom* name = str->isAtom() ? &str->asAtom() : AtomizeString(cx, str);
+ 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) {
+#ifdef ENABLE_RECORD_TUPLE
+ if (receiver.isExtendedPrimitive()) {
+ RootedId id(cx);
+ if (!ToPropertyKey(cx, key, &id)) {
+ return false;
+ }
+ RootedObject obj(cx, &receiver.toExtendedPrimitive());
+ if (!ExtendedPrimitiveGetProperty(cx, obj, receiver, id, res)) {
+ return false;
+ }
+ }
+#endif
+
+ // 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()) {
+ JSString* str = key.toString();
+ JSAtom* name = str->isAtom() ? &str->asAtom() : AtomizeString(cx, str);
+ 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));
+ if (id.isPrivateName()) {
+ // Clear enumerate flag off of private names.
+ flags &= ~JSPROP_ENUMERATE;
+ }
+ 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(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.setNumber(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.setNumber(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.setNumber(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.setNumber(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.setNumber(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.setNumber(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<T>
+ * 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>> {
+ Rooted<T>* savedRoot;
+
+ public:
+ ReservedRooted(Rooted<T>* root, const T& ptr) : savedRoot(root) {
+ *root = ptr;
+ }
+
+ explicit ReservedRooted(Rooted<T>* root) : savedRoot(root) {
+ *root = JS::SafelyInitialized<T>::create();
+ }
+
+ ~ReservedRooted() { *savedRoot = JS::SafelyInitialized<T>::create(); }
+
+ void set(const T& p) const { *savedRoot = p; }
+ operator Handle<T>() { return *savedRoot; }
+ operator Rooted<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 */