Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/. */
/*
* JavaScript "portable baseline interpreter": an interpreter that is
* capable of running ICs, but without any native code.
*
* See the [SMDOC] in vm/PortableBaselineInterpret.h for a high-level
* overview.
*/
#include "vm/PortableBaselineInterpret.h"
#include "mozilla/Maybe.h"
#include <algorithm>
#include "jsapi.h"
#include "builtin/DataViewObject.h"
#include "builtin/MapObject.h"
#include "builtin/String.h"
#include "debugger/DebugAPI.h"
#include "jit/BaselineFrame.h"
#include "jit/BaselineIC.h"
#include "jit/BaselineJIT.h"
#include "jit/CacheIR.h"
#include "jit/CacheIRCompiler.h"
#include "jit/CacheIRReader.h"
#include "jit/JitFrames.h"
#include "jit/JitScript.h"
#include "jit/JSJitFrameIter.h"
#include "jit/VMFunctions.h"
#include "proxy/DeadObjectProxy.h"
#include "proxy/DOMProxy.h"
#include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h"
#include "vm/EnvironmentObject.h"
#include "vm/EqualityOperations.h"
#include "vm/GeneratorObject.h"
#include "vm/Interpreter.h"
#include "vm/Iteration.h"
#include "vm/JitActivation.h"
#include "vm/JSScript.h"
#include "vm/Opcodes.h"
#include "vm/PlainObject.h"
#include "vm/Shape.h"
#include "debugger/DebugAPI-inl.h"
#include "jit/BaselineFrame-inl.h"
#include "jit/JitScript-inl.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/Interpreter-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/PlainObject-inl.h"
namespace js {
namespace pbl {
using namespace js::jit;
/*
* Debugging: enable `TRACE_INTERP` for an extremely detailed dump of
* what PBL is doing at every opcode step.
*/
// #define TRACE_INTERP
#ifdef TRACE_INTERP
# define TRACE_PRINTF(...) \
do { \
printf(__VA_ARGS__); \
fflush(stdout); \
} while (0)
#else
# define TRACE_PRINTF(...) \
do { \
} while (0)
#endif
// Whether we are using the "hybrid" strategy for ICs (see the [SMDOC]
// in PortableBaselineInterpret.h for more). This is currently a
// constant, but may become configurable in the future.
static const bool kHybridICs = true;
/*
* -----------------------------------------------
* Stack handling
* -----------------------------------------------
*/
// Large enough for an exit frame.
static const size_t kStackMargin = 1024;
/*
* A 64-bit value on the auxiliary stack. May either be a raw uint64_t
* or a `Value` (JS NaN-boxed value).
*/
struct StackVal {
uint64_t value;
explicit StackVal(uint64_t v) : value(v) {}
explicit StackVal(const Value& v) : value(v.asRawBits()) {}
uint64_t asUInt64() const { return value; }
Value asValue() const { return Value::fromRawBits(value); }
};
/*
* A native-pointer-sized value on the auxiliary stack. This is
* separate from the above because we support running on 32-bit
* systems as well! May either be a `void*` (or cast to a
* `CalleeToken`, which is a typedef for a `void*`), or a `uint32_t`,
* which always fits in a native pointer width on our supported
* platforms. (See static_assert below.)
*/
struct StackValNative {
static_assert(sizeof(uintptr_t) >= sizeof(uint32_t),
"Must be at least a 32-bit system to use PBL.");
uintptr_t value;
explicit StackValNative(void* v) : value(reinterpret_cast<uintptr_t>(v)) {}
explicit StackValNative(uint32_t v) : value(v) {}
void* asVoidPtr() const { return reinterpret_cast<void*>(value); }
CalleeToken asCalleeToken() const {
return reinterpret_cast<CalleeToken>(value);
}
};
// Assert that the stack alignment is no more than the size of a
// StackValNative -- we rely on this when setting up call frames.
static_assert(JitStackAlignment <= sizeof(StackValNative));
#define PUSH(val) *--sp = (val)
#define POP() (*sp++)
#define POPN(n) sp += (n)
#define PUSHNATIVE(val) \
do { \
StackValNative* nativeSP = reinterpret_cast<StackValNative*>(sp); \
*--nativeSP = (val); \
sp = reinterpret_cast<StackVal*>(nativeSP); \
} while (0)
#define POPNNATIVE(n) \
sp = reinterpret_cast<StackVal*>(reinterpret_cast<StackValNative*>(sp) + (n))
/*
* Helper class to manage the auxiliary stack and push/pop frames.
*/
struct Stack {
StackVal* fp;
StackVal* base;
StackVal* top;
StackVal* unwindingSP;
explicit Stack(PortableBaselineStack& pbs)
: fp(reinterpret_cast<StackVal*>(pbs.top)),
base(reinterpret_cast<StackVal*>(pbs.base)),
top(reinterpret_cast<StackVal*>(pbs.top)),
unwindingSP(nullptr) {}
MOZ_ALWAYS_INLINE bool check(StackVal* sp, size_t size, bool margin = true) {
return reinterpret_cast<uintptr_t>(base) + size +
(margin ? kStackMargin : 0) <=
reinterpret_cast<uintptr_t>(sp);
}
[[nodiscard]] MOZ_ALWAYS_INLINE StackVal* allocate(StackVal* sp,
size_t size) {
if (!check(sp, size, false)) {
return nullptr;
}
sp = reinterpret_cast<StackVal*>(reinterpret_cast<uintptr_t>(sp) - size);
return sp;
}
uint32_t frameSize(StackVal* sp, BaselineFrame* curFrame) const {
return sizeof(StackVal) * (reinterpret_cast<StackVal*>(fp) - sp);
}
[[nodiscard]] MOZ_ALWAYS_INLINE BaselineFrame* pushFrame(StackVal* sp,
JSContext* cx,
JSObject* envChain) {
TRACE_PRINTF("pushFrame: sp = %p fp = %p\n", sp, fp);
if (sp == base) {
return nullptr;
}
PUSHNATIVE(StackValNative(fp));
fp = sp;
TRACE_PRINTF("pushFrame: new fp = %p\n", fp);
BaselineFrame* frame =
reinterpret_cast<BaselineFrame*>(allocate(sp, BaselineFrame::Size()));
if (!frame) {
return nullptr;
}
frame->setFlags(BaselineFrame::Flags::RUNNING_IN_INTERPRETER);
frame->setEnvironmentChain(envChain);
JSScript* script = frame->script();
frame->setICScript(script->jitScript()->icScript());
frame->setInterpreterFields(script->code());
#ifdef DEBUG
frame->setDebugFrameSize(0);
#endif
return frame;
}
StackVal* popFrame() {
StackVal* newTOS =
reinterpret_cast<StackVal*>(reinterpret_cast<StackValNative*>(fp) + 1);
fp = reinterpret_cast<StackVal*>(
reinterpret_cast<StackValNative*>(fp)->asVoidPtr());
MOZ_ASSERT(fp);
TRACE_PRINTF("popFrame: fp = %p\n", fp);
return newTOS;
}
void setFrameSize(StackVal* sp, BaselineFrame* prevFrame) {
#ifdef DEBUG
MOZ_ASSERT(fp != nullptr);
uintptr_t frameSize =
reinterpret_cast<uintptr_t>(fp) - reinterpret_cast<uintptr_t>(sp);
MOZ_ASSERT(reinterpret_cast<uintptr_t>(fp) >=
reinterpret_cast<uintptr_t>(sp));
TRACE_PRINTF("pushExitFrame: fp = %p cur() = %p -> frameSize = %d\n", fp,
sp, int(frameSize));
MOZ_ASSERT(frameSize >= BaselineFrame::Size());
prevFrame->setDebugFrameSize(frameSize);
#endif
}
[[nodiscard]] MOZ_ALWAYS_INLINE StackVal* pushExitFrame(
StackVal* sp, BaselineFrame* prevFrame) {
uint8_t* prevFP =
reinterpret_cast<uint8_t*>(prevFrame) + BaselineFrame::Size();
MOZ_ASSERT(reinterpret_cast<StackVal*>(prevFP) == fp);
setFrameSize(sp, prevFrame);
if (!check(sp, sizeof(StackVal) * 4, false)) {
return nullptr;
}
PUSHNATIVE(StackValNative(
MakeFrameDescriptorForJitCall(FrameType::BaselineJS, 0)));
PUSHNATIVE(StackValNative(nullptr)); // fake return address.
PUSHNATIVE(StackValNative(prevFP));
StackVal* exitFP = sp;
fp = exitFP;
TRACE_PRINTF(" -> fp = %p\n", fp);
PUSHNATIVE(StackValNative(uint32_t(ExitFrameType::Bare)));
return exitFP;
}
void popExitFrame(StackVal* fp) {
StackVal* prevFP = reinterpret_cast<StackVal*>(
reinterpret_cast<StackValNative*>(fp)->asVoidPtr());
MOZ_ASSERT(prevFP);
this->fp = prevFP;
TRACE_PRINTF("popExitFrame: fp -> %p\n", fp);
}
BaselineFrame* frameFromFP() {
return reinterpret_cast<BaselineFrame*>(reinterpret_cast<uintptr_t>(fp) -
BaselineFrame::Size());
}
static HandleValue handle(StackVal* sp) {
return HandleValue::fromMarkedLocation(reinterpret_cast<Value*>(sp));
}
static MutableHandleValue handleMut(StackVal* sp) {
return MutableHandleValue::fromMarkedLocation(reinterpret_cast<Value*>(sp));
}
};
/*
* -----------------------------------------------
* Interpreter state
* -----------------------------------------------
*/
struct ICRegs {
CacheIRReader cacheIRReader;
static const int kMaxICVals = 16;
uint64_t icVals[kMaxICVals];
uint64_t icResult;
int extraArgs;
bool spreadCall;
ICRegs() : cacheIRReader(nullptr, nullptr) {}
};
struct State {
RootedValue value0;
RootedValue value1;
RootedValue value2;
RootedValue value3;
RootedValue res;
RootedObject obj0;
RootedObject obj1;
RootedObject obj2;
RootedString str0;
RootedString str1;
RootedScript script0;
Rooted<PropertyName*> name0;
Rooted<jsid> id0;
Rooted<JSAtom*> atom0;
RootedFunction fun0;
Rooted<Scope*> scope0;
explicit State(JSContext* cx)
: value0(cx),
value1(cx),
value2(cx),
value3(cx),
res(cx),
obj0(cx),
obj1(cx),
obj2(cx),
str0(cx),
str1(cx),
script0(cx),
name0(cx),
id0(cx),
atom0(cx),
fun0(cx),
scope0(cx) {}
};
/*
* -----------------------------------------------
* RAII helpers for pushing exit frames.
*
* (See [SMDOC] in PortableBaselineInterpret.h for more.)
* -----------------------------------------------
*/
class VMFrameManager {
JSContext* cx;
BaselineFrame* frame;
friend class VMFrame;
public:
VMFrameManager(JSContext*& cx_, BaselineFrame* frame_)
: cx(cx_), frame(frame_) {
// Once the manager exists, we need to create an exit frame to
// have access to the cx (unless the caller promises it is not
// calling into the rest of the runtime).
cx_ = nullptr;
}
void switchToFrame(BaselineFrame* frame) { this->frame = frame; }
// Provides the JSContext, but *only* if no calls into the rest of
// the runtime (that may invoke a GC or stack walk) occur. Avoids
// the overhead of pushing an exit frame.
JSContext* cxForLocalUseOnly() const { return cx; }
};
class VMFrame {
JSContext* cx;
Stack& stack;
StackVal* exitFP;
void* prevSavedStack;
public:
VMFrame(VMFrameManager& mgr, Stack& stack_, StackVal* sp, jsbytecode* pc)
: cx(mgr.cx), stack(stack_) {
mgr.frame->interpreterPC() = pc;
exitFP = stack.pushExitFrame(sp, mgr.frame);
if (!exitFP) {
return;
}
cx->activation()->asJit()->setJSExitFP(reinterpret_cast<uint8_t*>(exitFP));
prevSavedStack = cx->portableBaselineStack().top;
cx->portableBaselineStack().top = reinterpret_cast<void*>(spBelowFrame());
}
StackVal* spBelowFrame() {
return reinterpret_cast<StackVal*>(reinterpret_cast<uintptr_t>(exitFP) -
sizeof(StackValNative));
}
~VMFrame() {
stack.popExitFrame(exitFP);
cx->portableBaselineStack().top = prevSavedStack;
}
JSContext* getCx() const { return cx; }
operator JSContext*() const { return cx; }
bool success() const { return exitFP != nullptr; }
};
#define PUSH_EXIT_FRAME_OR_RET(value) \
VMFrame cx(frameMgr, stack, sp, pc); \
if (!cx.success()) { \
return value; \
} \
StackVal* sp = cx.spBelowFrame(); /* shadow the definition */ \
(void)sp; /* avoid unused-variable warnings */
#define PUSH_IC_FRAME() PUSH_EXIT_FRAME_OR_RET(ICInterpretOpResult::Error)
#define PUSH_FALLBACK_IC_FRAME() PUSH_EXIT_FRAME_OR_RET(PBIResult::Error)
#define PUSH_EXIT_FRAME() PUSH_EXIT_FRAME_OR_RET(PBIResult::Error)
/*
* -----------------------------------------------
* IC Interpreter
* -----------------------------------------------
*/
ICInterpretOpResult MOZ_ALWAYS_INLINE
ICInterpretOps(BaselineFrame* frame, VMFrameManager& frameMgr, State& state,
ICRegs& icregs, Stack& stack, StackVal* sp, ICCacheIRStub* cstub,
jsbytecode* pc) {
#define CACHEOP_CASE(name) \
cacheop_##name \
: TRACE_PRINTF("cacheop (frame %p pc %p stub %p): " #name "\n", frame, \
pc, cstub);
#define CACHEOP_CASE_FALLTHROUGH(name) CACHEOP_CASE(name)
#define CACHEOP_CASE_UNIMPL(name) cacheop_##name:
static const void* const addresses[long(CacheOp::NumOpcodes)] = {
#define OP(name, ...) &&cacheop_##name,
CACHE_IR_OPS(OP)
#undef OP
};
#define DISPATCH_CACHEOP() \
cacheop = icregs.cacheIRReader.readOp(); \
goto* addresses[long(cacheop)];
// We set a fixed bound on the number of icVals which is smaller than what IC
// generators may use. As a result we can't evaluate an IC if it defines too
// many values. Note that we don't need to check this when reading from icVals
// because we should have bailed out before the earlier write which defined the
// same value. Similarly, we don't need to check writes to locations which we've
// just read from.
#define BOUNDSCHECK(resultId) \
if (resultId.id() >= ICRegs::kMaxICVals) return ICInterpretOpResult::NextIC;
#define PREDICT_NEXT(name) \
if (icregs.cacheIRReader.peekOp() == CacheOp::name) { \
icregs.cacheIRReader.readOp(); \
goto cacheop_##name; \
}
#define PREDICT_RETURN() \
if (icregs.cacheIRReader.peekOp() == CacheOp::ReturnFromIC) { \
TRACE_PRINTF("stub successful, predicted return\n"); \
return ICInterpretOpResult::Return; \
}
CacheOp cacheop;
DISPATCH_CACHEOP();
CACHEOP_CASE(ReturnFromIC) {
TRACE_PRINTF("stub successful!\n");
return ICInterpretOpResult::Return;
}
CACHEOP_CASE(GuardToObject) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Value v = Value::fromRawBits(icregs.icVals[inputId.id()]);
TRACE_PRINTF("GuardToObject: icVal %" PRIx64 "\n",
icregs.icVals[inputId.id()]);
if (!v.isObject()) {
return ICInterpretOpResult::NextIC;
}
icregs.icVals[inputId.id()] = reinterpret_cast<uint64_t>(&v.toObject());
PREDICT_NEXT(GuardShape);
PREDICT_NEXT(GuardSpecificFunction);
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardIsNullOrUndefined) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Value v = Value::fromRawBits(icregs.icVals[inputId.id()]);
if (!v.isNullOrUndefined()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardIsNull) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Value v = Value::fromRawBits(icregs.icVals[inputId.id()]);
if (!v.isNull()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardIsUndefined) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Value v = Value::fromRawBits(icregs.icVals[inputId.id()]);
if (!v.isUndefined()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardIsNotUninitializedLexical) {
ValOperandId valId = icregs.cacheIRReader.valOperandId();
Value val = Value::fromRawBits(icregs.icVals[valId.id()]);
if (val == MagicValue(JS_UNINITIALIZED_LEXICAL)) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardToBoolean) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Value v = Value::fromRawBits(icregs.icVals[inputId.id()]);
if (!v.isBoolean()) {
return ICInterpretOpResult::NextIC;
}
icregs.icVals[inputId.id()] = v.toBoolean() ? 1 : 0;
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardToString) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Value v = Value::fromRawBits(icregs.icVals[inputId.id()]);
if (!v.isString()) {
return ICInterpretOpResult::NextIC;
}
icregs.icVals[inputId.id()] = reinterpret_cast<uint64_t>(v.toString());
PREDICT_NEXT(GuardToString);
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardToSymbol) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Value v = Value::fromRawBits(icregs.icVals[inputId.id()]);
if (!v.isSymbol()) {
return ICInterpretOpResult::NextIC;
}
icregs.icVals[inputId.id()] = reinterpret_cast<uint64_t>(v.toSymbol());
PREDICT_NEXT(GuardSpecificSymbol);
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardToBigInt) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Value v = Value::fromRawBits(icregs.icVals[inputId.id()]);
if (!v.isBigInt()) {
return ICInterpretOpResult::NextIC;
}
icregs.icVals[inputId.id()] = reinterpret_cast<uint64_t>(v.toBigInt());
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardIsNumber) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Value v = Value::fromRawBits(icregs.icVals[inputId.id()]);
if (!v.isNumber()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardToInt32) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Value v = Value::fromRawBits(icregs.icVals[inputId.id()]);
TRACE_PRINTF("GuardToInt32 (%d): icVal %" PRIx64 "\n", inputId.id(),
icregs.icVals[inputId.id()]);
if (!v.isInt32()) {
return ICInterpretOpResult::NextIC;
}
// N.B.: we don't need to unbox because the low 32 bits are
// already the int32 itself, and we are careful when using
// `Int32Operand`s to only use those bits.
PREDICT_NEXT(GuardToInt32);
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardToNonGCThing) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Value input = Value::fromRawBits(icregs.icVals[inputId.id()]);
if (input.isGCThing()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardBooleanToInt32) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Int32OperandId resultId = icregs.cacheIRReader.int32OperandId();
BOUNDSCHECK(resultId);
Value v = Value::fromRawBits(icregs.icVals[inputId.id()]);
if (!v.isBoolean()) {
return ICInterpretOpResult::NextIC;
}
icregs.icVals[resultId.id()] = v.toBoolean() ? 1 : 0;
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardToInt32Index) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Int32OperandId resultId = icregs.cacheIRReader.int32OperandId();
BOUNDSCHECK(resultId);
Value val = Value::fromRawBits(icregs.icVals[inputId.id()]);
if (val.isInt32()) {
icregs.icVals[resultId.id()] = val.toInt32();
DISPATCH_CACHEOP();
} else if (val.isDouble()) {
double doubleVal = val.toDouble();
if (doubleVal >= double(INT32_MIN) && doubleVal <= double(INT32_MAX)) {
icregs.icVals[resultId.id()] = int32_t(doubleVal);
DISPATCH_CACHEOP();
}
}
return ICInterpretOpResult::NextIC;
}
CACHEOP_CASE(Int32ToIntPtr) {
Int32OperandId inputId = icregs.cacheIRReader.int32OperandId();
IntPtrOperandId resultId = icregs.cacheIRReader.intPtrOperandId();
BOUNDSCHECK(resultId);
int32_t input = int32_t(icregs.icVals[inputId.id()]);
// Note that this must sign-extend to pointer width:
icregs.icVals[resultId.id()] = intptr_t(input);
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardToInt32ModUint32) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Int32OperandId resultId = icregs.cacheIRReader.int32OperandId();
BOUNDSCHECK(resultId);
Value input = Value::fromRawBits(icregs.icVals[inputId.id()]);
if (input.isInt32()) {
icregs.icVals[resultId.id()] = Int32Value(input.toInt32()).asRawBits();
DISPATCH_CACHEOP();
} else if (input.isDouble()) {
double doubleVal = input.toDouble();
// Accept any double that fits in an int64_t but truncate the top 32 bits.
if (doubleVal >= double(INT64_MIN) && doubleVal <= double(INT64_MAX)) {
icregs.icVals[resultId.id()] =
Int32Value(int64_t(doubleVal)).asRawBits();
DISPATCH_CACHEOP();
}
}
return ICInterpretOpResult::NextIC;
}
CACHEOP_CASE(GuardNonDoubleType) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
ValueType type = icregs.cacheIRReader.valueType();
Value val = Value::fromRawBits(icregs.icVals[inputId.id()]);
switch (type) {
case ValueType::String:
if (!val.isString()) {
return ICInterpretOpResult::NextIC;
}
break;
case ValueType::Symbol:
if (!val.isSymbol()) {
return ICInterpretOpResult::NextIC;
}
break;
case ValueType::BigInt:
if (!val.isBigInt()) {
return ICInterpretOpResult::NextIC;
}
break;
case ValueType::Int32:
if (!val.isInt32()) {
return ICInterpretOpResult::NextIC;
}
break;
case ValueType::Boolean:
if (!val.isBoolean()) {
return ICInterpretOpResult::NextIC;
}
break;
case ValueType::Undefined:
if (!val.isUndefined()) {
return ICInterpretOpResult::NextIC;
}
break;
case ValueType::Null:
if (!val.isNull()) {
return ICInterpretOpResult::NextIC;
}
break;
default:
MOZ_CRASH("Unexpected type");
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardShape) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t shapeOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
uintptr_t expectedShape =
cstub->stubInfo()->getStubRawWord(cstub, shapeOffset);
if (reinterpret_cast<uintptr_t>(obj->shape()) != expectedShape) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardFuse) {
RealmFuses::FuseIndex fuseIndex = icregs.cacheIRReader.realmFuseIndex();
if (!frameMgr.cxForLocalUseOnly()
->realm()
->realmFuses.getFuseByIndex(fuseIndex)
->intact()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardProto) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t protoOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
JSObject* proto = reinterpret_cast<JSObject*>(
cstub->stubInfo()->getStubRawWord(cstub, protoOffset));
if (obj->staticPrototype() != proto) {
return ICInterpretOpResult::NextIC;
}
PREDICT_NEXT(LoadProto);
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardNullProto) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
if (obj->taggedProto().raw()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardClass) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
GuardClassKind kind = icregs.cacheIRReader.guardClassKind();
JSObject* object = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
switch (kind) {
case GuardClassKind::Array:
if (object->getClass() != &ArrayObject::class_) {
return ICInterpretOpResult::NextIC;
}
break;
case GuardClassKind::PlainObject:
if (object->getClass() != &PlainObject::class_) {
return ICInterpretOpResult::NextIC;
}
break;
case GuardClassKind::FixedLengthArrayBuffer:
if (object->getClass() != &FixedLengthArrayBufferObject::class_) {
return ICInterpretOpResult::NextIC;
}
break;
case GuardClassKind::FixedLengthSharedArrayBuffer:
if (object->getClass() != &FixedLengthSharedArrayBufferObject::class_) {
return ICInterpretOpResult::NextIC;
}
break;
case GuardClassKind::FixedLengthDataView:
if (object->getClass() != &FixedLengthDataViewObject::class_) {
return ICInterpretOpResult::NextIC;
}
break;
case GuardClassKind::MappedArguments:
if (object->getClass() != &MappedArgumentsObject::class_) {
return ICInterpretOpResult::NextIC;
}
break;
case GuardClassKind::UnmappedArguments:
if (object->getClass() != &UnmappedArgumentsObject::class_) {
return ICInterpretOpResult::NextIC;
}
break;
case GuardClassKind::WindowProxy:
if (object->getClass() !=
frameMgr.cxForLocalUseOnly()->runtime()->maybeWindowProxyClass()) {
return ICInterpretOpResult::NextIC;
}
break;
case GuardClassKind::JSFunction:
if (!object->is<JSFunction>()) {
return ICInterpretOpResult::NextIC;
}
break;
case GuardClassKind::Set:
if (object->getClass() != &SetObject::class_) {
return ICInterpretOpResult::NextIC;
}
break;
case GuardClassKind::Map:
if (object->getClass() != &MapObject::class_) {
return ICInterpretOpResult::NextIC;
}
break;
case GuardClassKind::BoundFunction:
if (object->getClass() != &BoundFunctionObject::class_) {
return ICInterpretOpResult::NextIC;
}
break;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardAnyClass) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t claspOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
JSClass* clasp = reinterpret_cast<JSClass*>(
cstub->stubInfo()->getStubRawWord(cstub, claspOffset));
if (obj->getClass() != clasp) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardGlobalGeneration) {
uint32_t expectedOffset = icregs.cacheIRReader.stubOffset();
uint32_t generationAddrOffset = icregs.cacheIRReader.stubOffset();
uint32_t expected =
cstub->stubInfo()->getStubRawInt32(cstub, expectedOffset);
uint32_t* generationAddr = reinterpret_cast<uint32_t*>(
cstub->stubInfo()->getStubRawWord(cstub, generationAddrOffset));
if (*generationAddr != expected) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(HasClassResult) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t claspOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
JSClass* clasp = reinterpret_cast<JSClass*>(
cstub->stubInfo()->getStubRawWord(cstub, claspOffset));
icregs.icResult = BooleanValue(obj->getClass() == clasp).asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardCompartment) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t globalOffset = icregs.cacheIRReader.stubOffset();
uint32_t compartmentOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
JSObject* global = reinterpret_cast<JSObject*>(
cstub->stubInfo()->getStubRawWord(cstub, globalOffset));
JS::Compartment* compartment = reinterpret_cast<JS::Compartment*>(
cstub->stubInfo()->getStubRawWord(cstub, compartmentOffset));
if (IsDeadProxyObject(global)) {
return ICInterpretOpResult::NextIC;
}
if (obj->compartment() != compartment) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardIsExtensible) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
if (obj->nonProxyIsExtensible()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardIsNativeObject) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
if (!obj->is<NativeObject>()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardIsProxy) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
if (!obj->is<ProxyObject>()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardIsNotProxy) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
if (obj->is<ProxyObject>()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardIsNotArrayBufferMaybeShared) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
const JSClass* clasp = obj->getClass();
if (clasp == &ArrayBufferObject::protoClass_ ||
clasp == &SharedArrayBufferObject::protoClass_) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardIsTypedArray) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
if (!IsTypedArrayClass(obj->getClass())) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardHasProxyHandler) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t handlerOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
BaseProxyHandler* handler = reinterpret_cast<BaseProxyHandler*>(
cstub->stubInfo()->getStubRawWord(cstub, handlerOffset));
if (obj->as<ProxyObject>().handler() != handler) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardIsNotDOMProxy) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
if (obj->as<ProxyObject>().handler()->family() ==
GetDOMProxyHandlerFamily()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardSpecificObject) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t expectedOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
JSObject* expected = reinterpret_cast<JSObject*>(
cstub->stubInfo()->getStubRawWord(cstub, expectedOffset));
if (obj != expected) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardObjectIdentity) {
ObjOperandId obj1Id = icregs.cacheIRReader.objOperandId();
ObjOperandId obj2Id = icregs.cacheIRReader.objOperandId();
JSObject* obj1 = reinterpret_cast<JSObject*>(icregs.icVals[obj1Id.id()]);
JSObject* obj2 = reinterpret_cast<JSObject*>(icregs.icVals[obj2Id.id()]);
if (obj1 != obj2) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardSpecificFunction) {
ObjOperandId funId = icregs.cacheIRReader.objOperandId();
uint32_t expectedOffset = icregs.cacheIRReader.stubOffset();
uint32_t nargsAndFlagsOffset = icregs.cacheIRReader.stubOffset();
(void)nargsAndFlagsOffset; // Unused.
uintptr_t expected =
cstub->stubInfo()->getStubRawWord(cstub, expectedOffset);
if (expected != icregs.icVals[funId.id()]) {
return ICInterpretOpResult::NextIC;
}
PREDICT_NEXT(LoadArgumentFixedSlot);
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardFunctionScript) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t expectedOffset = icregs.cacheIRReader.stubOffset();
uint32_t nargsAndFlagsOffset = icregs.cacheIRReader.stubOffset();
JSFunction* fun = reinterpret_cast<JSFunction*>(icregs.icVals[objId.id()]);
BaseScript* expected = reinterpret_cast<BaseScript*>(
cstub->stubInfo()->getStubRawWord(cstub, expectedOffset));
(void)nargsAndFlagsOffset;
if (!fun->hasBaseScript() || fun->baseScript() != expected) {
return ICInterpretOpResult::NextIC;
}
PREDICT_NEXT(CallScriptedFunction);
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardSpecificAtom) {
StringOperandId strId = icregs.cacheIRReader.stringOperandId();
uint32_t expectedOffset = icregs.cacheIRReader.stubOffset();
uintptr_t expected =
cstub->stubInfo()->getStubRawWord(cstub, expectedOffset);
if (expected != icregs.icVals[strId.id()]) {
// TODO: BaselineCacheIRCompiler also checks for equal strings
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardSpecificSymbol) {
SymbolOperandId symId = icregs.cacheIRReader.symbolOperandId();
uint32_t expectedOffset = icregs.cacheIRReader.stubOffset();
uintptr_t expected =
cstub->stubInfo()->getStubRawWord(cstub, expectedOffset);
if (expected != icregs.icVals[symId.id()]) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardSpecificInt32) {
Int32OperandId numId = icregs.cacheIRReader.int32OperandId();
int32_t expected = icregs.cacheIRReader.int32Immediate();
if (expected != int32_t(icregs.icVals[numId.id()])) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardNoDenseElements) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
if (obj->as<NativeObject>().getDenseInitializedLength() != 0) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardStringToIndex) {
StringOperandId strId = icregs.cacheIRReader.stringOperandId();
Int32OperandId resultId = icregs.cacheIRReader.int32OperandId();
BOUNDSCHECK(resultId);
JSString* str = reinterpret_cast<JSString*>(icregs.icVals[strId.id()]);
int32_t result;
if (str->hasIndexValue()) {
uint32_t index = str->getIndexValue();
MOZ_ASSERT(index <= INT32_MAX);
result = index;
} else {
result = GetIndexFromString(str);
if (result < 0) {
return ICInterpretOpResult::NextIC;
}
}
icregs.icVals[resultId.id()] = result;
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardStringToInt32) {
StringOperandId strId = icregs.cacheIRReader.stringOperandId();
Int32OperandId resultId = icregs.cacheIRReader.int32OperandId();
BOUNDSCHECK(resultId);
JSString* str = reinterpret_cast<JSString*>(icregs.icVals[strId.id()]);
int32_t result;
// Use indexed value as fast path if possible.
if (str->hasIndexValue()) {
uint32_t index = str->getIndexValue();
MOZ_ASSERT(index <= INT32_MAX);
result = index;
} else {
if (!GetInt32FromStringPure(frameMgr.cxForLocalUseOnly(), str, &result)) {
return ICInterpretOpResult::NextIC;
}
}
icregs.icVals[resultId.id()] = result;
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardStringToNumber) {
StringOperandId strId = icregs.cacheIRReader.stringOperandId();
NumberOperandId resultId = icregs.cacheIRReader.numberOperandId();
BOUNDSCHECK(resultId);
JSString* str = reinterpret_cast<JSString*>(icregs.icVals[strId.id()]);
Value result;
// Use indexed value as fast path if possible.
if (str->hasIndexValue()) {
uint32_t index = str->getIndexValue();
MOZ_ASSERT(index <= INT32_MAX);
result = Int32Value(index);
} else {
double value;
if (!StringToNumberPure(frameMgr.cxForLocalUseOnly(), str, &value)) {
return ICInterpretOpResult::NextIC;
}
result = DoubleValue(value);
}
icregs.icVals[resultId.id()] = result.asRawBits();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(BooleanToNumber) {
BooleanOperandId booleanId = icregs.cacheIRReader.booleanOperandId();
NumberOperandId resultId = icregs.cacheIRReader.numberOperandId();
BOUNDSCHECK(resultId);
uint64_t boolean = icregs.icVals[booleanId.id()];
MOZ_ASSERT((boolean & ~1) == 0);
icregs.icVals[resultId.id()] = Int32Value(boolean).asRawBits();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardHasGetterSetter) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t idOffset = icregs.cacheIRReader.stubOffset();
uint32_t getterSetterOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
jsid id =
jsid::fromRawBits(cstub->stubInfo()->getStubRawWord(cstub, idOffset));
GetterSetter* getterSetter = reinterpret_cast<GetterSetter*>(
cstub->stubInfo()->getStubRawWord(cstub, getterSetterOffset));
if (!ObjectHasGetterSetterPure(frameMgr.cxForLocalUseOnly(), obj, id,
getterSetter)) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardInt32IsNonNegative) {
Int32OperandId indexId = icregs.cacheIRReader.int32OperandId();
int32_t index = int32_t(icregs.icVals[indexId.id()]);
if (index < 0) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardDynamicSlotIsSpecificObject) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
ObjOperandId expectedId = icregs.cacheIRReader.objOperandId();
uint32_t slotOffset = icregs.cacheIRReader.stubOffset();
JSObject* expected =
reinterpret_cast<JSObject*>(icregs.icVals[expectedId.id()]);
uintptr_t slot = cstub->stubInfo()->getStubRawInt32(cstub, slotOffset);
NativeObject* nobj =
reinterpret_cast<NativeObject*>(icregs.icVals[objId.id()]);
HeapSlot* slots = nobj->getSlotsUnchecked();
// Note that unlike similar opcodes, GuardDynamicSlotIsSpecificObject takes
// a slot index rather than a byte offset.
Value actual = slots[slot];
if (actual != ObjectValue(*expected)) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardDynamicSlotIsNotObject) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t slotOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
uint32_t slot = cstub->stubInfo()->getStubRawInt32(cstub, slotOffset);
NativeObject* nobj = &obj->as<NativeObject>();
HeapSlot* slots = nobj->getSlotsUnchecked();
// Note that unlike similar opcodes, GuardDynamicSlotIsNotObject takes a
// slot index rather than a byte offset.
Value actual = slots[slot];
if (actual.isObject()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardFixedSlotValue) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t offsetOffset = icregs.cacheIRReader.stubOffset();
uint32_t valOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
uint32_t offset = cstub->stubInfo()->getStubRawInt32(cstub, offsetOffset);
Value val = Value::fromRawBits(
cstub->stubInfo()->getStubRawInt64(cstub, valOffset));
GCPtr<Value>* slot = reinterpret_cast<GCPtr<Value>*>(
reinterpret_cast<uintptr_t>(obj) + offset);
Value actual = slot->get();
if (actual != val) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardDynamicSlotValue) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t offsetOffset = icregs.cacheIRReader.stubOffset();
uint32_t valOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
uint32_t offset = cstub->stubInfo()->getStubRawInt32(cstub, offsetOffset);
Value val = Value::fromRawBits(
cstub->stubInfo()->getStubRawInt64(cstub, valOffset));
NativeObject* nobj = &obj->as<NativeObject>();
HeapSlot* slots = nobj->getSlotsUnchecked();
Value actual = slots[offset / sizeof(Value)];
if (actual != val) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadFixedSlot) {
ValOperandId resultId = icregs.cacheIRReader.valOperandId();
BOUNDSCHECK(resultId);
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t offsetOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
uint32_t offset = cstub->stubInfo()->getStubRawInt32(cstub, offsetOffset);
GCPtr<Value>* slot = reinterpret_cast<GCPtr<Value>*>(
reinterpret_cast<uintptr_t>(obj) + offset);
Value actual = slot->get();
icregs.icVals[resultId.id()] = actual.asRawBits();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadDynamicSlot) {
ValOperandId resultId = icregs.cacheIRReader.valOperandId();
BOUNDSCHECK(resultId);
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t slotOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
uint32_t slot = cstub->stubInfo()->getStubRawInt32(cstub, slotOffset);
NativeObject* nobj = &obj->as<NativeObject>();
HeapSlot* slots = nobj->getSlotsUnchecked();
// Note that unlike similar opcodes, LoadDynamicSlot takes a slot index
// rather than a byte offset.
Value actual = slots[slot];
icregs.icVals[resultId.id()] = actual.asRawBits();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardNoAllocationMetadataBuilder) {
uint32_t builderAddrOffset = icregs.cacheIRReader.stubOffset();
uintptr_t builderAddr =
cstub->stubInfo()->getStubRawWord(cstub, builderAddrOffset);
if (*reinterpret_cast<uintptr_t*>(builderAddr) != 0) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardFunctionHasJitEntry) {
ObjOperandId funId = icregs.cacheIRReader.objOperandId();
bool constructing = icregs.cacheIRReader.readBool();
JSObject* fun = reinterpret_cast<JSObject*>(icregs.icVals[funId.id()]);
uint16_t flags = FunctionFlags::HasJitEntryFlags(constructing);
if (!fun->as<JSFunction>().flags().hasFlags(flags)) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardFunctionHasNoJitEntry) {
ObjOperandId funId = icregs.cacheIRReader.objOperandId();
JSObject* fun = reinterpret_cast<JSObject*>(icregs.icVals[funId.id()]);
uint16_t flags = FunctionFlags::HasJitEntryFlags(/*constructing =*/false);
if (fun->as<JSFunction>().flags().hasFlags(flags)) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardFunctionIsNonBuiltinCtor) {
ObjOperandId funId = icregs.cacheIRReader.objOperandId();
JSObject* fun = reinterpret_cast<JSObject*>(icregs.icVals[funId.id()]);
if (!fun->as<JSFunction>().isNonBuiltinConstructor()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardFunctionIsConstructor) {
ObjOperandId funId = icregs.cacheIRReader.objOperandId();
JSObject* fun = reinterpret_cast<JSObject*>(icregs.icVals[funId.id()]);
if (!fun->as<JSFunction>().isConstructor()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardNotClassConstructor) {
ObjOperandId funId = icregs.cacheIRReader.objOperandId();
JSObject* fun = reinterpret_cast<JSObject*>(icregs.icVals[funId.id()]);
if (fun->as<JSFunction>().isClassConstructor()) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardArrayIsPacked) {
ObjOperandId arrayId = icregs.cacheIRReader.objOperandId();
JSObject* array = reinterpret_cast<JSObject*>(icregs.icVals[arrayId.id()]);
if (!IsPackedArray(array)) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(GuardArgumentsObjectFlags) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint8_t flags = icregs.cacheIRReader.readByte();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
if (obj->as<ArgumentsObject>().hasFlags(flags)) {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadObject) {
ObjOperandId resultId = icregs.cacheIRReader.objOperandId();
BOUNDSCHECK(resultId);
uint32_t objOffset = icregs.cacheIRReader.stubOffset();
intptr_t obj = cstub->stubInfo()->getStubRawWord(cstub, objOffset);
icregs.icVals[resultId.id()] = obj;
PREDICT_NEXT(GuardShape);
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadProtoObject) {
ObjOperandId resultId = icregs.cacheIRReader.objOperandId();
BOUNDSCHECK(resultId);
uint32_t protoObjOffset = icregs.cacheIRReader.stubOffset();
ObjOperandId receiverObjId = icregs.cacheIRReader.objOperandId();
(void)receiverObjId;
intptr_t obj = cstub->stubInfo()->getStubRawWord(cstub, protoObjOffset);
icregs.icVals[resultId.id()] = obj;
PREDICT_NEXT(GuardShape);
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadProto) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
ObjOperandId resultId = icregs.cacheIRReader.objOperandId();
BOUNDSCHECK(resultId);
NativeObject* nobj =
reinterpret_cast<NativeObject*>(icregs.icVals[objId.id()]);
icregs.icVals[resultId.id()] =
reinterpret_cast<uintptr_t>(nobj->staticPrototype());
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadEnclosingEnvironment) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
ObjOperandId resultId = icregs.cacheIRReader.objOperandId();
BOUNDSCHECK(resultId);
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
JSObject* env = &obj->as<EnvironmentObject>().enclosingEnvironment();
icregs.icVals[resultId.id()] = reinterpret_cast<uintptr_t>(env);
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadWrapperTarget) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
ObjOperandId resultId = icregs.cacheIRReader.objOperandId();
BOUNDSCHECK(resultId);
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
JSObject* target = &obj->as<ProxyObject>().private_().toObject();
icregs.icVals[resultId.id()] = reinterpret_cast<uintptr_t>(target);
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadValueTag) {
ValOperandId valId = icregs.cacheIRReader.valOperandId();
ValueTagOperandId resultId = icregs.cacheIRReader.valueTagOperandId();
BOUNDSCHECK(resultId);
Value val = Value::fromRawBits(icregs.icVals[valId.id()]);
icregs.icVals[resultId.id()] = val.extractNonDoubleType();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadArgumentFixedSlot) {
ValOperandId resultId = icregs.cacheIRReader.valOperandId();
BOUNDSCHECK(resultId);
uint8_t slotIndex = icregs.cacheIRReader.readByte();
Value val = sp[slotIndex].asValue();
TRACE_PRINTF(" -> slot %d: val %" PRIx64 "\n", int(slotIndex),
val.asRawBits());
icregs.icVals[resultId.id()] = val.asRawBits();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadArgumentDynamicSlot) {
ValOperandId resultId = icregs.cacheIRReader.valOperandId();
BOUNDSCHECK(resultId);
Int32OperandId argcId = icregs.cacheIRReader.int32OperandId();
uint8_t slotIndex = icregs.cacheIRReader.readByte();
int32_t argc = int32_t(icregs.icVals[argcId.id()]);
Value val = sp[slotIndex + argc].asValue();
icregs.icVals[resultId.id()] = val.asRawBits();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(TruncateDoubleToUInt32) {
NumberOperandId inputId = icregs.cacheIRReader.numberOperandId();
Int32OperandId resultId = icregs.cacheIRReader.int32OperandId();
BOUNDSCHECK(resultId);
Value input = Value::fromRawBits(icregs.icVals[inputId.id()]);
icregs.icVals[resultId.id()] = JS::ToInt32(input.toNumber());
DISPATCH_CACHEOP();
}
CACHEOP_CASE(MegamorphicLoadSlotResult) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t nameOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
jsid name =
jsid::fromRawBits(cstub->stubInfo()->getStubRawWord(cstub, nameOffset));
if (!obj->shape()->isNative()) {
return ICInterpretOpResult::NextIC;
}
Value result;
if (!GetNativeDataPropertyPureWithCacheLookup(
frameMgr.cxForLocalUseOnly(), obj, name, nullptr, &result)) {
return ICInterpretOpResult::NextIC;
}
icregs.icResult = result.asRawBits();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(MegamorphicLoadSlotByValueResult) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
ValOperandId idId = icregs.cacheIRReader.valOperandId();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
Value id = Value::fromRawBits(icregs.icVals[idId.id()]);
if (!obj->shape()->isNative()) {
return ICInterpretOpResult::NextIC;
}
Value values[2] = {id};
if (!GetNativeDataPropertyByValuePure(frameMgr.cxForLocalUseOnly(), obj,
nullptr, values)) {
return ICInterpretOpResult::NextIC;
}
icregs.icResult = values[1].asRawBits();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(MegamorphicSetElement) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
ValOperandId idId = icregs.cacheIRReader.valOperandId();
ValOperandId rhsId = icregs.cacheIRReader.valOperandId();
bool strict = icregs.cacheIRReader.readBool();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
Value id = Value::fromRawBits(icregs.icVals[idId.id()]);
Value rhs = Value::fromRawBits(icregs.icVals[rhsId.id()]);
{
PUSH_IC_FRAME();
ReservedRooted<JSObject*> obj0(&state.obj0, obj);
ReservedRooted<Value> value0(&state.value0, id);
ReservedRooted<Value> value1(&state.value1, rhs);
if (!SetElementMegamorphic<false>(cx, obj0, value0, value1, strict)) {
return ICInterpretOpResult::Error;
}
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(StoreFixedSlot) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t offsetOffset = icregs.cacheIRReader.stubOffset();
ValOperandId rhsId = icregs.cacheIRReader.valOperandId();
uintptr_t offset = cstub->stubInfo()->getStubRawInt32(cstub, offsetOffset);
NativeObject* nobj =
reinterpret_cast<NativeObject*>(icregs.icVals[objId.id()]);
GCPtr<Value>* slot = reinterpret_cast<GCPtr<Value>*>(
reinterpret_cast<uintptr_t>(nobj) + offset);
Value val = Value::fromRawBits(icregs.icVals[rhsId.id()]);
slot->set(val);
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(StoreDynamicSlot) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t offsetOffset = icregs.cacheIRReader.stubOffset();
ValOperandId rhsId = icregs.cacheIRReader.valOperandId();
uint32_t offset = cstub->stubInfo()->getStubRawInt32(cstub, offsetOffset);
NativeObject* nobj =
reinterpret_cast<NativeObject*>(icregs.icVals[objId.id()]);
HeapSlot* slots = nobj->getSlotsUnchecked();
Value val = Value::fromRawBits(icregs.icVals[rhsId.id()]);
size_t dynSlot = offset / sizeof(Value);
size_t slot = dynSlot + nobj->numFixedSlots();
slots[dynSlot].set(nobj, HeapSlot::Slot, slot, val);
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(AddAndStoreFixedSlot) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t offsetOffset = icregs.cacheIRReader.stubOffset();
ValOperandId rhsId = icregs.cacheIRReader.valOperandId();
uint32_t newShapeOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
int32_t offset = cstub->stubInfo()->getStubRawInt32(cstub, offsetOffset);
Value rhs = Value::fromRawBits(icregs.icVals[rhsId.id()]);
Shape* newShape = reinterpret_cast<Shape*>(
cstub->stubInfo()->getStubRawWord(cstub, newShapeOffset));
obj->setShape(newShape);
GCPtr<Value>* slot = reinterpret_cast<GCPtr<Value>*>(
reinterpret_cast<uintptr_t>(obj) + offset);
slot->init(rhs);
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(AddAndStoreDynamicSlot) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t offsetOffset = icregs.cacheIRReader.stubOffset();
ValOperandId rhsId = icregs.cacheIRReader.valOperandId();
uint32_t newShapeOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
int32_t offset = cstub->stubInfo()->getStubRawInt32(cstub, offsetOffset);
Value rhs = Value::fromRawBits(icregs.icVals[rhsId.id()]);
Shape* newShape = reinterpret_cast<Shape*>(
cstub->stubInfo()->getStubRawWord(cstub, newShapeOffset));
NativeObject* nobj = &obj->as<NativeObject>();
obj->setShape(newShape);
HeapSlot* slots = nobj->getSlotsUnchecked();
size_t dynSlot = offset / sizeof(Value);
size_t slot = dynSlot + nobj->numFixedSlots();
slots[dynSlot].init(nobj, HeapSlot::Slot, slot, rhs);
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(AllocateAndStoreDynamicSlot) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t offsetOffset = icregs.cacheIRReader.stubOffset();
ValOperandId rhsId = icregs.cacheIRReader.valOperandId();
uint32_t newShapeOffset = icregs.cacheIRReader.stubOffset();
uint32_t numNewSlotsOffset = icregs.cacheIRReader.stubOffset();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
int32_t offset = cstub->stubInfo()->getStubRawInt32(cstub, offsetOffset);
Value rhs = Value::fromRawBits(icregs.icVals[rhsId.id()]);
Shape* newShape = reinterpret_cast<Shape*>(
cstub->stubInfo()->getStubRawWord(cstub, newShapeOffset));
int32_t numNewSlots =
cstub->stubInfo()->getStubRawInt32(cstub, numNewSlotsOffset);
NativeObject* nobj = &obj->as<NativeObject>();
// We have to (re)allocate dynamic slots. Do this first, as it's the
// only fallible operation here. Note that growSlotsPure is fallible but
// does not GC. Otherwise this is the same as AddAndStoreDynamicSlot above.
if (!NativeObject::growSlotsPure(frameMgr.cxForLocalUseOnly(), nobj,
numNewSlots)) {
return ICInterpretOpResult::NextIC;
}
obj->setShape(newShape);
HeapSlot* slots = nobj->getSlotsUnchecked();
size_t dynSlot = offset / sizeof(Value);
size_t slot = dynSlot + nobj->numFixedSlots();
slots[dynSlot].init(nobj, HeapSlot::Slot, slot, rhs);
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(StoreDenseElement) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
Int32OperandId indexId = icregs.cacheIRReader.int32OperandId();
ValOperandId rhsId = icregs.cacheIRReader.valOperandId();
NativeObject* nobj =
reinterpret_cast<NativeObject*>(icregs.icVals[objId.id()]);
ObjectElements* elems = nobj->getElementsHeader();
int32_t index = int32_t(icregs.icVals[indexId.id()]);
if (index < 0 || uint32_t(index) >= nobj->getDenseInitializedLength()) {
return ICInterpretOpResult::NextIC;
}
HeapSlot* slot = &elems->elements()[index];
if (slot->get().isMagic()) {
return ICInterpretOpResult::NextIC;
}
Value val = Value::fromRawBits(icregs.icVals[rhsId.id()]);
slot->set(nobj, HeapSlot::Element, index + elems->numShiftedElements(),
val);
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(StoreDenseElementHole) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
Int32OperandId indexId = icregs.cacheIRReader.int32OperandId();
ValOperandId rhsId = icregs.cacheIRReader.valOperandId();
bool handleAdd = icregs.cacheIRReader.readBool();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
uint32_t index = uint32_t(icregs.icVals[indexId.id()]);
Value rhs = Value::fromRawBits(icregs.icVals[rhsId.id()]);
NativeObject* nobj = &obj->as<NativeObject>();
uint32_t initLength = nobj->getDenseInitializedLength();
if (index < initLength) {
nobj->setDenseElement(index, rhs);
} else if (!handleAdd || index > initLength) {
return ICInterpretOpResult::NextIC;
} else {
if (index >= nobj->getDenseCapacity()) {
if (!NativeObject::addDenseElementPure(frameMgr.cxForLocalUseOnly(),
nobj)) {
return ICInterpretOpResult::NextIC;
}
}
nobj->setDenseInitializedLength(initLength + 1);
// Baseline always updates the length field by directly accessing its
// offset in ObjectElements. If the object is not an ArrayObject then this
// field is never read, so it's okay to skip the update here in that case.
if (nobj->is<ArrayObject>()) {
ArrayObject* aobj = &nobj->as<ArrayObject>();
uint32_t len = aobj->length();
if (len <= index) {
aobj->setLength(len + 1);
}
}
nobj->initDenseElement(index, rhs);
}
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(ArrayPush) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
ValOperandId rhsId = icregs.cacheIRReader.valOperandId();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
Value rhs = Value::fromRawBits(icregs.icVals[rhsId.id()]);
ArrayObject* aobj = &obj->as<ArrayObject>();
uint32_t initLength = aobj->getDenseInitializedLength();
if (aobj->length() != initLength) {
return ICInterpretOpResult::NextIC;
}
if (initLength >= aobj->getDenseCapacity()) {
if (!NativeObject::addDenseElementPure(frameMgr.cxForLocalUseOnly(),
aobj)) {
return ICInterpretOpResult::NextIC;
}
}
aobj->setDenseInitializedLength(initLength + 1);
aobj->setLength(initLength + 1);
aobj->initDenseElement(initLength, rhs);
icregs.icResult = Int32Value(initLength + 1).asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(IsObjectResult) {
ValOperandId inputId = icregs.cacheIRReader.valOperandId();
Value val = Value::fromRawBits(icregs.icVals[inputId.id()]);
icregs.icResult = BooleanValue(val.isObject()).asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(Int32MinMax) {
bool isMax = icregs.cacheIRReader.readBool();
Int32OperandId firstId = icregs.cacheIRReader.int32OperandId();
Int32OperandId secondId = icregs.cacheIRReader.int32OperandId();
Int32OperandId resultId = icregs.cacheIRReader.int32OperandId();
BOUNDSCHECK(resultId);
int32_t lhs = int32_t(icregs.icVals[firstId.id()]);
int32_t rhs = int32_t(icregs.icVals[secondId.id()]);
int32_t result = ((lhs > rhs) ^ isMax) ? rhs : lhs;
icregs.icVals[resultId.id()] = result;
DISPATCH_CACHEOP();
}
CACHEOP_CASE(StoreTypedArrayElement) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
Scalar::Type elementType = icregs.cacheIRReader.scalarType();
IntPtrOperandId indexId = icregs.cacheIRReader.intPtrOperandId();
uint32_t rhsId = icregs.cacheIRReader.rawOperandId();
bool handleOOB = icregs.cacheIRReader.readBool();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
uintptr_t index = uintptr_t(icregs.icVals[indexId.id()]);
uint64_t rhs = icregs.icVals[rhsId];
if (obj->as<TypedArrayObject>().length().isNothing()) {
return ICInterpretOpResult::NextIC;
}
if (index >= obj->as<TypedArrayObject>().length().value()) {
if (!handleOOB) {
return ICInterpretOpResult::NextIC;
}
} else {
Value v;
switch (elementType) {
case Scalar::Int8:
case Scalar::Uint8:
case Scalar::Int16:
case Scalar::Uint16:
case Scalar::Int32:
case Scalar::Uint32:
case Scalar::Uint8Clamped:
v = Int32Value(rhs);
break;
case Scalar::Float32:
case Scalar::Float64:
v = Value::fromRawBits(rhs);
MOZ_ASSERT(v.isNumber());
break;
case Scalar::BigInt64:
case Scalar::BigUint64:
v = BigIntValue(reinterpret_cast<JS::BigInt*>(rhs));
break;
case Scalar::MaxTypedArrayViewType:
case Scalar::Int64:
case Scalar::Simd128:
MOZ_CRASH("Unsupported TypedArray type");
}
// SetTypedArrayElement doesn't do anything that can actually GC or need a
// new context when the value can only be Int32, Double, or BigInt, as the
// above switch statement enforces.
FakeRooted<TypedArrayObject*> obj0(nullptr, &obj->as<TypedArrayObject>());
FakeRooted<Value> value0(nullptr, v);
ObjectOpResult result;
MOZ_ASSERT(elementType == obj0->type());
MOZ_ALWAYS_TRUE(SetTypedArrayElement(frameMgr.cxForLocalUseOnly(), obj0,
index, value0, result));
MOZ_ALWAYS_TRUE(result.ok());
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(CallInt32ToString) {
Int32OperandId inputId = icregs.cacheIRReader.int32OperandId();
StringOperandId resultId = icregs.cacheIRReader.stringOperandId();
BOUNDSCHECK(resultId);
int32_t input = int32_t(icregs.icVals[inputId.id()]);
JSLinearString* str =
Int32ToStringPure(frameMgr.cxForLocalUseOnly(), input);
if (str) {
icregs.icVals[resultId.id()] = reinterpret_cast<uintptr_t>(str);
} else {
return ICInterpretOpResult::NextIC;
}
DISPATCH_CACHEOP();
}
CACHEOP_CASE(CallScriptedFunction)
CACHEOP_CASE_FALLTHROUGH(CallNativeFunction) {
bool isNative = cacheop == CacheOp::CallNativeFunction;
TRACE_PRINTF("CallScriptedFunction / CallNativeFunction (native: %d)\n",
isNative);
ObjOperandId calleeId = icregs.cacheIRReader.objOperandId();
Int32OperandId argcId = icregs.cacheIRReader.int32OperandId();
CallFlags flags = icregs.cacheIRReader.callFlags();
uint32_t argcFixed = icregs.cacheIRReader.uint32Immediate();
bool ignoresRv = false;
if (isNative) {
ignoresRv = icregs.cacheIRReader.readBool();
}
JSFunction* callee =
reinterpret_cast<JSFunction*>(icregs.icVals[calleeId.id()]);
uint32_t argc = uint32_t(icregs.icVals[argcId.id()]);
(void)argcFixed;
if (!isNative) {
if (!callee->hasBaseScript() || !callee->baseScript()->hasBytecode() ||
!callee->baseScript()->hasJitScript()) {
return ICInterpretOpResult::NextIC;
}
}
// For now, fail any constructing or different-realm cases.
if (flags.isConstructing()) {
TRACE_PRINTF("failing: constructing\n");
return ICInterpretOpResult::NextIC;
}
if (!flags.isSameRealm()) {
TRACE_PRINTF("failing: not same realm\n");
return ICInterpretOpResult::NextIC;
}
// And support only "standard" arg formats.
if (flags.getArgFormat() != CallFlags::Standard) {
TRACE_PRINTF("failing: not standard arg format\n");
return ICInterpretOpResult::NextIC;
}
// For now, fail any arg-underflow case.
if (argc < callee->nargs()) {
TRACE_PRINTF("failing: too few args\n");
return ICInterpretOpResult::NextIC;
}
uint32_t extra = 1 + flags.isConstructing() + isNative;
uint32_t totalArgs = argc + extra;
StackVal* origArgs = sp;
{
PUSH_IC_FRAME();
if (!stack.check(sp, sizeof(StackVal) * (totalArgs + 6))) {
ReportOverRecursed(frameMgr.cxForLocalUseOnly());
return ICInterpretOpResult::Error;
}
// This will not be an Exit frame but a BaselineStub frame, so
// replace the ExitFrameType with the ICStub pointer.
POPNNATIVE(1);
PUSHNATIVE(StackValNative(cstub));
// Push args.
for (uint32_t i = 0; i < totalArgs; i++) {
PUSH(origArgs[i]);
}
Value* args = reinterpret_cast<Value*>(sp);
TRACE_PRINTF("pushing callee: %p\n", callee);
PUSHNATIVE(
StackValNative(CalleeToToken(callee, /* isConstructing = */ false)));
if (isNative) {
PUSHNATIVE(StackValNative(argc));
PUSHNATIVE(StackValNative(
MakeFrameDescriptorForJitCall(FrameType::BaselineStub, 0)));
// We *also* need an exit frame (the native baseline
// execution would invoke a trampoline here).
StackVal* trampolinePrevFP = stack.fp;
PUSHNATIVE(StackValNative(nullptr)); // fake return address.
PUSHNATIVE(StackValNative(stack.fp));
stack.fp = sp;
PUSHNATIVE(StackValNative(uint32_t(ExitFrameType::CallNative)));
cx.getCx()->activation()->asJit()->setJSExitFP(
reinterpret_cast<uint8_t*>(stack.fp));
cx.getCx()->portableBaselineStack().top = reinterpret_cast<void*>(sp);
JSNative native = ignoresRv
? callee->jitInfo()->ignoresReturnValueMethod
: callee->native();
bool success = native(cx, argc, args);
stack.fp = trampolinePrevFP;
POPNNATIVE(4);
if (!success) {
return ICInterpretOpResult::Error;
}
icregs.icResult = args[0].asRawBits();
} else {
PUSHNATIVE(StackValNative(
MakeFrameDescriptorForJitCall(FrameType::BaselineStub, argc)));
switch (PortableBaselineInterpret(
cx, state, stack, sp, /* envChain = */ nullptr,
reinterpret_cast<Value*>(&icregs.icResult))) {
case PBIResult::Ok:
break;
case PBIResult::Error:
return ICInterpretOpResult::Error;
case PBIResult::Unwind:
return ICInterpretOpResult::Unwind;
case PBIResult::UnwindError:
return ICInterpretOpResult::UnwindError;
case PBIResult::UnwindRet:
return ICInterpretOpResult::UnwindRet;
}
}
}
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(MetaScriptedThisShape) {
uint32_t thisShapeOffset = icregs.cacheIRReader.stubOffset();
// This op is only metadata for the Warp Transpiler and should be ignored.
(void)thisShapeOffset;
PREDICT_NEXT(CallScriptedFunction);
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadFixedSlotResult) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t offsetOffset = icregs.cacheIRReader.stubOffset();
uintptr_t offset = cstub->stubInfo()->getStubRawInt32(cstub, offsetOffset);
NativeObject* nobj =
reinterpret_cast<NativeObject*>(icregs.icVals[objId.id()]);
Value* slot =
reinterpret_cast<Value*>(reinterpret_cast<uintptr_t>(nobj) + offset);
TRACE_PRINTF(
"LoadFixedSlotResult: obj %p offsetOffset %d offset %d slotPtr %p "
"slot %" PRIx64 "\n",
nobj, int(offsetOffset), int(offset), slot, slot->asRawBits());
icregs.icResult = slot->asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadDynamicSlotResult) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
uint32_t offsetOffset = icregs.cacheIRReader.stubOffset();
uintptr_t offset = cstub->stubInfo()->getStubRawInt32(cstub, offsetOffset);
NativeObject* nobj =
reinterpret_cast<NativeObject*>(icregs.icVals[objId.id()]);
HeapSlot* slots = nobj->getSlotsUnchecked();
icregs.icResult = slots[offset / sizeof(Value)].get().asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadDenseElementResult) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
Int32OperandId indexId = icregs.cacheIRReader.int32OperandId();
NativeObject* nobj =
reinterpret_cast<NativeObject*>(icregs.icVals[objId.id()]);
ObjectElements* elems = nobj->getElementsHeader();
int32_t index = int32_t(icregs.icVals[indexId.id()]);
if (index < 0 || uint32_t(index) >= nobj->getDenseInitializedLength()) {
return ICInterpretOpResult::NextIC;
}
HeapSlot* slot = &elems->elements()[index];
Value val = slot->get();
if (val.isMagic()) {
return ICInterpretOpResult::NextIC;
}
icregs.icResult = val.asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadInt32ArrayLengthResult) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
ArrayObject* aobj =
reinterpret_cast<ArrayObject*>(icregs.icVals[objId.id()]);
uint32_t length = aobj->length();
if (length > uint32_t(INT32_MAX)) {
return ICInterpretOpResult::NextIC;
}
icregs.icResult = Int32Value(length).asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadInt32ArrayLength) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
Int32OperandId resultId = icregs.cacheIRReader.int32OperandId();
BOUNDSCHECK(resultId);
ArrayObject* aobj =
reinterpret_cast<ArrayObject*>(icregs.icVals[objId.id()]);
uint32_t length = aobj->length();
if (length > uint32_t(INT32_MAX)) {
return ICInterpretOpResult::NextIC;
}
icregs.icVals[resultId.id()] = length;
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadArgumentsObjectArgResult) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
Int32OperandId indexId = icregs.cacheIRReader.int32OperandId();
JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]);
uint32_t index = uint32_t(icregs.icVals[indexId.id()]);
ArgumentsObject* args = &obj->as<ArgumentsObject>();
if (index >= args->initialLength() || args->hasOverriddenElement()) {
return ICInterpretOpResult::NextIC;
}
if (args->argIsForwarded(index)) {
return ICInterpretOpResult::NextIC;
}
icregs.icResult = args->arg(index).asRawBits();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LinearizeForCharAccess) {
StringOperandId strId = icregs.cacheIRReader.stringOperandId();
Int32OperandId indexId = icregs.cacheIRReader.int32OperandId();
StringOperandId resultId = icregs.cacheIRReader.stringOperandId();
BOUNDSCHECK(resultId);
JSString* str =
reinterpret_cast<JSLinearString*>(icregs.icVals[strId.id()]);
(void)indexId;
if (!str->isRope()) {
icregs.icVals[resultId.id()] = reinterpret_cast<uintptr_t>(str);
} else {
PUSH_IC_FRAME();
JSLinearString* result = LinearizeForCharAccess(cx, str);
if (!result) {
return ICInterpretOpResult::Error;
}
icregs.icVals[resultId.id()] = reinterpret_cast<uintptr_t>(result);
}
PREDICT_NEXT(LoadStringCharResult);
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadStringCharResult) {
StringOperandId strId = icregs.cacheIRReader.stringOperandId();
Int32OperandId indexId = icregs.cacheIRReader.int32OperandId();
bool handleOOB = icregs.cacheIRReader.readBool();
JSString* str =
reinterpret_cast<JSLinearString*>(icregs.icVals[strId.id()]);
int32_t index = int32_t(icregs.icVals[indexId.id()]);
JSString* result = nullptr;
if (index < 0 || size_t(index) >= str->length()) {
if (handleOOB) {
// Return an empty string.
result = frameMgr.cxForLocalUseOnly()->names().empty_;
} else {
return ICInterpretOpResult::NextIC;
}
} else {
char16_t c;
// Guaranteed to be always work because this CacheIR op is
// always preceded by LinearizeForCharAccess.
MOZ_ALWAYS_TRUE(str->getChar(/* cx = */ nullptr, index, &c));
StaticStrings& sstr = frameMgr.cxForLocalUseOnly()->staticStrings();
if (sstr.hasUnit(c)) {
result = sstr.getUnit(c);
} else {
PUSH_IC_FRAME();
result = StringFromCharCode(cx, c);
if (!result) {
return ICInterpretOpResult::Error;
}
}
}
icregs.icResult = StringValue(result).asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadStringCharCodeResult) {
StringOperandId strId = icregs.cacheIRReader.stringOperandId();
Int32OperandId indexId = icregs.cacheIRReader.int32OperandId();
bool handleOOB = icregs.cacheIRReader.readBool();
JSString* str =
reinterpret_cast<JSLinearString*>(icregs.icVals[strId.id()]);
int32_t index = int32_t(icregs.icVals[indexId.id()]);
Value result;
if (index < 0 || size_t(index) >= str->length()) {
if (handleOOB) {
// Return NaN.
result = JS::NaNValue();
} else {
return ICInterpretOpResult::NextIC;
}
} else {
char16_t c;
// Guaranteed to be always work because this CacheIR op is
// always preceded by LinearizeForCharAccess.
MOZ_ALWAYS_TRUE(str->getChar(/* cx = */ nullptr, index, &c));
result = Int32Value(c);
}
icregs.icResult = result.asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadStringLengthResult) {
StringOperandId strId = icregs.cacheIRReader.stringOperandId();
JSString* str = reinterpret_cast<JSString*>(icregs.icVals[strId.id()]);
size_t length = str->length();
if (length > size_t(INT32_MAX)) {
return ICInterpretOpResult::NextIC;
}
icregs.icResult = Int32Value(length).asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadObjectResult) {
ObjOperandId objId = icregs.cacheIRReader.objOperandId();
icregs.icResult =
ObjectValue(*reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]))
.asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadStringResult) {
StringOperandId strId = icregs.cacheIRReader.stringOperandId();
icregs.icResult =
StringValue(reinterpret_cast<JSString*>(icregs.icVals[strId.id()]))
.asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadSymbolResult) {
SymbolOperandId symId = icregs.cacheIRReader.symbolOperandId();
icregs.icResult =
SymbolValue(reinterpret_cast<JS::Symbol*>(icregs.icVals[symId.id()]))
.asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadInt32Result) {
Int32OperandId valId = icregs.cacheIRReader.int32OperandId();
icregs.icResult = Int32Value(icregs.icVals[valId.id()]).asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadDoubleResult) {
NumberOperandId valId = icregs.cacheIRReader.numberOperandId();
Value val = Value::fromRawBits(icregs.icVals[valId.id()]);
if (val.isInt32()) {
val = DoubleValue(val.toInt32());
}
icregs.icResult = val.asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadBigIntResult) {
BigIntOperandId valId = icregs.cacheIRReader.bigIntOperandId();
icregs.icResult =
BigIntValue(reinterpret_cast<JS::BigInt*>(icregs.icVals[valId.id()]))
.asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadBooleanResult) {
bool val = icregs.cacheIRReader.readBool();
icregs.icResult = BooleanValue(val).asRawBits();
PREDICT_RETURN();
DISPATCH_CACHEOP();
}
CACHEOP_CASE(LoadInt32Constant) {
uint32_t valOffset = icregs.cacheIRReader.stubOffset();
Int32OperandId resultId = icregs.cacheIRReader.int32OperandId();
BOUNDSCHECK(resultId);
uint32_t value = cstub->stubInfo()->getStubRawInt32(cstub, valOffset);
<