876 lines
29 KiB
C++
876 lines
29 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
|
* 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 "jscompartment.h"
|
|
#include "jsnum.h"
|
|
#include "jsstr.h"
|
|
|
|
#include "jit/Ion.h"
|
|
#include "vm/ArgumentsObject.h"
|
|
|
|
#include "jsatominlines.h"
|
|
#include "jsobjinlines.h"
|
|
|
|
#include "vm/EnvironmentObject-inl.h"
|
|
#include "vm/Stack-inl.h"
|
|
#include "vm/String-inl.h"
|
|
|
|
namespace js {
|
|
|
|
/*
|
|
* Every possible consumer of MagicValue(JS_OPTIMIZED_ARGUMENTS) (as determined
|
|
* by ScriptAnalysis::needsArgsObj) must check for these magic values and, when
|
|
* one is received, act as if the value were the function's ArgumentsObject.
|
|
* Additionally, it is possible that, after 'arguments' was copied into a
|
|
* temporary, the arguments object has been created a some other failed guard
|
|
* that called JSScript::argumentsOptimizationFailed. In this case, it is
|
|
* always valid (and necessary) to replace JS_OPTIMIZED_ARGUMENTS with the real
|
|
* arguments object.
|
|
*/
|
|
static inline bool
|
|
IsOptimizedArguments(AbstractFramePtr frame, MutableHandleValue vp)
|
|
{
|
|
if (vp.isMagic(JS_OPTIMIZED_ARGUMENTS) && frame.script()->needsArgsObj())
|
|
vp.setObject(frame.argsObj());
|
|
return vp.isMagic(JS_OPTIMIZED_ARGUMENTS);
|
|
}
|
|
|
|
/*
|
|
* One optimized consumer of MagicValue(JS_OPTIMIZED_ARGUMENTS) is f.apply.
|
|
* However, this speculation must be guarded before calling 'apply' in case it
|
|
* is not the builtin Function.prototype.apply.
|
|
*/
|
|
static inline bool
|
|
GuardFunApplyArgumentsOptimization(JSContext* cx, AbstractFramePtr frame, CallArgs& args)
|
|
{
|
|
if (args.length() == 2 && IsOptimizedArguments(frame, args[1])) {
|
|
if (!IsNativeFunction(args.calleev(), js::fun_apply)) {
|
|
RootedScript script(cx, frame.script());
|
|
if (!JSScript::argumentsOptimizationFailed(cx, script))
|
|
return false;
|
|
args[1].setObject(frame.argsObj());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* 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_ARGUMENTS could flow into here.
|
|
return val.isMagic() && val.whyMagic() == JS_UNINITIALIZED_LEXICAL;
|
|
}
|
|
|
|
static inline bool
|
|
IsUninitializedLexicalSlot(HandleObject obj, HandleShape shape)
|
|
{
|
|
MOZ_ASSERT(shape);
|
|
if (obj->is<WithEnvironmentObject>())
|
|
return false;
|
|
// We check for IsImplicitDenseOrTypedArrayElement even though the shape
|
|
// is always a non-indexed property because proxy hooks may return a
|
|
// "non-native property found" shape, which happens to be encoded in the
|
|
// same way as the "dense element" shape. See MarkNonNativePropertyFound.
|
|
if (IsImplicitDenseOrTypedArrayElement(shape) ||
|
|
!shape->hasSlot() ||
|
|
!shape->hasDefaultGetter() ||
|
|
!shape->hasDefaultSetter())
|
|
{
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(obj->as<NativeObject>().containsPure(shape));
|
|
return IsUninitializedLexical(obj->as<NativeObject>().getSlot(shape->slot()));
|
|
}
|
|
|
|
static inline void
|
|
ReportUninitializedLexical(JSContext* cx, HandlePropertyName name)
|
|
{
|
|
ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, name);
|
|
}
|
|
|
|
static inline void
|
|
ReportUninitializedLexical(JSContext* cx, HandleScript script, jsbytecode* pc)
|
|
{
|
|
ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, script, pc);
|
|
}
|
|
|
|
static inline bool
|
|
CheckUninitializedLexical(JSContext* cx, PropertyName* name_, HandleValue val)
|
|
{
|
|
if (IsUninitializedLexical(val)) {
|
|
RootedPropertyName name(cx, name_);
|
|
ReportUninitializedLexical(cx, name);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static inline bool
|
|
CheckUninitializedLexical(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue val)
|
|
{
|
|
if (IsUninitializedLexical(val)) {
|
|
ReportUninitializedLexical(cx, script, pc);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static inline void
|
|
ReportRuntimeConstAssignment(JSContext* cx, HandlePropertyName name)
|
|
{
|
|
ReportRuntimeLexicalError(cx, JSMSG_BAD_CONST_ASSIGN, name);
|
|
}
|
|
|
|
static inline void
|
|
ReportRuntimeConstAssignment(JSContext* cx, HandleScript script, jsbytecode* pc)
|
|
{
|
|
ReportRuntimeLexicalError(cx, JSMSG_BAD_CONST_ASSIGN, script, pc);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
template <bool TypeOf> inline bool
|
|
FetchName(JSContext* cx, HandleObject obj, HandleObject obj2, HandlePropertyName name,
|
|
HandleShape shape, MutableHandleValue vp)
|
|
{
|
|
if (!shape) {
|
|
if (TypeOf) {
|
|
vp.setUndefined();
|
|
return true;
|
|
}
|
|
return ReportIsNotDefined(cx, name);
|
|
}
|
|
|
|
/* Take the slow path if shape was not found in a native object. */
|
|
if (!obj->isNative() || !obj2->isNative()) {
|
|
Rooted<jsid> id(cx, NameToId(name));
|
|
if (!GetProperty(cx, obj, obj, id, vp))
|
|
return false;
|
|
} else {
|
|
RootedObject normalized(cx, obj);
|
|
if (normalized->is<WithEnvironmentObject>() && !shape->hasDefaultGetter())
|
|
normalized = &normalized->as<WithEnvironmentObject>().object();
|
|
if (shape->isDataDescriptor() && shape->hasDefaultGetter()) {
|
|
/* Fast path for Object instance properties. */
|
|
MOZ_ASSERT(shape->hasSlot());
|
|
vp.set(obj2->as<NativeObject>().getSlot(shape->slot()));
|
|
} else {
|
|
if (!NativeGetExistingProperty(cx, normalized, obj2.as<NativeObject>(), shape, vp))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// We do our own explicit checking for |this|
|
|
if (name == cx->names().dotThis)
|
|
return true;
|
|
|
|
// NAME operations are the slow paths already, so unconditionally check
|
|
// for uninitialized lets.
|
|
return CheckUninitializedLexical(cx, name, vp);
|
|
}
|
|
|
|
inline bool
|
|
FetchNameNoGC(JSObject* pobj, Shape* shape, MutableHandleValue vp)
|
|
{
|
|
if (!shape || !pobj->isNative() || !shape->isDataDescriptor() || !shape->hasDefaultGetter())
|
|
return false;
|
|
|
|
vp.set(pobj->as<NativeObject>().getSlot(shape->slot()));
|
|
return !IsUninitializedLexical(vp);
|
|
}
|
|
|
|
inline bool
|
|
GetIntrinsicOperation(JSContext* cx, jsbytecode* pc, MutableHandleValue vp)
|
|
{
|
|
RootedPropertyName name(cx, cx->currentScript()->getName(pc));
|
|
return GlobalObject::getIntrinsicValue(cx, cx->global(), name, vp);
|
|
}
|
|
|
|
inline bool
|
|
SetIntrinsicOperation(JSContext* cx, JSScript* script, jsbytecode* pc, HandleValue val)
|
|
{
|
|
RootedPropertyName name(cx, script->getName(pc));
|
|
return GlobalObject::setIntrinsicValue(cx, cx->global(), name, val);
|
|
}
|
|
|
|
inline void
|
|
SetAliasedVarOperation(JSContext* cx, JSScript* script, jsbytecode* pc,
|
|
EnvironmentObject& obj, EnvironmentCoordinate ec, const Value& val,
|
|
MaybeCheckTDZ checkTDZ)
|
|
{
|
|
MOZ_ASSERT_IF(checkTDZ, !IsUninitializedLexical(obj.aliasedBinding(ec)));
|
|
|
|
// Avoid computing the name if no type updates are needed, as this may be
|
|
// expensive on scopes with large numbers of variables.
|
|
PropertyName* name = obj.isSingleton()
|
|
? EnvironmentCoordinateName(cx->caches.envCoordinateNameCache, script, pc)
|
|
: nullptr;
|
|
|
|
obj.setAliasedBinding(cx, ec, name, val);
|
|
}
|
|
|
|
inline bool
|
|
SetNameOperation(JSContext* cx, JSScript* script, jsbytecode* pc, HandleObject env,
|
|
HandleValue val)
|
|
{
|
|
MOZ_ASSERT(*pc == JSOP_SETNAME ||
|
|
*pc == JSOP_STRICTSETNAME ||
|
|
*pc == JSOP_SETGNAME ||
|
|
*pc == JSOP_STRICTSETGNAME);
|
|
MOZ_ASSERT_IF((*pc == JSOP_SETGNAME || *pc == JSOP_STRICTSETGNAME) &&
|
|
!script->hasNonSyntacticScope(),
|
|
env == cx->global() ||
|
|
env == &cx->global()->lexicalEnvironment() ||
|
|
env->is<RuntimeLexicalErrorObject>());
|
|
|
|
bool strict = *pc == JSOP_STRICTSETNAME || *pc == JSOP_STRICTSETGNAME;
|
|
RootedPropertyName 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()) {
|
|
RootedNativeObject varobj(cx);
|
|
if (env->is<DebugEnvironmentProxy>())
|
|
varobj = &env->as<DebugEnvironmentProxy>().environment().as<NativeObject>();
|
|
else
|
|
varobj = &env->as<NativeObject>();
|
|
MOZ_ASSERT(!varobj->getOpsSetProperty());
|
|
ok = NativeSetProperty(cx, varobj, id, val, receiver, Unqualified, result);
|
|
} else {
|
|
ok = SetProperty(cx, env, id, val, receiver, result);
|
|
}
|
|
return ok && result.checkStrictErrorOrWarning(cx, env, id, strict);
|
|
}
|
|
|
|
inline bool
|
|
DefLexicalOperation(JSContext* cx, Handle<LexicalEnvironmentObject*> lexicalEnv,
|
|
HandleObject varObj, HandlePropertyName name, unsigned attrs)
|
|
{
|
|
// Redeclaration checks should have already been done.
|
|
MOZ_ASSERT(CheckLexicalNameConflict(cx, lexicalEnv, varObj, name));
|
|
RootedId id(cx, NameToId(name));
|
|
RootedValue uninitialized(cx, MagicValue(JS_UNINITIALIZED_LEXICAL));
|
|
return NativeDefineProperty(cx, lexicalEnv, id, uninitialized, nullptr, nullptr, attrs);
|
|
}
|
|
|
|
inline bool
|
|
DefLexicalOperation(JSContext* cx, LexicalEnvironmentObject* lexicalEnvArg,
|
|
JSObject* varObjArg, JSScript* script, jsbytecode* pc)
|
|
{
|
|
MOZ_ASSERT(*pc == JSOP_DEFLET || *pc == JSOP_DEFCONST);
|
|
RootedPropertyName name(cx, script->getName(pc));
|
|
|
|
unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT;
|
|
if (*pc == JSOP_DEFCONST)
|
|
attrs |= JSPROP_READONLY;
|
|
|
|
Rooted<LexicalEnvironmentObject*> lexicalEnv(cx, lexicalEnvArg);
|
|
RootedObject varObj(cx, varObjArg);
|
|
MOZ_ASSERT_IF(!script->hasNonSyntacticScope(),
|
|
lexicalEnv == &cx->global()->lexicalEnvironment() && varObj == cx->global());
|
|
|
|
return DefLexicalOperation(cx, lexicalEnv, varObj, name, attrs);
|
|
}
|
|
|
|
inline void
|
|
InitGlobalLexicalOperation(JSContext* cx, LexicalEnvironmentObject* lexicalEnvArg,
|
|
JSScript* script, jsbytecode* pc, HandleValue value)
|
|
{
|
|
MOZ_ASSERT_IF(!script->hasNonSyntacticScope(),
|
|
lexicalEnvArg == &cx->global()->lexicalEnvironment());
|
|
MOZ_ASSERT(*pc == JSOP_INITGLEXICAL);
|
|
Rooted<LexicalEnvironmentObject*> lexicalEnv(cx, lexicalEnvArg);
|
|
RootedShape shape(cx, lexicalEnv->lookup(cx, script->getName(pc)));
|
|
MOZ_ASSERT(shape);
|
|
lexicalEnv->setSlotWithType(cx, shape, value);
|
|
}
|
|
|
|
inline bool
|
|
InitPropertyOperation(JSContext* cx, JSOp op, HandleObject obj, HandleId id, HandleValue rhs)
|
|
{
|
|
MOZ_ASSERT(obj->is<PlainObject>() || obj->is<JSFunction>());
|
|
unsigned propAttrs = GetInitDataPropAttrs(op);
|
|
return NativeDefineProperty(cx, obj.as<NativeObject>(), id, rhs,
|
|
nullptr, nullptr, propAttrs);
|
|
}
|
|
|
|
inline bool
|
|
DefVarOperation(JSContext* cx, HandleObject varobj, HandlePropertyName dn, unsigned attrs)
|
|
{
|
|
MOZ_ASSERT(varobj->isQualifiedVarObj());
|
|
|
|
#ifdef DEBUG
|
|
// Per spec, it is an error to redeclare a lexical binding. This should
|
|
// have already been checked.
|
|
if (JS_HasExtensibleLexicalEnvironment(varobj)) {
|
|
Rooted<LexicalEnvironmentObject*> lexicalEnv(cx);
|
|
lexicalEnv = &JS_ExtensibleLexicalEnvironment(varobj)->as<LexicalEnvironmentObject>();
|
|
MOZ_ASSERT(CheckVarNameConflict(cx, lexicalEnv, dn));
|
|
}
|
|
#endif
|
|
|
|
RootedShape prop(cx);
|
|
RootedObject obj2(cx);
|
|
if (!LookupProperty(cx, varobj, dn, &obj2, &prop))
|
|
return false;
|
|
|
|
/* Steps 8c, 8d. */
|
|
if (!prop || (obj2 != varobj && varobj->is<GlobalObject>())) {
|
|
if (!DefineProperty(cx, varobj, dn, UndefinedHandleValue, nullptr, nullptr, attrs))
|
|
return false;
|
|
}
|
|
|
|
if (varobj->is<GlobalObject>()) {
|
|
if (!varobj->compartment()->addToVarNames(cx, dn))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
NegOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue 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);
|
|
} else {
|
|
double d;
|
|
if (!ToNumber(cx, val, &d))
|
|
return false;
|
|
res.setNumber(-d);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
ToIdOperation(JSContext* cx, HandleScript script, jsbytecode* pc, 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::HandleObject receiver,
|
|
HandleValue key, MutableHandleValue res)
|
|
{
|
|
MOZ_ASSERT(op == JSOP_GETELEM || op == JSOP_CALLELEM || op == JSOP_GETELEM_SUPER);
|
|
MOZ_ASSERT_IF(op == JSOP_GETELEM || op == JSOP_CALLELEM, obj == receiver);
|
|
|
|
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);
|
|
|
|
assertSameCompartmentDebugOnly(cx, res);
|
|
return true;
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
GetPrimitiveElementOperation(JSContext* cx, JSOp op, JS::HandleValue receiver,
|
|
HandleValue key, MutableHandleValue res)
|
|
{
|
|
MOZ_ASSERT(op == JSOP_GETELEM || op == JSOP_CALLELEM);
|
|
|
|
// FIXME: Bug 1234324 We shouldn't be boxing here.
|
|
RootedObject boxed(cx, ToObjectFromStack(cx, receiver));
|
|
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);
|
|
|
|
assertSameCompartmentDebugOnly(cx, res);
|
|
return true;
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
GetElemOptimizedArguments(JSContext* cx, AbstractFramePtr frame, MutableHandleValue lref,
|
|
HandleValue rref, MutableHandleValue res, bool* done)
|
|
{
|
|
MOZ_ASSERT(!*done);
|
|
|
|
if (IsOptimizedArguments(frame, lref)) {
|
|
if (rref.isInt32()) {
|
|
int32_t i = rref.toInt32();
|
|
if (i >= 0 && uint32_t(i) < frame.numActualArgs()) {
|
|
res.set(frame.unaliasedActual(i));
|
|
*done = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
RootedScript script(cx, frame.script());
|
|
if (!JSScript::argumentsOptimizationFailed(cx, script))
|
|
return false;
|
|
|
|
lref.set(ObjectValue(frame.argsObj()));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
GetElementOperation(JSContext* cx, JSOp op, MutableHandleValue lref, HandleValue rref,
|
|
MutableHandleValue res)
|
|
{
|
|
MOZ_ASSERT(op == JSOP_GETELEM || op == JSOP_CALLELEM);
|
|
|
|
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()) {
|
|
RootedValue thisv(cx, lref);
|
|
return GetPrimitiveElementOperation(cx, op, thisv, rref, res);
|
|
}
|
|
|
|
RootedObject thisv(cx, &lref.toObject());
|
|
return GetObjectElementOperation(cx, op, thisv, thisv, rref, res);
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE JSString*
|
|
TypeOfOperation(const Value& v, JSRuntime* rt)
|
|
{
|
|
JSType type = js::TypeOfValue(v);
|
|
return TypeName(type, *rt->commonNames);
|
|
}
|
|
|
|
static inline JSString*
|
|
TypeOfObjectOperation(JSObject* obj, JSRuntime* rt)
|
|
{
|
|
JSType type = js::TypeOfObject(obj);
|
|
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));
|
|
MOZ_ASSERT(!obj->getClass()->getGetProperty());
|
|
MOZ_ASSERT(!obj->getClass()->getSetProperty());
|
|
|
|
RootedId id(cx);
|
|
if (!ToPropertyKey(cx, idval, &id))
|
|
return false;
|
|
|
|
unsigned flags = JSOp(*pc) == JSOP_INITHIDDENELEM ? 0 : JSPROP_ENUMERATE;
|
|
return DefineProperty(cx, obj, id, val, nullptr, nullptr, flags);
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
InitArrayElemOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, uint32_t index, HandleValue val)
|
|
{
|
|
JSOp op = JSOp(*pc);
|
|
MOZ_ASSERT(op == JSOP_INITELEM_ARRAY || op == JSOP_INITELEM_INC);
|
|
|
|
MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>());
|
|
|
|
if (op == JSOP_INITELEM_INC && index == INT32_MAX) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SPREAD_TOO_LARGE);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* If val is a hole, do not call DefineElement.
|
|
*
|
|
* Furthermore, if the current op is JSOP_INITELEM_INC, always call
|
|
* SetLengthProperty even if it is not the last element initialiser,
|
|
* because it may be followed by JSOP_SPREAD, which will not set the array
|
|
* length if nothing is spread.
|
|
*
|
|
* Alternatively, if the current op is JSOP_INITELEM_ARRAY, the length will
|
|
* have already been set by the earlier JSOP_NEWARRAY; JSOP_INITELEM_ARRAY
|
|
* cannot follow JSOP_SPREAD.
|
|
*/
|
|
if (val.isMagic(JS_ELEMENTS_HOLE)) {
|
|
if (op == JSOP_INITELEM_INC) {
|
|
if (!SetLengthProperty(cx, obj, index + 1))
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!DefineElement(cx, obj, index, val, nullptr, nullptr, JSPROP_ENUMERATE))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
ProcessCallSiteObjOperation(JSContext* cx, RootedObject& cso, RootedObject& raw,
|
|
RootedValue& rawValue)
|
|
{
|
|
bool extensible;
|
|
if (!IsExtensible(cx, cso, &extensible))
|
|
return false;
|
|
if (extensible) {
|
|
JSAtom* name = cx->names().raw;
|
|
if (!DefineProperty(cx, cso, name->asPropertyName(), rawValue, nullptr, nullptr, 0))
|
|
return false;
|
|
if (!FreezeObject(cx, raw))
|
|
return false;
|
|
if (!FreezeObject(cx, cso))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#define RELATIONAL_OP(OP) \
|
|
JS_BEGIN_MACRO \
|
|
/* Optimize for two int-tagged operands (typical loop control). */ \
|
|
if (lhs.isInt32() && rhs.isInt32()) { \
|
|
*res = lhs.toInt32() OP rhs.toInt32(); \
|
|
} else { \
|
|
if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) \
|
|
return false; \
|
|
if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) \
|
|
return false; \
|
|
if (lhs.isString() && rhs.isString()) { \
|
|
JSString* l = lhs.toString(); \
|
|
JSString* r = rhs.toString(); \
|
|
int32_t result; \
|
|
if (!CompareStrings(cx, l, r, &result)) \
|
|
return false; \
|
|
*res = result OP 0; \
|
|
} else { \
|
|
double l, r; \
|
|
if (!ToNumber(cx, lhs, &l) || !ToNumber(cx, rhs, &r)) \
|
|
return false; \
|
|
*res = (l OP r); \
|
|
} \
|
|
} \
|
|
return true; \
|
|
JS_END_MACRO
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
LessThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) {
|
|
RELATIONAL_OP(<);
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
LessThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) {
|
|
RELATIONAL_OP(<=);
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
GreaterThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) {
|
|
RELATIONAL_OP(>);
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
GreaterThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) {
|
|
RELATIONAL_OP(>=);
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
BitNot(JSContext* cx, HandleValue in, int* out)
|
|
{
|
|
int i;
|
|
if (!ToInt32(cx, in, &i))
|
|
return false;
|
|
*out = ~i;
|
|
return true;
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
BitXor(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out)
|
|
{
|
|
int left, right;
|
|
if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right))
|
|
return false;
|
|
*out = left ^ right;
|
|
return true;
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
BitOr(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out)
|
|
{
|
|
int left, right;
|
|
if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right))
|
|
return false;
|
|
*out = left | right;
|
|
return true;
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
BitAnd(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out)
|
|
{
|
|
int left, right;
|
|
if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right))
|
|
return false;
|
|
*out = left & right;
|
|
return true;
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
BitLsh(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out)
|
|
{
|
|
int32_t left, right;
|
|
if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right))
|
|
return false;
|
|
*out = uint32_t(left) << (right & 31);
|
|
return true;
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
BitRsh(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out)
|
|
{
|
|
int32_t left, right;
|
|
if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right))
|
|
return false;
|
|
*out = left >> (right & 31);
|
|
return true;
|
|
}
|
|
|
|
static MOZ_ALWAYS_INLINE bool
|
|
UrshOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue out)
|
|
{
|
|
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;
|
|
}
|
|
|
|
template <typename T>
|
|
static MOZ_ALWAYS_INLINE bool
|
|
SignExtendOperation(JSContext* cx, HandleValue in, int* out)
|
|
{
|
|
int32_t i;
|
|
if (!ToInt32(cx, in, &i))
|
|
return false;
|
|
*out = (T)i;
|
|
return true;
|
|
}
|
|
|
|
#undef RELATIONAL_OP
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* FastCallGuard is used to optimize calls to JS functions from natives written
|
|
* in C++, e.g. Array.prototype.map. If the callee is not Ion-compiled, this
|
|
* will just call js::Call. If the callee has a valid IonScript, however, it
|
|
* will enter Ion directly.
|
|
*/
|
|
class FastCallGuard
|
|
{
|
|
InvokeArgs args_;
|
|
RootedFunction fun_;
|
|
RootedScript script_;
|
|
|
|
// Constructing a JitContext is pretty expensive due to the TLS access,
|
|
// so only do this if we have to.
|
|
bool useIon_;
|
|
|
|
public:
|
|
FastCallGuard(JSContext* cx, const Value& fval)
|
|
: args_(cx)
|
|
, fun_(cx)
|
|
, script_(cx)
|
|
, useIon_(jit::IsIonEnabled(cx))
|
|
{
|
|
initFunction(fval);
|
|
}
|
|
|
|
void initFunction(const Value& fval) {
|
|
if (fval.isObject() && fval.toObject().is<JSFunction>()) {
|
|
JSFunction* fun = &fval.toObject().as<JSFunction>();
|
|
if (fun->isInterpreted())
|
|
fun_ = fun;
|
|
}
|
|
}
|
|
|
|
InvokeArgs& args() {
|
|
return args_;
|
|
}
|
|
|
|
bool call(JSContext* cx, HandleValue callee, HandleValue thisv, MutableHandleValue rval) {
|
|
args_.CallArgs::setCallee(callee);
|
|
args_.CallArgs::setThis(thisv);
|
|
|
|
if (useIon_ && fun_) {
|
|
if (!script_) {
|
|
script_ = JSFunction::getOrCreateScript(cx, fun_);
|
|
if (!script_)
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(fun_->nonLazyScript() == script_);
|
|
|
|
jit::MethodStatus status = jit::CanEnterUsingFastInvoke(cx, script_, args_.length());
|
|
if (status == jit::Method_Error)
|
|
return false;
|
|
if (status == jit::Method_Compiled) {
|
|
jit::JitExecStatus result = jit::FastInvoke(cx, fun_, args_);
|
|
if (IsErrorStatus(result))
|
|
return false;
|
|
|
|
MOZ_ASSERT(result == jit::JitExec_Ok);
|
|
rval.set(args_.CallArgs::rval());
|
|
return true;
|
|
}
|
|
|
|
MOZ_ASSERT(status == jit::Method_Skipped);
|
|
|
|
if (script_->canIonCompile()) {
|
|
// This script is not yet hot. Since calling into Ion is much
|
|
// faster here, bump the warm-up counter a bit to account for this.
|
|
script_->incWarmUpCounter(5);
|
|
}
|
|
}
|
|
|
|
if (!InternalCallOrConstruct(cx, args_, NO_CONSTRUCT))
|
|
return false;
|
|
|
|
rval.set(args_.CallArgs::rval());
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
FastCallGuard(const FastCallGuard& other) = delete;
|
|
void operator=(const FastCallGuard& other) = delete;
|
|
};
|
|
|
|
} /* namespace js */
|
|
|
|
#endif /* vm_Interpreter_inl_h */
|