diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/src/vm/PortableBaselineInterpret.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/vm/PortableBaselineInterpret.cpp')
-rw-r--r-- | js/src/vm/PortableBaselineInterpret.cpp | 5538 |
1 files changed, 5538 insertions, 0 deletions
diff --git a/js/src/vm/PortableBaselineInterpret.cpp b/js/src/vm/PortableBaselineInterpret.cpp new file mode 100644 index 0000000000..e2acaf2d7b --- /dev/null +++ b/js/src/vm/PortableBaselineInterpret.cpp @@ -0,0 +1,5538 @@ +/* -*- 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 "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; \ + } + + 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(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(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(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()]); + uintptr_t expectedProto = + cstub->stubInfo()->getStubRawWord(cstub, protoOffset); + if (reinterpret_cast<uintptr_t>(obj->staticPrototype()) != expectedProto) { + return ICInterpretOpResult::NextIC; + } + PREDICT_NEXT(LoadProto); + 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(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(GuardSpecificObject) { + ObjOperandId objId = icregs.cacheIRReader.objOperandId(); + uint32_t expectedOffset = icregs.cacheIRReader.stubOffset(); + uintptr_t expected = + cstub->stubInfo()->getStubRawWord(cstub, expectedOffset); + if (expected != icregs.icVals[objId.id()]) { + 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()]) { + 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(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 offset = cstub->stubInfo()->getStubRawInt32(cstub, slotOffset); + NativeObject* nobj = + reinterpret_cast<NativeObject*>(icregs.icVals[objId.id()]); + HeapSlot* slots = nobj->getSlotsUnchecked(); + Value actual = slots[offset / sizeof(Value)]; + if (actual != ObjectValue(*expected)) { + return ICInterpretOpResult::NextIC; + } + 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(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(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(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_NEXT(ReturnFromIC); + 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_NEXT(ReturnFromIC); + 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_NEXT(ReturnFromIC); + 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_NEXT(ReturnFromIC); + 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(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() || !flags.isSameRealm()) { + TRACE_PRINTF("failing: constructing or 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; + } + } + } + + 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_NEXT(ReturnFromIC); + 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_NEXT(ReturnFromIC); + 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_NEXT(ReturnFromIC); + 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_NEXT(ReturnFromIC); + 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(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_NEXT(ReturnFromIC); + 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_NEXT(ReturnFromIC); + 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_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(LoadObjectResult) { + ObjOperandId objId = icregs.cacheIRReader.objOperandId(); + icregs.icResult = + ObjectValue(*reinterpret_cast<JSObject*>(icregs.icVals[objId.id()])) + .asRawBits(); + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(LoadStringResult) { + StringOperandId strId = icregs.cacheIRReader.stringOperandId(); + icregs.icResult = + StringValue(reinterpret_cast<JSString*>(icregs.icVals[strId.id()])) + .asRawBits(); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(LoadSymbolResult) { + SymbolOperandId symId = icregs.cacheIRReader.symbolOperandId(); + icregs.icResult = + SymbolValue(reinterpret_cast<JS::Symbol*>(icregs.icVals[symId.id()])) + .asRawBits(); + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(LoadInt32Result) { + Int32OperandId valId = icregs.cacheIRReader.int32OperandId(); + icregs.icResult = Int32Value(icregs.icVals[valId.id()]).asRawBits(); + PREDICT_NEXT(ReturnFromIC); + 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_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(LoadBigIntResult) { + BigIntOperandId valId = icregs.cacheIRReader.bigIntOperandId(); + icregs.icResult = + BigIntValue(reinterpret_cast<JS::BigInt*>(icregs.icVals[valId.id()])) + .asRawBits(); + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(LoadBooleanResult) { + bool val = icregs.cacheIRReader.readBool(); + icregs.icResult = BooleanValue(val).asRawBits(); + PREDICT_NEXT(ReturnFromIC); + 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); + icregs.icVals[resultId.id()] = value; + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(LoadConstantStringResult) { + uint32_t strOffset = icregs.cacheIRReader.stubOffset(); + JSString* str = reinterpret_cast<JSString*>( + cstub->stubInfo()->getStubRawWord(cstub, strOffset)); + icregs.icResult = StringValue(str).asRawBits(); + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + +#define INT32_OP(name, op, extra_check) \ + CACHEOP_CASE(Int32##name##Result) { \ + Int32OperandId lhsId = icregs.cacheIRReader.int32OperandId(); \ + Int32OperandId rhsId = icregs.cacheIRReader.int32OperandId(); \ + int64_t lhs = int64_t(int32_t(icregs.icVals[lhsId.id()])); \ + int64_t rhs = int64_t(int32_t(icregs.icVals[rhsId.id()])); \ + extra_check; \ + int64_t result = lhs op rhs; \ + if (result < INT32_MIN || result > INT32_MAX) { \ + return ICInterpretOpResult::NextIC; \ + } \ + icregs.icResult = Int32Value(int32_t(result)).asRawBits(); \ + PREDICT_NEXT(ReturnFromIC); \ + DISPATCH_CACHEOP(); \ + } + + INT32_OP(Add, +, {}); + INT32_OP(Sub, -, {}); + INT32_OP(Mul, *, { + if (rhs * lhs == 0 && ((rhs < 0) ^ (lhs < 0))) { + return ICInterpretOpResult::NextIC; + } + }); + INT32_OP(Div, /, { + if (rhs == 0 || (lhs == INT32_MIN && rhs == -1)) { + return ICInterpretOpResult::NextIC; + } + if (lhs == 0 && rhs < 0) { + return ICInterpretOpResult::NextIC; + } + if (lhs % rhs != 0) { + return ICInterpretOpResult::NextIC; + } + }); + INT32_OP(Mod, %, { + if (rhs == 0 || (lhs == INT32_MIN && rhs == -1)) { + return ICInterpretOpResult::NextIC; + } + if (lhs % rhs == 0 && lhs < 0) { + return ICInterpretOpResult::NextIC; + } + }); + INT32_OP(BitOr, |, {}); + INT32_OP(BitAnd, &, {}); + + CACHEOP_CASE(Int32PowResult) { + Int32OperandId lhsId = icregs.cacheIRReader.int32OperandId(); + Int32OperandId rhsId = icregs.cacheIRReader.int32OperandId(); + int64_t lhs = int64_t(int32_t(icregs.icVals[lhsId.id()])); + int64_t rhs = int64_t(int32_t(icregs.icVals[rhsId.id()])); + int64_t result; + + if (lhs == 1) { + result = 1; + } else if (rhs < 0) { + return ICInterpretOpResult::NextIC; + } else { + result = 1; + int64_t runningSquare = lhs; + while (rhs) { + if (rhs & 1) { + result *= runningSquare; + if (result > int64_t(INT32_MAX)) { + return ICInterpretOpResult::NextIC; + } + } + rhs >>= 1; + if (rhs == 0) { + break; + } + runningSquare *= runningSquare; + if (runningSquare > int64_t(INT32_MAX)) { + return ICInterpretOpResult::NextIC; + } + } + } + + icregs.icResult = Int32Value(int32_t(result)).asRawBits(); + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(Int32IncResult) { + Int32OperandId inputId = icregs.cacheIRReader.int32OperandId(); + int64_t value = int64_t(int32_t(icregs.icVals[inputId.id()])); + value++; + if (value > INT32_MAX) { + return ICInterpretOpResult::NextIC; + } + icregs.icResult = Int32Value(int32_t(value)).asRawBits(); + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(LoadInt32TruthyResult) { + ValOperandId inputId = icregs.cacheIRReader.valOperandId(); + int32_t val = int32_t(icregs.icVals[inputId.id()]); + icregs.icResult = BooleanValue(val != 0).asRawBits(); + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(LoadStringTruthyResult) { + StringOperandId strId = icregs.cacheIRReader.stringOperandId(); + JSString* str = + reinterpret_cast<JSLinearString*>(icregs.icVals[strId.id()]); + icregs.icResult = BooleanValue(str->length() > 0).asRawBits(); + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(LoadObjectTruthyResult) { + ObjOperandId objId = icregs.cacheIRReader.objOperandId(); + JSObject* obj = reinterpret_cast<JSObject*>(icregs.icVals[objId.id()]); + const JSClass* cls = obj->getClass(); + if (cls->isProxyObject()) { + return ICInterpretOpResult::NextIC; + } + icregs.icResult = BooleanValue(!cls->emulatesUndefined()).asRawBits(); + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(LoadValueResult) { + uint32_t valOffset = icregs.cacheIRReader.stubOffset(); + icregs.icResult = cstub->stubInfo()->getStubRawInt64(cstub, valOffset); + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(LoadOperandResult) { + ValOperandId inputId = icregs.cacheIRReader.valOperandId(); + icregs.icResult = icregs.icVals[inputId.id()]; + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(CallStringConcatResult) { + StringOperandId lhsId = icregs.cacheIRReader.stringOperandId(); + StringOperandId rhsId = icregs.cacheIRReader.stringOperandId(); + // We don't push a frame and do a CanGC invocation here; we do a + // pure (NoGC) invocation only, because it's cheaper. + FakeRooted<JSString*> lhs( + nullptr, reinterpret_cast<JSString*>(icregs.icVals[lhsId.id()])); + FakeRooted<JSString*> rhs( + nullptr, reinterpret_cast<JSString*>(icregs.icVals[rhsId.id()])); + JSString* result = + ConcatStrings<NoGC>(frameMgr.cxForLocalUseOnly(), lhs, rhs); + if (result) { + icregs.icResult = StringValue(result).asRawBits(); + } else { + return ICInterpretOpResult::NextIC; + } + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(CompareStringResult) { + JSOp op = icregs.cacheIRReader.jsop(); + StringOperandId lhsId = icregs.cacheIRReader.stringOperandId(); + StringOperandId rhsId = icregs.cacheIRReader.stringOperandId(); + { + PUSH_IC_FRAME(); + ReservedRooted<JSString*> lhs( + &state.str0, reinterpret_cast<JSString*>(icregs.icVals[lhsId.id()])); + ReservedRooted<JSString*> rhs( + &state.str1, reinterpret_cast<JSString*>(icregs.icVals[rhsId.id()])); + bool result; + switch (op) { + case JSOp::Eq: + case JSOp::StrictEq: + if (lhs->length() != rhs->length()) { + result = false; + break; + } + if (!StringsEqual<EqualityKind::Equal>(cx, lhs, rhs, &result)) { + return ICInterpretOpResult::Error; + } + break; + case JSOp::Ne: + case JSOp::StrictNe: + if (lhs->length() != rhs->length()) { + result = true; + break; + } + if (!StringsEqual<EqualityKind::NotEqual>(cx, lhs, rhs, &result)) { + return ICInterpretOpResult::Error; + } + break; + case JSOp::Lt: + if (!StringsCompare<ComparisonKind::LessThan>(cx, lhs, rhs, + &result)) { + return ICInterpretOpResult::Error; + } + break; + case JSOp::Ge: + if (!StringsCompare<ComparisonKind::GreaterThanOrEqual>(cx, lhs, rhs, + &result)) { + return ICInterpretOpResult::Error; + } + break; + case JSOp::Le: + if (!StringsCompare<ComparisonKind::GreaterThanOrEqual>( + cx, /* N.B. swapped order */ rhs, lhs, &result)) { + return ICInterpretOpResult::Error; + } + break; + case JSOp::Gt: + if (!StringsCompare<ComparisonKind::LessThan>( + cx, /* N.B. swapped order */ rhs, lhs, &result)) { + return ICInterpretOpResult::Error; + } + break; + default: + MOZ_CRASH("bad opcode"); + } + icregs.icResult = BooleanValue(result).asRawBits(); + } + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(CompareInt32Result) { + JSOp op = icregs.cacheIRReader.jsop(); + Int32OperandId lhsId = icregs.cacheIRReader.int32OperandId(); + Int32OperandId rhsId = icregs.cacheIRReader.int32OperandId(); + int64_t lhs = int64_t(int32_t(icregs.icVals[lhsId.id()])); + int64_t rhs = int64_t(int32_t(icregs.icVals[rhsId.id()])); + TRACE_PRINTF("lhs (%d) = %" PRIi64 " rhs (%d) = %" PRIi64 "\n", lhsId.id(), + lhs, rhsId.id(), rhs); + bool result; + switch (op) { + case JSOp::Eq: + case JSOp::StrictEq: + result = lhs == rhs; + break; + case JSOp::Ne: + case JSOp::StrictNe: + result = lhs != rhs; + break; + case JSOp::Lt: + result = lhs < rhs; + break; + case JSOp::Le: + result = lhs <= rhs; + break; + case JSOp::Gt: + result = lhs > rhs; + break; + case JSOp::Ge: + result = lhs >= rhs; + break; + default: + MOZ_CRASH("Unexpected opcode"); + } + icregs.icResult = BooleanValue(result).asRawBits(); + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(CompareNullUndefinedResult) { + JSOp op = icregs.cacheIRReader.jsop(); + bool isUndefined = icregs.cacheIRReader.readBool(); + ValOperandId inputId = icregs.cacheIRReader.valOperandId(); + Value val = Value::fromRawBits(icregs.icVals[inputId.id()]); + if (val.isObject() && val.toObject().getClass()->isProxyObject()) { + return ICInterpretOpResult::NextIC; + } + + bool result; + switch (op) { + case JSOp::Eq: + result = + val.isUndefined() || val.isNull() || + (val.isObject() && val.toObject().getClass()->emulatesUndefined()); + break; + case JSOp::Ne: + result = !( + val.isUndefined() || val.isNull() || + (val.isObject() && val.toObject().getClass()->emulatesUndefined())); + break; + case JSOp::StrictEq: + result = isUndefined ? val.isUndefined() : val.isNull(); + break; + case JSOp::StrictNe: + result = !(isUndefined ? val.isUndefined() : val.isNull()); + break; + default: + MOZ_CRASH("bad opcode"); + } + icregs.icResult = BooleanValue(result).asRawBits(); + PREDICT_NEXT(ReturnFromIC); + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE(AssertPropertyLookup) { + ObjOperandId objId = icregs.cacheIRReader.objOperandId(); + uint32_t idOffset = icregs.cacheIRReader.stubOffset(); + uint32_t slotOffset = icregs.cacheIRReader.stubOffset(); + // Debug-only assertion; we can ignore. + (void)objId; + (void)idOffset; + (void)slotOffset; + DISPATCH_CACHEOP(); + } + + CACHEOP_CASE_UNIMPL(GuardToNonGCThing) + CACHEOP_CASE_UNIMPL(Int32ToIntPtr) + CACHEOP_CASE_UNIMPL(GuardNumberToIntPtrIndex) + CACHEOP_CASE_UNIMPL(GuardToInt32ModUint32) + CACHEOP_CASE_UNIMPL(GuardToUint8Clamped) + CACHEOP_CASE_UNIMPL(GuardMultipleShapes) + CACHEOP_CASE_UNIMPL(GuardNullProto) + CACHEOP_CASE_UNIMPL(GuardAnyClass) + CACHEOP_CASE_UNIMPL(HasClassResult) + CACHEOP_CASE_UNIMPL(CallRegExpMatcherResult) + CACHEOP_CASE_UNIMPL(CallRegExpSearcherResult) + CACHEOP_CASE_UNIMPL(RegExpSearcherLastLimitResult) + CACHEOP_CASE_UNIMPL(RegExpHasCaptureGroupsResult) + CACHEOP_CASE_UNIMPL(RegExpBuiltinExecMatchResult) + CACHEOP_CASE_UNIMPL(RegExpBuiltinExecTestResult) + CACHEOP_CASE_UNIMPL(RegExpFlagResult) + CACHEOP_CASE_UNIMPL(CallSubstringKernelResult) + CACHEOP_CASE_UNIMPL(StringReplaceStringResult) + CACHEOP_CASE_UNIMPL(StringSplitStringResult) + CACHEOP_CASE_UNIMPL(RegExpPrototypeOptimizableResult) + CACHEOP_CASE_UNIMPL(RegExpInstanceOptimizableResult) + CACHEOP_CASE_UNIMPL(GetFirstDollarIndexResult) + CACHEOP_CASE_UNIMPL(GuardCompartment) + CACHEOP_CASE_UNIMPL(GuardIsExtensible) + CACHEOP_CASE_UNIMPL(GuardIsNativeObject) + CACHEOP_CASE_UNIMPL(GuardIsProxy) + CACHEOP_CASE_UNIMPL(GuardIsNotProxy) + CACHEOP_CASE_UNIMPL(GuardIsNotArrayBufferMaybeShared) + CACHEOP_CASE_UNIMPL(GuardIsTypedArray) + CACHEOP_CASE_UNIMPL(GuardIsFixedLengthTypedArray) + CACHEOP_CASE_UNIMPL(GuardHasProxyHandler) + CACHEOP_CASE_UNIMPL(GuardIsNotDOMProxy) + CACHEOP_CASE_UNIMPL(GuardObjectIdentity) + CACHEOP_CASE_UNIMPL(GuardNoDenseElements) + CACHEOP_CASE_UNIMPL(GuardStringToIndex) + CACHEOP_CASE_UNIMPL(GuardStringToInt32) + CACHEOP_CASE_UNIMPL(GuardStringToNumber) + CACHEOP_CASE_UNIMPL(StringToAtom) + CACHEOP_CASE_UNIMPL(BooleanToNumber) + CACHEOP_CASE_UNIMPL(GuardHasGetterSetter) + CACHEOP_CASE_UNIMPL(GuardInt32IsNonNegative) + CACHEOP_CASE_UNIMPL(GuardIndexIsValidUpdateOrAdd) + CACHEOP_CASE_UNIMPL(GuardIndexIsNotDenseElement) + CACHEOP_CASE_UNIMPL(GuardTagNotEqual) + CACHEOP_CASE_UNIMPL(GuardXrayExpandoShapeAndDefaultProto) + CACHEOP_CASE_UNIMPL(GuardXrayNoExpando) + CACHEOP_CASE_UNIMPL(GuardDynamicSlotIsNotObject) + CACHEOP_CASE_UNIMPL(GuardFixedSlotValue) + CACHEOP_CASE_UNIMPL(GuardDynamicSlotValue) + CACHEOP_CASE_UNIMPL(LoadScriptedProxyHandler) + CACHEOP_CASE_UNIMPL(IdToStringOrSymbol) + CACHEOP_CASE_UNIMPL(LoadFixedSlot) + CACHEOP_CASE_UNIMPL(LoadDynamicSlot) + CACHEOP_CASE_UNIMPL(GuardFunctionHasJitEntry) + CACHEOP_CASE_UNIMPL(GuardFunctionHasNoJitEntry) + CACHEOP_CASE_UNIMPL(GuardFunctionIsNonBuiltinCtor) + CACHEOP_CASE_UNIMPL(GuardFunctionIsConstructor) + CACHEOP_CASE_UNIMPL(GuardNotClassConstructor) + CACHEOP_CASE_UNIMPL(GuardArrayIsPacked) + CACHEOP_CASE_UNIMPL(GuardArgumentsObjectFlags) + CACHEOP_CASE_UNIMPL(LoadEnclosingEnvironment) + CACHEOP_CASE_UNIMPL(LoadWrapperTarget) + CACHEOP_CASE_UNIMPL(LoadValueTag) + CACHEOP_CASE_UNIMPL(TruncateDoubleToUInt32) + CACHEOP_CASE_UNIMPL(DoubleToUint8Clamped) + CACHEOP_CASE_UNIMPL(MegamorphicLoadSlotResult) + CACHEOP_CASE_UNIMPL(MegamorphicLoadSlotByValueResult) + CACHEOP_CASE_UNIMPL(MegamorphicStoreSlot) + CACHEOP_CASE_UNIMPL(MegamorphicSetElement) + CACHEOP_CASE_UNIMPL(MegamorphicHasPropResult) + CACHEOP_CASE_UNIMPL(SmallObjectVariableKeyHasOwnResult) + CACHEOP_CASE_UNIMPL(ObjectToIteratorResult) + CACHEOP_CASE_UNIMPL(ValueToIteratorResult) + CACHEOP_CASE_UNIMPL(LoadDOMExpandoValue) + CACHEOP_CASE_UNIMPL(LoadDOMExpandoValueGuardGeneration) + CACHEOP_CASE_UNIMPL(LoadDOMExpandoValueIgnoreGeneration) + CACHEOP_CASE_UNIMPL(GuardDOMExpandoMissingOrGuardShape) + CACHEOP_CASE_UNIMPL(AddAndStoreFixedSlot) + CACHEOP_CASE_UNIMPL(AddAndStoreDynamicSlot) + CACHEOP_CASE_UNIMPL(AllocateAndStoreDynamicSlot) + CACHEOP_CASE_UNIMPL(AddSlotAndCallAddPropHook) + CACHEOP_CASE_UNIMPL(StoreDenseElementHole) + CACHEOP_CASE_UNIMPL(ArrayPush) + CACHEOP_CASE_UNIMPL(ArrayJoinResult) + CACHEOP_CASE_UNIMPL(ObjectKeysResult) + CACHEOP_CASE_UNIMPL(PackedArrayPopResult) + CACHEOP_CASE_UNIMPL(PackedArrayShiftResult) + CACHEOP_CASE_UNIMPL(PackedArraySliceResult) + CACHEOP_CASE_UNIMPL(ArgumentsSliceResult) + CACHEOP_CASE_UNIMPL(IsArrayResult) + CACHEOP_CASE_UNIMPL(StoreFixedSlotUndefinedResult) + CACHEOP_CASE_UNIMPL(IsPackedArrayResult) + CACHEOP_CASE_UNIMPL(IsCallableResult) + CACHEOP_CASE_UNIMPL(IsConstructorResult) + CACHEOP_CASE_UNIMPL(IsCrossRealmArrayConstructorResult) + CACHEOP_CASE_UNIMPL(IsTypedArrayResult) + CACHEOP_CASE_UNIMPL(IsTypedArrayConstructorResult) + CACHEOP_CASE_UNIMPL(ArrayBufferViewByteOffsetInt32Result) + CACHEOP_CASE_UNIMPL(ArrayBufferViewByteOffsetDoubleResult) + CACHEOP_CASE_UNIMPL(TypedArrayByteLengthInt32Result) + CACHEOP_CASE_UNIMPL(TypedArrayByteLengthDoubleResult) + CACHEOP_CASE_UNIMPL(TypedArrayElementSizeResult) + CACHEOP_CASE_UNIMPL(GuardHasAttachedArrayBuffer) + CACHEOP_CASE_UNIMPL(NewArrayIteratorResult) + CACHEOP_CASE_UNIMPL(NewStringIteratorResult) + CACHEOP_CASE_UNIMPL(NewRegExpStringIteratorResult) + CACHEOP_CASE_UNIMPL(ObjectCreateResult) + CACHEOP_CASE_UNIMPL(NewArrayFromLengthResult) + CACHEOP_CASE_UNIMPL(NewTypedArrayFromLengthResult) + CACHEOP_CASE_UNIMPL(NewTypedArrayFromArrayBufferResult) + CACHEOP_CASE_UNIMPL(NewTypedArrayFromArrayResult) + CACHEOP_CASE_UNIMPL(NewStringObjectResult) + CACHEOP_CASE_UNIMPL(StringFromCharCodeResult) + CACHEOP_CASE_UNIMPL(StringFromCodePointResult) + CACHEOP_CASE_UNIMPL(StringIncludesResult) + CACHEOP_CASE_UNIMPL(StringIndexOfResult) + CACHEOP_CASE_UNIMPL(StringLastIndexOfResult) + CACHEOP_CASE_UNIMPL(StringStartsWithResult) + CACHEOP_CASE_UNIMPL(StringEndsWithResult) + CACHEOP_CASE_UNIMPL(StringToLowerCaseResult) + CACHEOP_CASE_UNIMPL(StringToUpperCaseResult) + CACHEOP_CASE_UNIMPL(StringTrimResult) + CACHEOP_CASE_UNIMPL(StringTrimStartResult) + CACHEOP_CASE_UNIMPL(StringTrimEndResult) + CACHEOP_CASE_UNIMPL(LinearizeForCodePointAccess) + CACHEOP_CASE_UNIMPL(LoadStringAtResult) + CACHEOP_CASE_UNIMPL(LoadStringCodePointResult) + CACHEOP_CASE_UNIMPL(ToRelativeStringIndex) + CACHEOP_CASE_UNIMPL(MathAbsInt32Result) + CACHEOP_CASE_UNIMPL(MathAbsNumberResult) + CACHEOP_CASE_UNIMPL(MathClz32Result) + CACHEOP_CASE_UNIMPL(MathSignInt32Result) + CACHEOP_CASE_UNIMPL(MathSignNumberResult) + CACHEOP_CASE_UNIMPL(MathSignNumberToInt32Result) + CACHEOP_CASE_UNIMPL(MathImulResult) + CACHEOP_CASE_UNIMPL(MathSqrtNumberResult) + CACHEOP_CASE_UNIMPL(MathFRoundNumberResult) + CACHEOP_CASE_UNIMPL(MathRandomResult) + CACHEOP_CASE_UNIMPL(MathHypot2NumberResult) + CACHEOP_CASE_UNIMPL(MathHypot3NumberResult) + CACHEOP_CASE_UNIMPL(MathHypot4NumberResult) + CACHEOP_CASE_UNIMPL(MathAtan2NumberResult) + CACHEOP_CASE_UNIMPL(MathFloorNumberResult) + CACHEOP_CASE_UNIMPL(MathCeilNumberResult) + CACHEOP_CASE_UNIMPL(MathTruncNumberResult) + CACHEOP_CASE_UNIMPL(MathFloorToInt32Result) + CACHEOP_CASE_UNIMPL(MathCeilToInt32Result) + CACHEOP_CASE_UNIMPL(MathTruncToInt32Result) + CACHEOP_CASE_UNIMPL(MathRoundToInt32Result) + CACHEOP_CASE_UNIMPL(NumberMinMax) + CACHEOP_CASE_UNIMPL(Int32MinMaxArrayResult) + CACHEOP_CASE_UNIMPL(NumberMinMaxArrayResult) + CACHEOP_CASE_UNIMPL(MathFunctionNumberResult) + CACHEOP_CASE_UNIMPL(NumberParseIntResult) + CACHEOP_CASE_UNIMPL(DoubleParseIntResult) + CACHEOP_CASE_UNIMPL(ObjectToStringResult) + CACHEOP_CASE_UNIMPL(ReflectGetPrototypeOfResult) + CACHEOP_CASE_UNIMPL(StoreTypedArrayElement) + CACHEOP_CASE_UNIMPL(AtomicsCompareExchangeResult) + CACHEOP_CASE_UNIMPL(AtomicsExchangeResult) + CACHEOP_CASE_UNIMPL(AtomicsAddResult) + CACHEOP_CASE_UNIMPL(AtomicsSubResult) + CACHEOP_CASE_UNIMPL(AtomicsAndResult) + CACHEOP_CASE_UNIMPL(AtomicsOrResult) + CACHEOP_CASE_UNIMPL(AtomicsXorResult) + CACHEOP_CASE_UNIMPL(AtomicsLoadResult) + CACHEOP_CASE_UNIMPL(AtomicsStoreResult) + CACHEOP_CASE_UNIMPL(AtomicsIsLockFreeResult) + CACHEOP_CASE_UNIMPL(CallNativeSetter) + CACHEOP_CASE_UNIMPL(CallScriptedSetter) + CACHEOP_CASE_UNIMPL(CallInlinedSetter) + CACHEOP_CASE_UNIMPL(CallDOMSetter) + CACHEOP_CASE_UNIMPL(CallSetArrayLength) + CACHEOP_CASE_UNIMPL(ProxySet) + CACHEOP_CASE_UNIMPL(ProxySetByValue) + CACHEOP_CASE_UNIMPL(CallAddOrUpdateSparseElementHelper) + CACHEOP_CASE_UNIMPL(CallNumberToString) + CACHEOP_CASE_UNIMPL(Int32ToStringWithBaseResult) + CACHEOP_CASE_UNIMPL(BooleanToString) + CACHEOP_CASE_UNIMPL(CallBoundScriptedFunction) + CACHEOP_CASE_UNIMPL(CallWasmFunction) + CACHEOP_CASE_UNIMPL(GuardWasmArg) + CACHEOP_CASE_UNIMPL(CallDOMFunction) + CACHEOP_CASE_UNIMPL(CallClassHook) + CACHEOP_CASE_UNIMPL(CallInlinedFunction) +#ifdef JS_PUNBOX64 + CACHEOP_CASE_UNIMPL(CallScriptedProxyGetResult) + CACHEOP_CASE_UNIMPL(CallScriptedProxyGetByValueResult) +#endif + CACHEOP_CASE_UNIMPL(MetaScriptedThisShape) + CACHEOP_CASE_UNIMPL(BindFunctionResult) + CACHEOP_CASE_UNIMPL(SpecializedBindFunctionResult) + CACHEOP_CASE_UNIMPL(LoadFixedSlotTypedResult) + CACHEOP_CASE_UNIMPL(LoadDenseElementHoleResult) + CACHEOP_CASE_UNIMPL(CallGetSparseElementResult) + CACHEOP_CASE_UNIMPL(LoadDenseElementExistsResult) + CACHEOP_CASE_UNIMPL(LoadTypedArrayElementExistsResult) + CACHEOP_CASE_UNIMPL(LoadDenseElementHoleExistsResult) + CACHEOP_CASE_UNIMPL(LoadTypedArrayElementResult) + CACHEOP_CASE_UNIMPL(LoadDataViewValueResult) + CACHEOP_CASE_UNIMPL(StoreDataViewValueResult) + CACHEOP_CASE_UNIMPL(LoadArgumentsObjectArgResult) + CACHEOP_CASE_UNIMPL(LoadArgumentsObjectArgHoleResult) + CACHEOP_CASE_UNIMPL(LoadArgumentsObjectArgExistsResult) + CACHEOP_CASE_UNIMPL(LoadArgumentsObjectLengthResult) + CACHEOP_CASE_UNIMPL(LoadArgumentsObjectLength) + CACHEOP_CASE_UNIMPL(LoadFunctionLengthResult) + CACHEOP_CASE_UNIMPL(LoadFunctionNameResult) + CACHEOP_CASE_UNIMPL(LoadBoundFunctionNumArgs) + CACHEOP_CASE_UNIMPL(LoadBoundFunctionTarget) + CACHEOP_CASE_UNIMPL(GuardBoundFunctionIsConstructor) + CACHEOP_CASE_UNIMPL(LoadArrayBufferByteLengthInt32Result) + CACHEOP_CASE_UNIMPL(LoadArrayBufferByteLengthDoubleResult) + CACHEOP_CASE_UNIMPL(LoadArrayBufferViewLengthInt32Result) + CACHEOP_CASE_UNIMPL(LoadArrayBufferViewLengthDoubleResult) + CACHEOP_CASE_UNIMPL(FrameIsConstructingResult) + CACHEOP_CASE_UNIMPL(CallScriptedGetterResult) + CACHEOP_CASE_UNIMPL(CallInlinedGetterResult) + CACHEOP_CASE_UNIMPL(CallNativeGetterResult) + CACHEOP_CASE_UNIMPL(CallDOMGetterResult) + CACHEOP_CASE_UNIMPL(ProxyGetResult) + CACHEOP_CASE_UNIMPL(ProxyGetByValueResult) + CACHEOP_CASE_UNIMPL(ProxyHasPropResult) + CACHEOP_CASE_UNIMPL(CallObjectHasSparseElementResult) + CACHEOP_CASE_UNIMPL(CallNativeGetElementResult) + CACHEOP_CASE_UNIMPL(CallNativeGetElementSuperResult) + CACHEOP_CASE_UNIMPL(GetNextMapSetEntryForIteratorResult) + CACHEOP_CASE_UNIMPL(LoadUndefinedResult) + CACHEOP_CASE_UNIMPL(LoadDoubleConstant) + CACHEOP_CASE_UNIMPL(LoadBooleanConstant) + CACHEOP_CASE_UNIMPL(LoadUndefined) + CACHEOP_CASE_UNIMPL(LoadConstantString) + CACHEOP_CASE_UNIMPL(LoadInstanceOfObjectResult) + CACHEOP_CASE_UNIMPL(LoadTypeOfObjectResult) + CACHEOP_CASE_UNIMPL(DoubleAddResult) + CACHEOP_CASE_UNIMPL(DoubleSubResult) + CACHEOP_CASE_UNIMPL(DoubleMulResult) + CACHEOP_CASE_UNIMPL(DoubleDivResult) + CACHEOP_CASE_UNIMPL(DoubleModResult) + CACHEOP_CASE_UNIMPL(DoublePowResult) + CACHEOP_CASE_UNIMPL(BigIntAddResult) + CACHEOP_CASE_UNIMPL(BigIntSubResult) + CACHEOP_CASE_UNIMPL(BigIntMulResult) + CACHEOP_CASE_UNIMPL(BigIntDivResult) + CACHEOP_CASE_UNIMPL(BigIntModResult) + CACHEOP_CASE_UNIMPL(BigIntPowResult) + CACHEOP_CASE_UNIMPL(Int32BitXorResult) + CACHEOP_CASE_UNIMPL(Int32LeftShiftResult) + CACHEOP_CASE_UNIMPL(Int32RightShiftResult) + CACHEOP_CASE_UNIMPL(Int32URightShiftResult) + CACHEOP_CASE_UNIMPL(Int32NotResult) + CACHEOP_CASE_UNIMPL(BigIntBitOrResult) + CACHEOP_CASE_UNIMPL(BigIntBitXorResult) + CACHEOP_CASE_UNIMPL(BigIntBitAndResult) + CACHEOP_CASE_UNIMPL(BigIntLeftShiftResult) + CACHEOP_CASE_UNIMPL(BigIntRightShiftResult) + CACHEOP_CASE_UNIMPL(BigIntNotResult) + CACHEOP_CASE_UNIMPL(Int32NegationResult) + CACHEOP_CASE_UNIMPL(DoubleNegationResult) + CACHEOP_CASE_UNIMPL(BigIntNegationResult) + CACHEOP_CASE_UNIMPL(Int32DecResult) + CACHEOP_CASE_UNIMPL(DoubleIncResult) + CACHEOP_CASE_UNIMPL(DoubleDecResult) + CACHEOP_CASE_UNIMPL(BigIntIncResult) + CACHEOP_CASE_UNIMPL(BigIntDecResult) + CACHEOP_CASE_UNIMPL(LoadDoubleTruthyResult) + CACHEOP_CASE_UNIMPL(LoadBigIntTruthyResult) + CACHEOP_CASE_UNIMPL(LoadValueTruthyResult) + CACHEOP_CASE_UNIMPL(NewPlainObjectResult) + CACHEOP_CASE_UNIMPL(NewArrayObjectResult) + CACHEOP_CASE_UNIMPL(CallStringObjectConcatResult) + CACHEOP_CASE_UNIMPL(CallIsSuspendedGeneratorResult) + CACHEOP_CASE_UNIMPL(CompareObjectResult) + CACHEOP_CASE_UNIMPL(CompareSymbolResult) + CACHEOP_CASE_UNIMPL(CompareDoubleResult) + CACHEOP_CASE_UNIMPL(CompareBigIntResult) + CACHEOP_CASE_UNIMPL(CompareBigIntInt32Result) + CACHEOP_CASE_UNIMPL(CompareBigIntNumberResult) + CACHEOP_CASE_UNIMPL(CompareBigIntStringResult) + CACHEOP_CASE_UNIMPL(CompareDoubleSameValueResult) + CACHEOP_CASE_UNIMPL(SameValueResult) + CACHEOP_CASE_UNIMPL(IndirectTruncateInt32Result) + CACHEOP_CASE_UNIMPL(BigIntAsIntNResult) + CACHEOP_CASE_UNIMPL(BigIntAsUintNResult) + CACHEOP_CASE_UNIMPL(SetHasResult) + CACHEOP_CASE_UNIMPL(SetHasNonGCThingResult) + CACHEOP_CASE_UNIMPL(SetHasStringResult) + CACHEOP_CASE_UNIMPL(SetHasSymbolResult) + CACHEOP_CASE_UNIMPL(SetHasBigIntResult) + CACHEOP_CASE_UNIMPL(SetHasObjectResult) + CACHEOP_CASE_UNIMPL(SetSizeResult) + CACHEOP_CASE_UNIMPL(MapHasResult) + CACHEOP_CASE_UNIMPL(MapHasNonGCThingResult) + CACHEOP_CASE_UNIMPL(MapHasStringResult) + CACHEOP_CASE_UNIMPL(MapHasSymbolResult) + CACHEOP_CASE_UNIMPL(MapHasBigIntResult) + CACHEOP_CASE_UNIMPL(MapHasObjectResult) + CACHEOP_CASE_UNIMPL(MapGetResult) + CACHEOP_CASE_UNIMPL(MapGetNonGCThingResult) + CACHEOP_CASE_UNIMPL(MapGetStringResult) + CACHEOP_CASE_UNIMPL(MapGetSymbolResult) + CACHEOP_CASE_UNIMPL(MapGetBigIntResult) + CACHEOP_CASE_UNIMPL(MapGetObjectResult) + CACHEOP_CASE_UNIMPL(MapSizeResult) + CACHEOP_CASE_UNIMPL(ArrayFromArgumentsObjectResult) + CACHEOP_CASE_UNIMPL(CloseIterScriptedResult) + CACHEOP_CASE_UNIMPL(CallPrintString) + CACHEOP_CASE_UNIMPL(Breakpoint) + CACHEOP_CASE_UNIMPL(WrapResult) + CACHEOP_CASE_UNIMPL(Bailout) + CACHEOP_CASE_UNIMPL(AssertRecoveredOnBailoutResult) + CACHEOP_CASE_UNIMPL(GuardIsNotUninitializedLexical) { + TRACE_PRINTF("unknown CacheOp: %s\n", CacheIROpNames[int(cacheop)]); + return ICInterpretOpResult::NextIC; + } + +#undef PREDICT_NEXT +} + +/* + * ----------------------------------------------- + * IC callsite logic, and fallback stubs + * ----------------------------------------------- + */ + +#define SAVE_INPUTS(arity) \ + do { \ + switch (arity) { \ + case 0: \ + break; \ + case 1: \ + inputs[0] = icregs.icVals[0]; \ + break; \ + case 2: \ + inputs[0] = icregs.icVals[0]; \ + inputs[1] = icregs.icVals[1]; \ + break; \ + case 3: \ + inputs[0] = icregs.icVals[0]; \ + inputs[1] = icregs.icVals[1]; \ + inputs[2] = icregs.icVals[2]; \ + break; \ + } \ + } while (0) + +#define RESTORE_INPUTS(arity) \ + do { \ + switch (arity) { \ + case 0: \ + break; \ + case 1: \ + icregs.icVals[0] = inputs[0]; \ + break; \ + case 2: \ + icregs.icVals[0] = inputs[0]; \ + icregs.icVals[1] = inputs[1]; \ + break; \ + case 3: \ + icregs.icVals[0] = inputs[0]; \ + icregs.icVals[1] = inputs[1]; \ + icregs.icVals[2] = inputs[2]; \ + break; \ + } \ + } while (0) + +#define DEFINE_IC(kind, arity, fallback_body) \ + static PBIResult MOZ_ALWAYS_INLINE IC##kind( \ + BaselineFrame* frame, VMFrameManager& frameMgr, State& state, \ + ICRegs& icregs, Stack& stack, StackVal* sp, jsbytecode* pc) { \ + ICStub* stub = frame->interpreterICEntry()->firstStub(); \ + uint64_t inputs[3]; \ + SAVE_INPUTS(arity); \ + while (true) { \ + next_stub: \ + if (stub->isFallback()) { \ + ICFallbackStub* fallback = stub->toFallbackStub(); \ + fallback_body; \ + icregs.icResult = state.res.asRawBits(); \ + state.res = UndefinedValue(); \ + return PBIResult::Ok; \ + error: \ + return PBIResult::Error; \ + } else { \ + ICCacheIRStub* cstub = stub->toCacheIRStub(); \ + cstub->incrementEnteredCount(); \ + new (&icregs.cacheIRReader) CacheIRReader(cstub->stubInfo()); \ + switch (ICInterpretOps(frame, frameMgr, state, icregs, stack, sp, \ + cstub, pc)) { \ + case ICInterpretOpResult::NextIC: \ + stub = stub->maybeNext(); \ + RESTORE_INPUTS(arity); \ + goto next_stub; \ + case ICInterpretOpResult::Return: \ + return PBIResult::Ok; \ + case ICInterpretOpResult::Error: \ + return PBIResult::Error; \ + case ICInterpretOpResult::Unwind: \ + return PBIResult::Unwind; \ + case ICInterpretOpResult::UnwindError: \ + return PBIResult::UnwindError; \ + case ICInterpretOpResult::UnwindRet: \ + return PBIResult::UnwindRet; \ + } \ + } \ + } \ + } + +#define IC_LOAD_VAL(state_elem, index) \ + ReservedRooted<Value> state_elem(&state.state_elem, \ + Value::fromRawBits(icregs.icVals[(index)])) +#define IC_LOAD_OBJ(state_elem, index) \ + ReservedRooted<JSObject*> state_elem( \ + &state.state_elem, reinterpret_cast<JSObject*>(icregs.icVals[(index)])) + +DEFINE_IC(Typeof, 1, { + IC_LOAD_VAL(value0, 0); + PUSH_FALLBACK_IC_FRAME(); + if (!DoTypeOfFallback(cx, frame, fallback, value0, &state.res)) { + goto error; + } +}); + +DEFINE_IC(GetName, 1, { + IC_LOAD_OBJ(obj0, 0); + PUSH_FALLBACK_IC_FRAME(); + if (!DoGetNameFallback(cx, frame, fallback, obj0, &state.res)) { + goto error; + } +}); + +DEFINE_IC(Call, 1, { + uint32_t argc = uint32_t(icregs.icVals[0]); + uint32_t totalArgs = + argc + icregs.extraArgs; // this, callee, (constructing?), func args + Value* args = reinterpret_cast<Value*>(&sp[0]); + TRACE_PRINTF("Call fallback: argc %d totalArgs %d args %p\n", argc, totalArgs, + args); + // Reverse values on the stack. + std::reverse(args, args + totalArgs); + { + PUSH_FALLBACK_IC_FRAME(); + if (icregs.spreadCall) { + if (!DoSpreadCallFallback(cx, frame, fallback, args, &state.res)) { + std::reverse(args, args + totalArgs); + goto error; + } + } else { + if (!DoCallFallback(cx, frame, fallback, argc, args, &state.res)) { + std::reverse(args, args + totalArgs); + goto error; + } + } + } +}); + +DEFINE_IC(UnaryArith, 1, { + IC_LOAD_VAL(value0, 0); + PUSH_FALLBACK_IC_FRAME(); + if (!DoUnaryArithFallback(cx, frame, fallback, value0, &state.res)) { + goto error; + } +}); + +DEFINE_IC(BinaryArith, 2, { + IC_LOAD_VAL(value0, 0); + IC_LOAD_VAL(value1, 1); + PUSH_FALLBACK_IC_FRAME(); + if (!DoBinaryArithFallback(cx, frame, fallback, value0, value1, &state.res)) { + goto error; + } +}); + +DEFINE_IC(ToBool, 1, { + IC_LOAD_VAL(value0, 0); + PUSH_FALLBACK_IC_FRAME(); + if (!DoToBoolFallback(cx, frame, fallback, value0, &state.res)) { + goto error; + } +}); + +DEFINE_IC(Compare, 2, { + IC_LOAD_VAL(value0, 0); + IC_LOAD_VAL(value1, 1); + PUSH_FALLBACK_IC_FRAME(); + if (!DoCompareFallback(cx, frame, fallback, value0, value1, &state.res)) { + goto error; + } +}); + +DEFINE_IC(InstanceOf, 2, { + IC_LOAD_VAL(value0, 0); + IC_LOAD_VAL(value1, 1); + PUSH_FALLBACK_IC_FRAME(); + if (!DoInstanceOfFallback(cx, frame, fallback, value0, value1, &state.res)) { + goto error; + } +}); + +DEFINE_IC(In, 2, { + IC_LOAD_VAL(value0, 0); + IC_LOAD_VAL(value1, 1); + PUSH_FALLBACK_IC_FRAME(); + if (!DoInFallback(cx, frame, fallback, value0, value1, &state.res)) { + goto error; + } +}); + +DEFINE_IC(BindName, 1, { + IC_LOAD_OBJ(obj0, 0); + PUSH_FALLBACK_IC_FRAME(); + if (!DoBindNameFallback(cx, frame, fallback, obj0, &state.res)) { + goto error; + } +}); + +DEFINE_IC(SetProp, 2, { + IC_LOAD_VAL(value0, 0); + IC_LOAD_VAL(value1, 1); + PUSH_FALLBACK_IC_FRAME(); + if (!DoSetPropFallback(cx, frame, fallback, nullptr, value0, value1)) { + goto error; + } +}); + +DEFINE_IC(NewObject, 0, { + PUSH_FALLBACK_IC_FRAME(); + if (!DoNewObjectFallback(cx, frame, fallback, &state.res)) { + goto error; + } +}); + +DEFINE_IC(GetProp, 1, { + IC_LOAD_VAL(value0, 0); + PUSH_FALLBACK_IC_FRAME(); + if (!DoGetPropFallback(cx, frame, fallback, &value0, &state.res)) { + goto error; + } +}); + +DEFINE_IC(GetPropSuper, 2, { + IC_LOAD_VAL(value0, 1); + IC_LOAD_VAL(value1, 0); + PUSH_FALLBACK_IC_FRAME(); + if (!DoGetPropSuperFallback(cx, frame, fallback, value0, &value1, + &state.res)) { + goto error; + } +}); + +DEFINE_IC(GetElem, 2, { + IC_LOAD_VAL(value0, 0); + IC_LOAD_VAL(value1, 1); + PUSH_FALLBACK_IC_FRAME(); + if (!DoGetElemFallback(cx, frame, fallback, value0, value1, &state.res)) { + goto error; + } +}); + +DEFINE_IC(GetElemSuper, 3, { + IC_LOAD_VAL(value0, 0); // receiver + IC_LOAD_VAL(value1, 1); // obj (lhs) + IC_LOAD_VAL(value2, 2); // key (rhs) + PUSH_FALLBACK_IC_FRAME(); + if (!DoGetElemSuperFallback(cx, frame, fallback, value1, value2, value0, + &state.res)) { + goto error; + } +}); + +DEFINE_IC(NewArray, 0, { + PUSH_FALLBACK_IC_FRAME(); + if (!DoNewArrayFallback(cx, frame, fallback, &state.res)) { + goto error; + } +}); + +DEFINE_IC(GetIntrinsic, 0, { + PUSH_FALLBACK_IC_FRAME(); + if (!DoGetIntrinsicFallback(cx, frame, fallback, &state.res)) { + goto error; + } +}); + +DEFINE_IC(SetElem, 3, { + IC_LOAD_VAL(value0, 0); + IC_LOAD_VAL(value1, 1); + IC_LOAD_VAL(value2, 2); + PUSH_FALLBACK_IC_FRAME(); + if (!DoSetElemFallback(cx, frame, fallback, nullptr, value0, value1, + value2)) { + goto error; + } +}); + +DEFINE_IC(HasOwn, 2, { + IC_LOAD_VAL(value0, 0); + IC_LOAD_VAL(value1, 1); + PUSH_FALLBACK_IC_FRAME(); + if (!DoHasOwnFallback(cx, frame, fallback, value0, value1, &state.res)) { + goto error; + } +}); + +DEFINE_IC(CheckPrivateField, 2, { + IC_LOAD_VAL(value0, 0); + IC_LOAD_VAL(value1, 1); + PUSH_FALLBACK_IC_FRAME(); + if (!DoCheckPrivateFieldFallback(cx, frame, fallback, value0, value1, + &state.res)) { + goto error; + } +}); + +DEFINE_IC(GetIterator, 1, { + IC_LOAD_VAL(value0, 0); + PUSH_FALLBACK_IC_FRAME(); + if (!DoGetIteratorFallback(cx, frame, fallback, value0, &state.res)) { + goto error; + } +}); + +DEFINE_IC(ToPropertyKey, 1, { + IC_LOAD_VAL(value0, 0); + PUSH_FALLBACK_IC_FRAME(); + if (!DoToPropertyKeyFallback(cx, frame, fallback, value0, &state.res)) { + goto error; + } +}); + +DEFINE_IC(OptimizeSpreadCall, 1, { + IC_LOAD_VAL(value0, 0); + PUSH_FALLBACK_IC_FRAME(); + if (!DoOptimizeSpreadCallFallback(cx, frame, fallback, value0, &state.res)) { + goto error; + } +}); + +DEFINE_IC(OptimizeGetIterator, 1, { + IC_LOAD_VAL(value0, 0); + PUSH_FALLBACK_IC_FRAME(); + if (!DoOptimizeGetIteratorFallback(cx, frame, fallback, value0, &state.res)) { + goto error; + } +}); + +DEFINE_IC(Rest, 0, { + PUSH_FALLBACK_IC_FRAME(); + if (!DoRestFallback(cx, frame, fallback, &state.res)) { + goto error; + } +}); + +DEFINE_IC(CloseIter, 1, { + IC_LOAD_OBJ(obj0, 0); + PUSH_FALLBACK_IC_FRAME(); + if (!DoCloseIterFallback(cx, frame, fallback, obj0)) { + goto error; + } +}); + +/* + * ----------------------------------------------- + * Main JSOp interpreter + * ----------------------------------------------- + */ + +static EnvironmentObject& getEnvironmentFromCoordinate( + BaselineFrame* frame, EnvironmentCoordinate ec) { + JSObject* env = frame->environmentChain(); + for (unsigned i = ec.hops(); i; i--) { + if (env->is<EnvironmentObject>()) { + env = &env->as<EnvironmentObject>().enclosingEnvironment(); + } else { + MOZ_ASSERT(env->is<DebugEnvironmentProxy>()); + env = &env->as<DebugEnvironmentProxy>().enclosingEnvironment(); + } + } + return env->is<EnvironmentObject>() + ? env->as<EnvironmentObject>() + : env->as<DebugEnvironmentProxy>().environment(); +} + +#ifndef __wasi__ +# define DEBUG_CHECK() \ + if (frame->isDebuggee()) { \ + TRACE_PRINTF( \ + "Debug check: frame is debuggee, checking for debug script\n"); \ + if (script->hasDebugScript()) { \ + goto debug; \ + } \ + } +#else +# define DEBUG_CHECK() +#endif + +#define LABEL(op) (&&label_##op) +#define CASE(op) label_##op: +#if !defined(TRACE_INTERP) +# define DISPATCH() \ + DEBUG_CHECK(); \ + goto* addresses[*pc] +#else +# define DISPATCH() \ + DEBUG_CHECK(); \ + goto dispatch +#endif + +#define ADVANCE(delta) pc += (delta); +#define ADVANCE_AND_DISPATCH(delta) \ + ADVANCE(delta); \ + DISPATCH(); + +#define END_OP(op) ADVANCE_AND_DISPATCH(JSOpLength_##op); + +#define IC_SET_ARG_FROM_STACK(index, stack_index) \ + icregs.icVals[(index)] = sp[(stack_index)].asUInt64(); +#define IC_POP_ARG(index) icregs.icVals[(index)] = (*sp++).asUInt64(); +#define IC_SET_VAL_ARG(index, expr) icregs.icVals[(index)] = (expr).asRawBits(); +#define IC_SET_OBJ_ARG(index, expr) \ + icregs.icVals[(index)] = reinterpret_cast<uint64_t>(expr); +#define IC_PUSH_RESULT() PUSH(StackVal(icregs.icResult)); + +#if !defined(TRACE_INTERP) +# define PREDICT_NEXT(op) \ + if (JSOp(*pc) == JSOp::op) { \ + DEBUG_CHECK(); \ + goto label_##op; \ + } +#else +# define PREDICT_NEXT(op) +#endif + +#define COUNT_COVERAGE_PC(PC) \ + if (script->hasScriptCounts()) { \ + PCCounts* counts = script->maybeGetPCCounts(PC); \ + MOZ_ASSERT(counts); \ + counts->numExec()++; \ + } +#define COUNT_COVERAGE_MAIN() \ + { \ + jsbytecode* main = script->main(); \ + if (!BytecodeIsJumpTarget(JSOp(*main))) COUNT_COVERAGE_PC(main); \ + } + +#define NEXT_IC() frame->interpreterICEntry()++; + +#define INVOKE_IC(kind) \ + switch (IC##kind(frame, frameMgr, state, icregs, stack, sp, pc)) { \ + case PBIResult::Ok: \ + break; \ + case PBIResult::Error: \ + goto error; \ + case PBIResult::Unwind: \ + goto unwind; \ + case PBIResult::UnwindError: \ + goto unwind_error; \ + case PBIResult::UnwindRet: \ + goto unwind_ret; \ + } \ + NEXT_IC(); + +PBIResult PortableBaselineInterpret(JSContext* cx_, State& state, Stack& stack, + StackVal* sp, JSObject* envChain, + Value* ret) { +#define OPCODE_LABEL(op, ...) LABEL(op), +#define TRAILING_LABEL(v) LABEL(default), + + static const void* const addresses[EnableInterruptsPseudoOpcode + 1] = { + FOR_EACH_OPCODE(OPCODE_LABEL) + FOR_EACH_TRAILING_UNUSED_OPCODE(TRAILING_LABEL)}; + +#undef OPCODE_LABEL +#undef TRAILING_LABEL + + PUSHNATIVE(StackValNative(nullptr)); // Fake return address. + BaselineFrame* frame = stack.pushFrame(sp, cx_, envChain); + MOZ_ASSERT(frame); // safety: stack margin. + sp = reinterpret_cast<StackVal*>(frame); + + // Save the entry frame so that when unwinding, we know when to + // return from this C++ frame. + StackVal* entryFrame = sp; + + ICRegs icregs; + RootedScript script(cx_, frame->script()); + jsbytecode* pc = frame->interpreterPC(); + bool from_unwind = false; + + VMFrameManager frameMgr(cx_, frame); + + AutoCheckRecursionLimit recursion(frameMgr.cxForLocalUseOnly()); + if (!recursion.checkDontReport(frameMgr.cxForLocalUseOnly())) { + PUSH_EXIT_FRAME(); + ReportOverRecursed(frameMgr.cxForLocalUseOnly()); + return PBIResult::Error; + } + + // Check max stack depth once, so we don't need to check it + // otherwise below for ordinary stack-manipulation opcodes (just for + // exit frames). + if (!stack.check(sp, sizeof(StackVal) * script->nslots())) { + PUSH_EXIT_FRAME(); + ReportOverRecursed(frameMgr.cxForLocalUseOnly()); + return PBIResult::Error; + } + + uint32_t nfixed = script->nfixed(); + for (uint32_t i = 0; i < nfixed; i++) { + PUSH(StackVal(UndefinedValue())); + } + ret->setUndefined(); + + if (CalleeTokenIsFunction(frame->calleeToken())) { + JSFunction* func = CalleeTokenToFunction(frame->calleeToken()); + frame->setEnvironmentChain(func->environment()); + if (func->needsFunctionEnvironmentObjects()) { + PUSH_EXIT_FRAME(); + if (!js::InitFunctionEnvironmentObjects(cx, frame)) { + goto error; + } + TRACE_PRINTF("callee is func %p; created environment object: %p\n", func, + frame->environmentChain()); + } + } + + // Check if we are being debugged, and set a flag in the frame if + // so. + if (script->isDebuggee()) { + TRACE_PRINTF("Script is debuggee\n"); + frame->setIsDebuggee(); + + PUSH_EXIT_FRAME(); + if (!DebugPrologue(cx, frame)) { + goto error; + } + } + + if (!script->hasScriptCounts()) { + if (frameMgr.cxForLocalUseOnly()->realm()->collectCoverageForDebug()) { + PUSH_EXIT_FRAME(); + if (!script->initScriptCounts(cx)) { + goto error; + } + } + } + COUNT_COVERAGE_MAIN(); + +#ifndef __wasi__ + if (frameMgr.cxForLocalUseOnly()->hasAnyPendingInterrupt()) { + PUSH_EXIT_FRAME(); + if (!InterruptCheck(cx)) { + goto error; + } + } +#endif + + TRACE_PRINTF("Entering: sp = %p fp = %p frame = %p, script = %p, pc = %p\n", + sp, stack.fp, frame, script.get(), pc); + TRACE_PRINTF("nslots = %d nfixed = %d\n", int(script->nslots()), + int(script->nfixed())); + + while (true) { + DEBUG_CHECK(); + +#ifndef __wasi__ + dispatch: +#endif + +#ifdef TRACE_INTERP + { + JSOp op = JSOp(*pc); + printf("sp[0] = %" PRIx64 " sp[1] = %" PRIx64 " sp[2] = %" PRIx64 "\n", + sp[0].asUInt64(), sp[1].asUInt64(), sp[2].asUInt64()); + printf("script = %p pc = %p: %s (ic %d) pending = %d\n", script.get(), pc, + CodeName(op), + (int)(frame->interpreterICEntry() - + script->jitScript()->icScript()->icEntries()), + frameMgr.cxForLocalUseOnly()->isExceptionPending()); + printf("sp = %p fp = %p\n", sp, stack.fp); + printf("TOS tag: %d\n", int(sp[0].asValue().asRawBits() >> 47)); + fflush(stdout); + } +#endif + + goto* addresses[*pc]; + + CASE(Nop) { END_OP(Nop); } + CASE(NopIsAssignOp) { END_OP(NopIsAssignOp); } + CASE(Undefined) { + PUSH(StackVal(UndefinedValue())); + END_OP(Undefined); + } + CASE(Null) { + PUSH(StackVal(NullValue())); + END_OP(Null); + } + CASE(False) { + PUSH(StackVal(BooleanValue(false))); + END_OP(False); + } + CASE(True) { + PUSH(StackVal(BooleanValue(true))); + END_OP(True); + } + CASE(Int32) { + PUSH(StackVal(Int32Value(GET_INT32(pc)))); + END_OP(Int32); + } + CASE(Zero) { + PUSH(StackVal(Int32Value(0))); + END_OP(Zero); + } + CASE(One) { + PUSH(StackVal(Int32Value(1))); + END_OP(One); + } + CASE(Int8) { + PUSH(StackVal(Int32Value(GET_INT8(pc)))); + END_OP(Int8); + } + CASE(Uint16) { + PUSH(StackVal(Int32Value(GET_UINT16(pc)))); + END_OP(Uint16); + } + CASE(Uint24) { + PUSH(StackVal(Int32Value(GET_UINT24(pc)))); + END_OP(Uint24); + } + CASE(Double) { + PUSH(StackVal(GET_INLINE_VALUE(pc))); + END_OP(Double); + } + CASE(BigInt) { + PUSH(StackVal(JS::BigIntValue(script->getBigInt(pc)))); + END_OP(BigInt); + } + CASE(String) { + PUSH(StackVal(StringValue(script->getString(pc)))); + END_OP(String); + } + CASE(Symbol) { + PUSH(StackVal( + SymbolValue(frameMgr.cxForLocalUseOnly()->wellKnownSymbols().get( + GET_UINT8(pc))))); + END_OP(Symbol); + } + CASE(Void) { + sp[0] = StackVal(JS::UndefinedValue()); + END_OP(Void); + } + + CASE(Typeof) + CASE(TypeofExpr) { + static_assert(JSOpLength_Typeof == JSOpLength_TypeofExpr); + if (kHybridICs) { + sp[0] = StackVal(StringValue(TypeOfOperation( + Stack::handle(sp), frameMgr.cxForLocalUseOnly()->runtime()))); + NEXT_IC(); + } else { + IC_POP_ARG(0); + INVOKE_IC(Typeof); + IC_PUSH_RESULT(); + } + END_OP(Typeof); + } + + CASE(Pos) { + if (sp[0].asValue().isNumber()) { + // Nothing! + NEXT_IC(); + END_OP(Pos); + } else { + goto generic_unary; + } + } + CASE(Neg) { + if (sp[0].asValue().isInt32()) { + int32_t i = sp[0].asValue().toInt32(); + if (i != 0 && i != INT32_MIN) { + sp[0] = StackVal(Int32Value(-i)); + NEXT_IC(); + END_OP(Neg); + } + } + if (sp[0].asValue().isNumber()) { + sp[0] = StackVal(NumberValue(-sp[0].asValue().toNumber())); + NEXT_IC(); + END_OP(Neg); + } + goto generic_unary; + } + + CASE(Inc) { + if (sp[0].asValue().isInt32()) { + int32_t i = sp[0].asValue().toInt32(); + if (i != INT32_MAX) { + sp[0] = StackVal(Int32Value(i + 1)); + NEXT_IC(); + END_OP(Inc); + } + } + if (sp[0].asValue().isNumber()) { + sp[0] = StackVal(NumberValue(sp[0].asValue().toNumber() + 1)); + NEXT_IC(); + END_OP(Inc); + } + goto generic_unary; + } + CASE(Dec) { + if (sp[0].asValue().isInt32()) { + int32_t i = sp[0].asValue().toInt32(); + if (i != INT32_MIN) { + sp[0] = StackVal(Int32Value(i - 1)); + NEXT_IC(); + END_OP(Dec); + } + } + if (sp[0].asValue().isNumber()) { + sp[0] = StackVal(NumberValue(sp[0].asValue().toNumber() - 1)); + NEXT_IC(); + END_OP(Dec); + } + goto generic_unary; + } + + CASE(BitNot) { + if (sp[0].asValue().isInt32()) { + int32_t i = sp[0].asValue().toInt32(); + sp[0] = StackVal(Int32Value(~i)); + NEXT_IC(); + END_OP(Inc); + } + goto generic_unary; + } + + CASE(ToNumeric) { + if (sp[0].asValue().isNumeric()) { + NEXT_IC(); + } else if (kHybridICs) { + MutableHandleValue val = Stack::handleMut(&sp[0]); + PUSH_EXIT_FRAME(); + if (!ToNumeric(cx, val)) { + goto error; + } + NEXT_IC(); + } else { + goto generic_unary; + } + END_OP(ToNumeric); + } + + generic_unary: { + static_assert(JSOpLength_Pos == JSOpLength_Neg); + static_assert(JSOpLength_Pos == JSOpLength_BitNot); + static_assert(JSOpLength_Pos == JSOpLength_Inc); + static_assert(JSOpLength_Pos == JSOpLength_Dec); + static_assert(JSOpLength_Pos == JSOpLength_ToNumeric); + IC_POP_ARG(0); + INVOKE_IC(UnaryArith); + IC_PUSH_RESULT(); + END_OP(Pos); + } + + CASE(Not) { + if (kHybridICs) { + sp[0] = StackVal(BooleanValue(!ToBoolean(Stack::handle(sp)))); + NEXT_IC(); + } else { + IC_POP_ARG(0); + INVOKE_IC(ToBool); + PUSH(StackVal( + BooleanValue(!Value::fromRawBits(icregs.icResult).toBoolean()))); + } + END_OP(Not); + } + + CASE(And) { + bool result; + if (kHybridICs) { + result = ToBoolean(Stack::handle(sp)); + NEXT_IC(); + } else { + IC_SET_ARG_FROM_STACK(0, 0); + INVOKE_IC(ToBool); + result = Value::fromRawBits(icregs.icResult).toBoolean(); + } + int32_t jumpOffset = GET_JUMP_OFFSET(pc); + if (!result) { + ADVANCE(jumpOffset); + PREDICT_NEXT(JumpTarget); + PREDICT_NEXT(LoopHead); + } else { + ADVANCE(JSOpLength_And); + } + DISPATCH(); + } + CASE(Or) { + bool result; + if (kHybridICs) { + result = ToBoolean(Stack::handle(sp)); + NEXT_IC(); + } else { + IC_SET_ARG_FROM_STACK(0, 0); + INVOKE_IC(ToBool); + result = Value::fromRawBits(icregs.icResult).toBoolean(); + } + int32_t jumpOffset = GET_JUMP_OFFSET(pc); + if (result) { + ADVANCE(jumpOffset); + PREDICT_NEXT(JumpTarget); + PREDICT_NEXT(LoopHead); + } else { + ADVANCE(JSOpLength_Or); + } + DISPATCH(); + } + CASE(JumpIfTrue) { + bool result; + if (kHybridICs) { + result = ToBoolean(Stack::handle(sp)); + POP(); + NEXT_IC(); + } else { + IC_POP_ARG(0); + INVOKE_IC(ToBool); + result = Value::fromRawBits(icregs.icResult).toBoolean(); + } + int32_t jumpOffset = GET_JUMP_OFFSET(pc); + if (result) { + ADVANCE(jumpOffset); + PREDICT_NEXT(JumpTarget); + PREDICT_NEXT(LoopHead); + } else { + ADVANCE(JSOpLength_JumpIfTrue); + } + DISPATCH(); + } + CASE(JumpIfFalse) { + bool result; + if (kHybridICs) { + result = ToBoolean(Stack::handle(sp)); + POP(); + NEXT_IC(); + } else { + IC_POP_ARG(0); + INVOKE_IC(ToBool); + result = Value::fromRawBits(icregs.icResult).toBoolean(); + } + int32_t jumpOffset = GET_JUMP_OFFSET(pc); + if (!result) { + ADVANCE(jumpOffset); + PREDICT_NEXT(JumpTarget); + PREDICT_NEXT(LoopHead); + } else { + ADVANCE(JSOpLength_JumpIfFalse); + } + DISPATCH(); + } + + CASE(Add) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + int64_t lhs = sp[1].asValue().toInt32(); + int64_t rhs = sp[0].asValue().toInt32(); + if (lhs + rhs >= int64_t(INT32_MIN) && + lhs + rhs <= int64_t(INT32_MAX)) { + POP(); + sp[0] = StackVal(Int32Value(int32_t(lhs + rhs))); + NEXT_IC(); + END_OP(Add); + } + } + if (sp[0].asValue().isNumber() && sp[1].asValue().isNumber()) { + double lhs = sp[1].asValue().toNumber(); + double rhs = sp[0].asValue().toNumber(); + POP(); + sp[0] = StackVal(NumberValue(lhs + rhs)); + NEXT_IC(); + END_OP(Add); + } + if (kHybridICs) { + MutableHandleValue lhs = Stack::handleMut(sp + 1); + MutableHandleValue rhs = Stack::handleMut(sp); + MutableHandleValue result = Stack::handleMut(sp + 1); + { + PUSH_EXIT_FRAME(); + if (!AddOperation(cx, lhs, rhs, result)) { + goto error; + } + } + POP(); + NEXT_IC(); + END_OP(Add); + } + goto generic_binary; + } + + CASE(Sub) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + int64_t lhs = sp[1].asValue().toInt32(); + int64_t rhs = sp[0].asValue().toInt32(); + if (lhs - rhs >= int64_t(INT32_MIN) && + lhs - rhs <= int64_t(INT32_MAX)) { + POP(); + sp[0] = StackVal(Int32Value(int32_t(lhs - rhs))); + NEXT_IC(); + END_OP(Sub); + } + } + if (sp[0].asValue().isNumber() && sp[1].asValue().isNumber()) { + double lhs = sp[1].asValue().toNumber(); + double rhs = sp[0].asValue().toNumber(); + POP(); + sp[0] = StackVal(NumberValue(lhs - rhs)); + NEXT_IC(); + END_OP(Add); + } + if (kHybridICs) { + MutableHandleValue lhs = Stack::handleMut(sp + 1); + MutableHandleValue rhs = Stack::handleMut(sp); + MutableHandleValue result = Stack::handleMut(sp + 1); + { + PUSH_EXIT_FRAME(); + if (!SubOperation(cx, lhs, rhs, result)) { + goto error; + } + } + POP(); + NEXT_IC(); + END_OP(Sub); + } + goto generic_binary; + } + + CASE(Mul) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + int64_t lhs = sp[1].asValue().toInt32(); + int64_t rhs = sp[0].asValue().toInt32(); + int64_t product = lhs * rhs; + if (product >= int64_t(INT32_MIN) && product <= int64_t(INT32_MAX) && + (product != 0 || !((lhs < 0) ^ (rhs < 0)))) { + POP(); + sp[0] = StackVal(Int32Value(int32_t(product))); + NEXT_IC(); + END_OP(Mul); + } + } + if (sp[0].asValue().isNumber() && sp[1].asValue().isNumber()) { + double lhs = sp[1].asValue().toNumber(); + double rhs = sp[0].asValue().toNumber(); + POP(); + sp[0] = StackVal(NumberValue(lhs * rhs)); + NEXT_IC(); + END_OP(Mul); + } + if (kHybridICs) { + MutableHandleValue lhs = Stack::handleMut(sp + 1); + MutableHandleValue rhs = Stack::handleMut(sp); + MutableHandleValue result = Stack::handleMut(sp + 1); + { + PUSH_EXIT_FRAME(); + if (!MulOperation(cx, lhs, rhs, result)) { + goto error; + } + } + POP(); + NEXT_IC(); + END_OP(Mul); + } + goto generic_binary; + } + CASE(Div) { + if (sp[0].asValue().isNumber() && sp[1].asValue().isNumber()) { + double lhs = sp[1].asValue().toNumber(); + double rhs = sp[0].asValue().toNumber(); + POP(); + sp[0] = StackVal(NumberValue(NumberDiv(lhs, rhs))); + NEXT_IC(); + END_OP(Div); + } + if (kHybridICs) { + MutableHandleValue lhs = Stack::handleMut(sp + 1); + MutableHandleValue rhs = Stack::handleMut(sp); + MutableHandleValue result = Stack::handleMut(sp + 1); + { + PUSH_EXIT_FRAME(); + if (!DivOperation(cx, lhs, rhs, result)) { + goto error; + } + } + POP(); + NEXT_IC(); + END_OP(Div); + } + goto generic_binary; + } + CASE(Mod) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + int64_t lhs = sp[1].asValue().toInt32(); + int64_t rhs = sp[0].asValue().toInt32(); + if (lhs > 0 && rhs > 0) { + int64_t mod = lhs % rhs; + POP(); + sp[0] = StackVal(Int32Value(int32_t(mod))); + NEXT_IC(); + END_OP(Mod); + } + } + if (sp[0].asValue().isNumber() && sp[1].asValue().isNumber()) { + double lhs = sp[1].asValue().toNumber(); + double rhs = sp[0].asValue().toNumber(); + POP(); + sp[0] = StackVal(DoubleValue(NumberMod(lhs, rhs))); + NEXT_IC(); + END_OP(Mod); + } + if (kHybridICs) { + MutableHandleValue lhs = Stack::handleMut(sp + 1); + MutableHandleValue rhs = Stack::handleMut(sp); + MutableHandleValue result = Stack::handleMut(sp + 1); + { + PUSH_EXIT_FRAME(); + if (!ModOperation(cx, lhs, rhs, result)) { + goto error; + } + } + POP(); + NEXT_IC(); + END_OP(Mod); + } + goto generic_binary; + } + CASE(Pow) { + if (sp[0].asValue().isNumber() && sp[1].asValue().isNumber()) { + double lhs = sp[1].asValue().toNumber(); + double rhs = sp[0].asValue().toNumber(); + POP(); + sp[0] = StackVal(NumberValue(ecmaPow(lhs, rhs))); + NEXT_IC(); + END_OP(Pow); + } + if (kHybridICs) { + MutableHandleValue lhs = Stack::handleMut(sp + 1); + MutableHandleValue rhs = Stack::handleMut(sp); + MutableHandleValue result = Stack::handleMut(sp + 1); + { + PUSH_EXIT_FRAME(); + if (!PowOperation(cx, lhs, rhs, result)) { + goto error; + } + } + POP(); + NEXT_IC(); + END_OP(Pow); + } + goto generic_binary; + } + CASE(BitOr) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + int32_t lhs = sp[1].asValue().toInt32(); + int32_t rhs = sp[0].asValue().toInt32(); + POP(); + sp[0] = StackVal(Int32Value(lhs | rhs)); + NEXT_IC(); + END_OP(BitOr); + } + goto generic_binary; + } + CASE(BitAnd) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + int32_t lhs = sp[1].asValue().toInt32(); + int32_t rhs = sp[0].asValue().toInt32(); + POP(); + sp[0] = StackVal(Int32Value(lhs & rhs)); + NEXT_IC(); + END_OP(BitAnd); + } + goto generic_binary; + } + CASE(BitXor) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + int32_t lhs = sp[1].asValue().toInt32(); + int32_t rhs = sp[0].asValue().toInt32(); + POP(); + sp[0] = StackVal(Int32Value(lhs ^ rhs)); + NEXT_IC(); + END_OP(BitXor); + } + goto generic_binary; + } + CASE(Lsh) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + // Unsigned to avoid undefined behavior on left-shift overflow + // (see comment in BitLshOperation in Interpreter.cpp). + uint32_t lhs = uint32_t(sp[1].asValue().toInt32()); + uint32_t rhs = uint32_t(sp[0].asValue().toInt32()); + POP(); + rhs &= 31; + sp[0] = StackVal(Int32Value(int32_t(lhs << rhs))); + NEXT_IC(); + END_OP(Lsh); + } + goto generic_binary; + } + CASE(Rsh) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + int32_t lhs = sp[1].asValue().toInt32(); + int32_t rhs = sp[0].asValue().toInt32(); + POP(); + rhs &= 31; + sp[0] = StackVal(Int32Value(lhs >> rhs)); + NEXT_IC(); + END_OP(Rsh); + } + goto generic_binary; + } + CASE(Ursh) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + uint32_t lhs = uint32_t(sp[1].asValue().toInt32()); + int32_t rhs = sp[0].asValue().toInt32(); + POP(); + rhs &= 31; + uint32_t result = lhs >> rhs; + if (result <= uint32_t(INT32_MAX)) { + sp[0] = StackVal(Int32Value(int32_t(result))); + } else { + sp[0] = StackVal(NumberValue(double(result))); + } + NEXT_IC(); + END_OP(Ursh); + } + goto generic_binary; + } + + generic_binary: { + static_assert(JSOpLength_BitOr == JSOpLength_BitXor); + static_assert(JSOpLength_BitOr == JSOpLength_BitAnd); + static_assert(JSOpLength_BitOr == JSOpLength_Lsh); + static_assert(JSOpLength_BitOr == JSOpLength_Rsh); + static_assert(JSOpLength_BitOr == JSOpLength_Ursh); + static_assert(JSOpLength_BitOr == JSOpLength_Add); + static_assert(JSOpLength_BitOr == JSOpLength_Sub); + static_assert(JSOpLength_BitOr == JSOpLength_Mul); + static_assert(JSOpLength_BitOr == JSOpLength_Div); + static_assert(JSOpLength_BitOr == JSOpLength_Mod); + static_assert(JSOpLength_BitOr == JSOpLength_Pow); + IC_POP_ARG(1); + IC_POP_ARG(0); + INVOKE_IC(BinaryArith); + IC_PUSH_RESULT(); + END_OP(Div); + } + + CASE(Eq) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + bool result = sp[0].asValue().toInt32() == sp[1].asValue().toInt32(); + POP(); + sp[0] = StackVal(BooleanValue(result)); + NEXT_IC(); + END_OP(Eq); + } + if (sp[0].asValue().isNumber() && sp[1].asValue().isNumber()) { + double lhs = sp[1].asValue().toNumber(); + double rhs = sp[0].asValue().toNumber(); + bool result = lhs == rhs; + POP(); + sp[0] = StackVal(BooleanValue(result)); + NEXT_IC(); + END_OP(Eq); + } + if (sp[0].asValue().isNumber() && sp[1].asValue().isNumber()) { + bool result = sp[0].asValue().toNumber() == sp[1].asValue().toNumber(); + POP(); + sp[0] = StackVal(BooleanValue(result)); + NEXT_IC(); + END_OP(Eq); + } + goto generic_cmp; + } + + CASE(Ne) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + bool result = sp[0].asValue().toInt32() != sp[1].asValue().toInt32(); + POP(); + sp[0] = StackVal(BooleanValue(result)); + NEXT_IC(); + END_OP(Ne); + } + if (sp[0].asValue().isNumber() && sp[1].asValue().isNumber()) { + double lhs = sp[1].asValue().toNumber(); + double rhs = sp[0].asValue().toNumber(); + bool result = lhs != rhs; + POP(); + sp[0] = StackVal(BooleanValue(result)); + NEXT_IC(); + END_OP(Ne); + } + if (sp[0].asValue().isNumber() && sp[1].asValue().isNumber()) { + bool result = sp[0].asValue().toNumber() != sp[1].asValue().toNumber(); + POP(); + sp[0] = StackVal(BooleanValue(result)); + NEXT_IC(); + END_OP(Eq); + } + goto generic_cmp; + } + + CASE(Lt) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + bool result = sp[1].asValue().toInt32() < sp[0].asValue().toInt32(); + POP(); + sp[0] = StackVal(BooleanValue(result)); + NEXT_IC(); + END_OP(Lt); + } + if (sp[0].asValue().isNumber() && sp[1].asValue().isNumber()) { + double lhs = sp[1].asValue().toNumber(); + double rhs = sp[0].asValue().toNumber(); + bool result = lhs < rhs; + if (std::isnan(lhs) || std::isnan(rhs)) { + result = false; + } + POP(); + sp[0] = StackVal(BooleanValue(result)); + NEXT_IC(); + END_OP(Lt); + } + goto generic_cmp; + } + CASE(Le) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + bool result = sp[1].asValue().toInt32() <= sp[0].asValue().toInt32(); + POP(); + sp[0] = StackVal(BooleanValue(result)); + NEXT_IC(); + END_OP(Le); + } + if (sp[0].asValue().isNumber() && sp[1].asValue().isNumber()) { + double lhs = sp[1].asValue().toNumber(); + double rhs = sp[0].asValue().toNumber(); + bool result = lhs <= rhs; + if (std::isnan(lhs) || std::isnan(rhs)) { + result = false; + } + POP(); + sp[0] = StackVal(BooleanValue(result)); + NEXT_IC(); + END_OP(Le); + } + goto generic_cmp; + } + CASE(Gt) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + bool result = sp[1].asValue().toInt32() > sp[0].asValue().toInt32(); + POP(); + sp[0] = StackVal(BooleanValue(result)); + NEXT_IC(); + END_OP(Gt); + } + if (sp[0].asValue().isNumber() && sp[1].asValue().isNumber()) { + double lhs = sp[1].asValue().toNumber(); + double rhs = sp[0].asValue().toNumber(); + bool result = lhs > rhs; + if (std::isnan(lhs) || std::isnan(rhs)) { + result = false; + } + POP(); + sp[0] = StackVal(BooleanValue(result)); + NEXT_IC(); + END_OP(Gt); + } + goto generic_cmp; + } + CASE(Ge) { + if (sp[0].asValue().isInt32() && sp[1].asValue().isInt32()) { + bool result = sp[1].asValue().toInt32() >= sp[0].asValue().toInt32(); + POP(); + sp[0] = StackVal(BooleanValue(result)); + NEXT_IC(); + END_OP(Ge); + } + if (sp[0].asValue().isNumber() && sp[1].asValue().isNumber()) { + double lhs = sp[1].asValue().toNumber(); + double rhs = sp[0].asValue().toNumber(); + bool result = lhs >= rhs; + if (std::isnan(lhs) || std::isnan(rhs)) { + result = false; + } + POP(); + sp[0] = StackVal(BooleanValue(result)); + NEXT_IC(); + END_OP(Ge); + } + goto generic_cmp; + } + + CASE(StrictEq) + CASE(StrictNe) { + if (kHybridICs) { + bool result; + HandleValue lval = Stack::handle(sp + 1); + HandleValue rval = Stack::handle(sp); + if (sp[0].asValue().isString() && sp[1].asValue().isString()) { + PUSH_EXIT_FRAME(); + if (!js::StrictlyEqual(cx, lval, rval, &result)) { + goto error; + } + } else { + if (!js::StrictlyEqual(nullptr, lval, rval, &result)) { + goto error; + } + } + POP(); + sp[0] = StackVal( + BooleanValue((JSOp(*pc) == JSOp::StrictEq) ? result : !result)); + NEXT_IC(); + END_OP(StrictEq); + } else { + goto generic_cmp; + } + } + + generic_cmp: { + static_assert(JSOpLength_Eq == JSOpLength_Ne); + static_assert(JSOpLength_Eq == JSOpLength_StrictEq); + static_assert(JSOpLength_Eq == JSOpLength_StrictNe); + static_assert(JSOpLength_Eq == JSOpLength_Lt); + static_assert(JSOpLength_Eq == JSOpLength_Gt); + static_assert(JSOpLength_Eq == JSOpLength_Le); + static_assert(JSOpLength_Eq == JSOpLength_Ge); + IC_POP_ARG(1); + IC_POP_ARG(0); + INVOKE_IC(Compare); + IC_PUSH_RESULT(); + END_OP(Eq); + } + + CASE(Instanceof) { + IC_POP_ARG(1); + IC_POP_ARG(0); + INVOKE_IC(InstanceOf); + IC_PUSH_RESULT(); + END_OP(Instanceof); + } + + CASE(In) { + IC_POP_ARG(1); + IC_POP_ARG(0); + INVOKE_IC(In); + IC_PUSH_RESULT(); + END_OP(In); + } + + CASE(ToPropertyKey) { + IC_POP_ARG(0); + INVOKE_IC(ToPropertyKey); + IC_PUSH_RESULT(); + END_OP(ToPropertyKey); + } + + CASE(ToString) { + if (sp[0].asValue().isString()) { + END_OP(ToString); + } + { + ReservedRooted<Value> value0(&state.value0, POP().asValue()); + if (JSString* result = + ToStringSlow<NoGC>(frameMgr.cxForLocalUseOnly(), value0)) { + PUSH(StackVal(StringValue(result))); + } else { + { + PUSH_EXIT_FRAME(); + result = ToString<CanGC>(cx, value0); + if (!result) { + goto error; + } + } + PUSH(StackVal(StringValue(result))); + } + } + END_OP(ToString); + } + + CASE(IsNullOrUndefined) { + bool result = sp[0].asValue().isNull() || sp[0].asValue().isUndefined(); + PUSH(StackVal(BooleanValue(result))); + END_OP(IsNullOrUndefined); + } + + CASE(GlobalThis) { + PUSH(StackVal(ObjectValue(*frameMgr.cxForLocalUseOnly() + ->global() + ->lexicalEnvironment() + .thisObject()))); + END_OP(GlobalThis); + } + + CASE(NonSyntacticGlobalThis) { + { + ReservedRooted<JSObject*> obj0(&state.obj0, frame->environmentChain()); + ReservedRooted<Value> value0(&state.value0); + { + PUSH_EXIT_FRAME(); + js::GetNonSyntacticGlobalThis(cx, obj0, &value0); + } + PUSH(StackVal(value0)); + } + END_OP(NonSyntacticGlobalThis); + } + + CASE(NewTarget) { + PUSH(StackVal(frame->newTarget())); + END_OP(NewTarget); + } + + CASE(DynamicImport) { + { + ReservedRooted<Value> value0(&state.value0, + POP().asValue()); // options + ReservedRooted<Value> value1(&state.value1, + POP().asValue()); // specifier + JSObject* promise; + { + PUSH_EXIT_FRAME(); + promise = StartDynamicModuleImport(cx, script, value1, value0); + if (!promise) { + goto error; + } + } + PUSH(StackVal(ObjectValue(*promise))); + } + END_OP(DynamicImport); + } + + CASE(ImportMeta) { + JSObject* metaObject; + { + PUSH_EXIT_FRAME(); + metaObject = ImportMetaOperation(cx, script); + if (!metaObject) { + goto error; + } + } + PUSH(StackVal(ObjectValue(*metaObject))); + END_OP(ImportMeta); + } + + CASE(NewInit) { + if (kHybridICs) { + JSObject* obj; + { + PUSH_EXIT_FRAME(); + obj = NewObjectOperation(cx, script, pc); + if (!obj) { + goto error; + } + } + PUSH(StackVal(ObjectValue(*obj))); + NEXT_IC(); + END_OP(NewInit); + } else { + INVOKE_IC(NewObject); + IC_PUSH_RESULT(); + END_OP(NewInit); + } + } + CASE(NewObject) { + if (kHybridICs) { + JSObject* obj; + { + PUSH_EXIT_FRAME(); + obj = NewObjectOperation(cx, script, pc); + if (!obj) { + goto error; + } + } + PUSH(StackVal(ObjectValue(*obj))); + NEXT_IC(); + END_OP(NewObject); + } else { + INVOKE_IC(NewObject); + IC_PUSH_RESULT(); + END_OP(NewObject); + } + } + CASE(Object) { + PUSH(StackVal(ObjectValue(*script->getObject(pc)))); + END_OP(Object); + } + CASE(ObjWithProto) { + { + ReservedRooted<Value> value0(&state.value0, sp[0].asValue()); + JSObject* obj; + { + PUSH_EXIT_FRAME(); + obj = ObjectWithProtoOperation(cx, value0); + if (!obj) { + goto error; + } + } + sp[0] = StackVal(ObjectValue(*obj)); + } + END_OP(ObjWithProto); + } + + CASE(InitElem) + CASE(InitHiddenElem) + CASE(InitLockedElem) + CASE(InitElemInc) + CASE(SetElem) + CASE(StrictSetElem) { + static_assert(JSOpLength_InitElem == JSOpLength_InitHiddenElem); + static_assert(JSOpLength_InitElem == JSOpLength_InitLockedElem); + static_assert(JSOpLength_InitElem == JSOpLength_InitElemInc); + static_assert(JSOpLength_InitElem == JSOpLength_SetElem); + static_assert(JSOpLength_InitElem == JSOpLength_StrictSetElem); + StackVal val = sp[0]; + IC_POP_ARG(2); + IC_POP_ARG(1); + IC_SET_ARG_FROM_STACK(0, 0); + if (JSOp(*pc) == JSOp::SetElem || JSOp(*pc) == JSOp::StrictSetElem) { + sp[0] = val; + } + INVOKE_IC(SetElem); + if (JSOp(*pc) == JSOp::InitElemInc) { + PUSH(StackVal( + Int32Value(Value::fromRawBits(icregs.icVals[1]).toInt32() + 1))); + } + END_OP(InitElem); + } + + CASE(InitPropGetter) + CASE(InitHiddenPropGetter) + CASE(InitPropSetter) + CASE(InitHiddenPropSetter) { + static_assert(JSOpLength_InitPropGetter == + JSOpLength_InitHiddenPropGetter); + static_assert(JSOpLength_InitPropGetter == JSOpLength_InitPropSetter); + static_assert(JSOpLength_InitPropGetter == + JSOpLength_InitHiddenPropSetter); + { + ReservedRooted<JSObject*> obj1(&state.obj1, + &POP().asValue().toObject()); // val + ReservedRooted<JSObject*> obj0( + &state.obj0, &sp[0].asValue().toObject()); // obj; leave on stack + ReservedRooted<PropertyName*> name0(&state.name0, script->getName(pc)); + { + PUSH_EXIT_FRAME(); + if (!InitPropGetterSetterOperation(cx, pc, obj0, name0, obj1)) { + goto error; + } + } + } + END_OP(InitPropGetter); + } + + CASE(InitElemGetter) + CASE(InitHiddenElemGetter) + CASE(InitElemSetter) + CASE(InitHiddenElemSetter) { + static_assert(JSOpLength_InitElemGetter == + JSOpLength_InitHiddenElemGetter); + static_assert(JSOpLength_InitElemGetter == JSOpLength_InitElemSetter); + static_assert(JSOpLength_InitElemGetter == + JSOpLength_InitHiddenElemSetter); + { + ReservedRooted<JSObject*> obj1(&state.obj1, + &POP().asValue().toObject()); // val + ReservedRooted<Value> value0(&state.value0, POP().asValue()); // idval + ReservedRooted<JSObject*> obj0( + &state.obj0, &sp[0].asValue().toObject()); // obj; leave on stack + { + PUSH_EXIT_FRAME(); + if (!InitElemGetterSetterOperation(cx, pc, obj0, value0, obj1)) { + goto error; + } + } + } + END_OP(InitElemGetter); + } + + CASE(GetProp) + CASE(GetBoundName) { + static_assert(JSOpLength_GetProp == JSOpLength_GetBoundName); + IC_POP_ARG(0); + INVOKE_IC(GetProp); + IC_PUSH_RESULT(); + END_OP(GetProp); + } + CASE(GetPropSuper) { + IC_POP_ARG(0); + IC_POP_ARG(1); + INVOKE_IC(GetPropSuper); + IC_PUSH_RESULT(); + END_OP(GetPropSuper); + } + + CASE(GetElem) { + HandleValue lhs = Stack::handle(&sp[1]); + HandleValue rhs = Stack::handle(&sp[0]); + uint32_t index; + if (IsDefinitelyIndex(rhs, &index)) { + if (lhs.isString()) { + JSString* str = lhs.toString(); + if (index < str->length() && str->isLinear()) { + JSLinearString* linear = &str->asLinear(); + char16_t c = linear->latin1OrTwoByteChar(index); + StaticStrings& sstr = frameMgr.cxForLocalUseOnly()->staticStrings(); + if (sstr.hasUnit(c)) { + sp[1] = StackVal(StringValue(sstr.getUnit(c))); + POP(); + NEXT_IC(); + END_OP(GetElem); + } + } + } + if (lhs.isObject()) { + JSObject* obj = &lhs.toObject(); + Value ret; + if (GetElementNoGC(frameMgr.cxForLocalUseOnly(), obj, lhs, index, + &ret)) { + sp[1] = StackVal(ret); + POP(); + NEXT_IC(); + END_OP(GetElem); + } + } + } + + IC_POP_ARG(1); + IC_POP_ARG(0); + INVOKE_IC(GetElem); + IC_PUSH_RESULT(); + END_OP(GetElem); + } + + CASE(GetElemSuper) { + // N.B.: second and third args are out of order! See the saga at + // https://bugzilla.mozilla.org/show_bug.cgi?id=1709328; this is + // an echo of that issue. + IC_POP_ARG(1); + IC_POP_ARG(2); + IC_POP_ARG(0); + INVOKE_IC(GetElemSuper); + IC_PUSH_RESULT(); + END_OP(GetElemSuper); + } + + CASE(DelProp) { + { + ReservedRooted<Value> value0(&state.value0, POP().asValue()); + ReservedRooted<PropertyName*> name0(&state.name0, script->getName(pc)); + bool res = false; + { + PUSH_EXIT_FRAME(); + if (!DelPropOperation<false>(cx, value0, name0, &res)) { + goto error; + } + } + PUSH(StackVal(BooleanValue(res))); + } + END_OP(DelProp); + } + CASE(StrictDelProp) { + { + ReservedRooted<Value> value0(&state.value0, POP().asValue()); + ReservedRooted<PropertyName*> name0(&state.name0, script->getName(pc)); + bool res = false; + { + PUSH_EXIT_FRAME(); + if (!DelPropOperation<true>(cx, value0, name0, &res)) { + goto error; + } + } + PUSH(StackVal(BooleanValue(res))); + } + END_OP(StrictDelProp); + } + CASE(DelElem) { + { + ReservedRooted<Value> value1(&state.value1, POP().asValue()); + ReservedRooted<Value> value0(&state.value0, POP().asValue()); + bool res = false; + { + PUSH_EXIT_FRAME(); + if (!DelElemOperation<false>(cx, value0, value1, &res)) { + goto error; + } + } + PUSH(StackVal(BooleanValue(res))); + } + END_OP(DelElem); + } + CASE(StrictDelElem) { + { + ReservedRooted<Value> value1(&state.value1, POP().asValue()); + ReservedRooted<Value> value0(&state.value0, POP().asValue()); + bool res = false; + { + PUSH_EXIT_FRAME(); + if (!DelElemOperation<true>(cx, value0, value1, &res)) { + goto error; + } + } + PUSH(StackVal(BooleanValue(res))); + } + END_OP(StrictDelElem); + } + + CASE(HasOwn) { + IC_POP_ARG(1); + IC_POP_ARG(0); + INVOKE_IC(HasOwn); + IC_PUSH_RESULT(); + END_OP(HasOwn); + } + + CASE(CheckPrivateField) { + IC_SET_ARG_FROM_STACK(1, 0); + IC_SET_ARG_FROM_STACK(0, 1); + INVOKE_IC(CheckPrivateField); + IC_PUSH_RESULT(); + END_OP(CheckPrivateField); + } + + CASE(NewPrivateName) { + { + ReservedRooted<JSAtom*> atom0(&state.atom0, script->getAtom(pc)); + JS::Symbol* symbol; + { + PUSH_EXIT_FRAME(); + symbol = NewPrivateName(cx, atom0); + if (!symbol) { + goto error; + } + } + PUSH(StackVal(SymbolValue(symbol))); + } + END_OP(NewPrivateName); + } + + CASE(SuperBase) { + JSFunction& superEnvFunc = POP().asValue().toObject().as<JSFunction>(); + MOZ_ASSERT(superEnvFunc.allowSuperProperty()); + MOZ_ASSERT(superEnvFunc.baseScript()->needsHomeObject()); + const Value& homeObjVal = superEnvFunc.getExtendedSlot( + FunctionExtended::METHOD_HOMEOBJECT_SLOT); + + JSObject* homeObj = &homeObjVal.toObject(); + JSObject* superBase = HomeObjectSuperBase(homeObj); + + PUSH(StackVal(ObjectOrNullValue(superBase))); + END_OP(SuperBase); + } + + CASE(SetPropSuper) + CASE(StrictSetPropSuper) { + // stack signature: receiver, lval, rval => rval + static_assert(JSOpLength_SetPropSuper == JSOpLength_StrictSetPropSuper); + bool strict = JSOp(*pc) == JSOp::StrictSetPropSuper; + { + ReservedRooted<Value> value2(&state.value2, POP().asValue()); // rval + ReservedRooted<Value> value1(&state.value1, POP().asValue()); // lval + ReservedRooted<Value> value0(&state.value0, + POP().asValue()); // recevier + ReservedRooted<PropertyName*> name0(&state.name0, script->getName(pc)); + { + PUSH_EXIT_FRAME(); + // SetPropertySuper(cx, lval, receiver, name, rval, strict) + // (N.B.: lval and receiver are transposed!) + if (!SetPropertySuper(cx, value1, value0, name0, value2, strict)) { + goto error; + } + } + PUSH(StackVal(value2)); + } + END_OP(SetPropSuper); + } + + CASE(SetElemSuper) + CASE(StrictSetElemSuper) { + // stack signature: receiver, key, lval, rval => rval + static_assert(JSOpLength_SetElemSuper == JSOpLength_StrictSetElemSuper); + bool strict = JSOp(*pc) == JSOp::StrictSetElemSuper; + { + ReservedRooted<Value> value3(&state.value3, POP().asValue()); // rval + ReservedRooted<Value> value2(&state.value2, POP().asValue()); // lval + ReservedRooted<Value> value1(&state.value1, POP().asValue()); // index + ReservedRooted<Value> value0(&state.value0, + POP().asValue()); // receiver + { + PUSH_EXIT_FRAME(); + // SetElementSuper(cx, lval, receiver, index, rval, strict) + // (N.B.: lval, receiver and index are rotated!) + if (!SetElementSuper(cx, value2, value0, value1, value3, strict)) { + goto error; + } + } + PUSH(StackVal(value3)); // value + } + END_OP(SetElemSuper); + } + + CASE(Iter) { + IC_POP_ARG(0); + INVOKE_IC(GetIterator); + IC_PUSH_RESULT(); + END_OP(Iter); + } + + CASE(MoreIter) { + // iter => iter, name + Value v = IteratorMore(&sp[0].asValue().toObject()); + PUSH(StackVal(v)); + END_OP(MoreIter); + } + + CASE(IsNoIter) { + // iter => iter, bool + bool result = sp[0].asValue().isMagic(JS_NO_ITER_VALUE); + PUSH(StackVal(BooleanValue(result))); + END_OP(IsNoIter); + } + + CASE(EndIter) { + // iter, interval => + POP(); + CloseIterator(&POP().asValue().toObject()); + END_OP(EndIter); + } + + CASE(CloseIter) { + IC_SET_OBJ_ARG(0, &POP().asValue().toObject()); + INVOKE_IC(CloseIter); + END_OP(CloseIter); + } + + CASE(CheckIsObj) { + if (!sp[0].asValue().isObject()) { + PUSH_EXIT_FRAME(); + MOZ_ALWAYS_FALSE( + js::ThrowCheckIsObject(cx, js::CheckIsObjectKind(GET_UINT8(pc)))); + /* abandon frame; error handler will re-establish sp */ + goto error; + } + END_OP(CheckIsObj); + } + + CASE(CheckObjCoercible) { + { + ReservedRooted<Value> value0(&state.value0, sp[0].asValue()); + if (value0.isNullOrUndefined()) { + PUSH_EXIT_FRAME(); + MOZ_ALWAYS_FALSE(ThrowObjectCoercible(cx, value0)); + /* abandon frame; error handler will re-establish sp */ + goto error; + } + } + END_OP(CheckObjCoercible); + } + + CASE(ToAsyncIter) { + // iter, next => asynciter + { + ReservedRooted<Value> value0(&state.value0, POP().asValue()); // next + ReservedRooted<JSObject*> obj0(&state.obj0, + &POP().asValue().toObject()); // iter + JSObject* result; + { + PUSH_EXIT_FRAME(); + result = CreateAsyncFromSyncIterator(cx, obj0, value0); + if (!result) { + goto error; + } + } + PUSH(StackVal(ObjectValue(*result))); + } + END_OP(ToAsyncIter); + } + + CASE(MutateProto) { + // obj, protoVal => obj + { + ReservedRooted<Value> value0(&state.value0, POP().asValue()); + ReservedRooted<JSObject*> obj0(&state.obj0, + &sp[0].asValue().toObject()); + { + PUSH_EXIT_FRAME(); + if (!MutatePrototype(cx, obj0.as<PlainObject>(), value0)) { + goto error; + } + } + } + END_OP(MutateProto); + } + + CASE(NewArray) { + if (kHybridICs) { + ArrayObject* obj; + { + PUSH_EXIT_FRAME(); + uint32_t length = GET_UINT32(pc); + obj = NewArrayOperation(cx, length); + if (!obj) { + goto error; + } + } + PUSH(StackVal(ObjectValue(*obj))); + NEXT_IC(); + END_OP(NewArray); + } else { + INVOKE_IC(NewArray); + IC_PUSH_RESULT(); + END_OP(NewArray); + } + } + + CASE(InitElemArray) { + // array, val => array + { + ReservedRooted<Value> value0(&state.value0, POP().asValue()); + ReservedRooted<JSObject*> obj0(&state.obj0, + &sp[0].asValue().toObject()); + { + PUSH_EXIT_FRAME(); + InitElemArrayOperation(cx, pc, obj0.as<ArrayObject>(), value0); + } + } + END_OP(InitElemArray); + } + + CASE(Hole) { + PUSH(StackVal(MagicValue(JS_ELEMENTS_HOLE))); + END_OP(Hole); + } + + CASE(RegExp) { + JSObject* obj; + { + PUSH_EXIT_FRAME(); + ReservedRooted<JSObject*> obj0(&state.obj0, script->getRegExp(pc)); + obj = CloneRegExpObject(cx, obj0.as<RegExpObject>()); + if (!obj) { + goto error; + } + } + PUSH(StackVal(ObjectValue(*obj))); + END_OP(RegExp); + } + + CASE(Lambda) { + { + ReservedRooted<JSFunction*> fun0(&state.fun0, script->getFunction(pc)); + ReservedRooted<JSObject*> obj0(&state.obj0, frame->environmentChain()); + JSObject* res; + { + PUSH_EXIT_FRAME(); + res = js::Lambda(cx, fun0, obj0); + if (!res) { + goto error; + } + } + PUSH(StackVal(ObjectValue(*res))); + } + END_OP(Lambda); + } + + CASE(SetFunName) { + // fun, name => fun + { + ReservedRooted<Value> value0(&state.value0, POP().asValue()); // name + ReservedRooted<JSFunction*> fun0( + &state.fun0, &sp[0].asValue().toObject().as<JSFunction>()); + FunctionPrefixKind prefixKind = FunctionPrefixKind(GET_UINT8(pc)); + { + PUSH_EXIT_FRAME(); + if (!SetFunctionName(cx, fun0, value0, prefixKind)) { + goto error; + } + } + } + END_OP(SetFunName); + } + + CASE(InitHomeObject) { + // fun, homeObject => fun + { + ReservedRooted<JSObject*> obj0( + &state.obj0, &POP().asValue().toObject()); // homeObject + ReservedRooted<JSFunction*> fun0( + &state.fun0, &sp[0].asValue().toObject().as<JSFunction>()); + MOZ_ASSERT(fun0->allowSuperProperty()); + MOZ_ASSERT(obj0->is<PlainObject>() || obj0->is<JSFunction>()); + fun0->setExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT, + ObjectValue(*obj0)); + } + END_OP(InitHomeObject); + } + + CASE(CheckClassHeritage) { + { + ReservedRooted<Value> value0(&state.value0, sp[0].asValue()); + { + PUSH_EXIT_FRAME(); + if (!CheckClassHeritageOperation(cx, value0)) { + goto error; + } + } + } + END_OP(CheckClassHeritage); + } + + CASE(FunWithProto) { + // proto => obj + { + ReservedRooted<JSObject*> obj0(&state.obj0, + &POP().asValue().toObject()); // proto + ReservedRooted<JSObject*> obj1(&state.obj1, frame->environmentChain()); + ReservedRooted<JSFunction*> fun0(&state.fun0, script->getFunction(pc)); + JSObject* obj; + { + PUSH_EXIT_FRAME(); + obj = FunWithProtoOperation(cx, fun0, obj1, obj0); + if (!obj) { + goto error; + } + } + PUSH(StackVal(ObjectValue(*obj))); + } + END_OP(FunWithProto); + } + + CASE(BuiltinObject) { + auto kind = BuiltinObjectKind(GET_UINT8(pc)); + JSObject* builtin; + { + PUSH_EXIT_FRAME(); + builtin = BuiltinObjectOperation(cx, kind); + if (!builtin) { + goto error; + } + } + PUSH(StackVal(ObjectValue(*builtin))); + END_OP(BuiltinObject); + } + + CASE(Call) + CASE(CallIgnoresRv) + CASE(CallContent) + CASE(CallIter) + CASE(CallContentIter) + CASE(Eval) + CASE(StrictEval) + CASE(SuperCall) + CASE(New) + CASE(NewContent) { + static_assert(JSOpLength_Call == JSOpLength_CallIgnoresRv); + static_assert(JSOpLength_Call == JSOpLength_CallContent); + static_assert(JSOpLength_Call == JSOpLength_CallIter); + static_assert(JSOpLength_Call == JSOpLength_CallContentIter); + static_assert(JSOpLength_Call == JSOpLength_Eval); + static_assert(JSOpLength_Call == JSOpLength_StrictEval); + static_assert(JSOpLength_Call == JSOpLength_SuperCall); + static_assert(JSOpLength_Call == JSOpLength_New); + static_assert(JSOpLength_Call == JSOpLength_NewContent); + JSOp op = JSOp(*pc); + bool constructing = + (op == JSOp::New || op == JSOp::NewContent || op == JSOp::SuperCall); + uint32_t argc = GET_ARGC(pc); + do { + { + // CallArgsFromSp would be called with + // - numValues = argc + 2 + constructing + // - stackSlots = argc + constructing + // - sp = vp + numValues + // CallArgs::create then gets + // - argc_ = stackSlots - constructing = argc + // - argv_ = sp - stackSlots = vp + 2 + // our arguments are in reverse order compared to what CallArgs + // expects so we should subtract any array subscripts from (sp + + // stackSlots - 1) + StackVal* firstArg = sp + argc + constructing - 1; + + // callee is argv_[-2] -> sp + argc + constructing + 1 + // this is argv_[-1] -> sp + argc + constructing + // newTarget is argv_[argc_] -> sp + constructing - 1 + // but this/newTarget are only used when constructing is 1 so we can + // simplify this is argv_[-1] -> sp + argc + 1 newTarget is + // argv_[argc_] -> sp + + HandleValue callee = Stack::handle(firstArg + 2); + if (!callee.isObject() || !callee.toObject().is<JSFunction>()) { + TRACE_PRINTF("missed fastpath: not a function\n"); + break; + } + ReservedRooted<JSFunction*> func(&state.fun0, + &callee.toObject().as<JSFunction>()); + if (!func->hasBaseScript() || !func->isInterpreted()) { + TRACE_PRINTF("missed fastpath: not an interpreted script\n"); + break; + } + if (!constructing && func->isClassConstructor()) { + TRACE_PRINTF("missed fastpath: constructor called without `new`\n"); + break; + } + if (!func->baseScript()->hasBytecode()) { + TRACE_PRINTF("missed fastpath: no bytecode\n"); + break; + } + ReservedRooted<JSScript*> calleeScript( + &state.script0, func->baseScript()->asJSScript()); + if (!calleeScript->hasJitScript()) { + TRACE_PRINTF("missed fastpath: no jit-script\n"); + break; + } + if (frameMgr.cxForLocalUseOnly()->realm() != calleeScript->realm()) { + TRACE_PRINTF("missed fastpath: mismatched realm\n"); + break; + } + if (argc < func->nargs()) { + TRACE_PRINTF("missed fastpath: not enough arguments\n"); + break; + } + + // Fast-path: function, interpreted, has JitScript, same realm, no + // argument underflow. + + // Include newTarget in the args if it exists; exclude callee + uint32_t totalArgs = argc + 1 + constructing; + StackVal* origArgs = sp; + + TRACE_PRINTF( + "Call fastpath: argc = %d origArgs = %p callee = %" PRIx64 "\n", + argc, origArgs, callee.get().asRawBits()); + + if (!stack.check(sp, sizeof(StackVal) * (totalArgs + 3))) { + TRACE_PRINTF("missed fastpath: would cause stack overrun\n"); + break; + } + + if (constructing) { + MutableHandleValue thisv = Stack::handleMut(firstArg + 1); + if (!thisv.isObject()) { + HandleValue newTarget = Stack::handle(firstArg - argc); + ReservedRooted<JSObject*> obj0(&state.obj0, + &newTarget.toObject()); + + PUSH_EXIT_FRAME(); + // CreateThis might discard the JitScript but we're counting on it + // continuing to exist while we evaluate the fastpath. + AutoKeepJitScripts keepJitScript(cx); + if (!CreateThis(cx, func, obj0, GenericObject, thisv)) { + goto error; + } + + TRACE_PRINTF("created %" PRIx64 "\n", thisv.get().asRawBits()); + } + } + + // 0. Save current PC in current frame, so we can retrieve + // it later. + frame->interpreterPC() = pc; + + // 1. Push a baseline stub frame. Don't use the frame manager + // -- we don't want the frame to be auto-freed when we leave + // this scope, and we don't want to shadow `sp`. + StackVal* exitFP = stack.pushExitFrame(sp, frame); + MOZ_ASSERT(exitFP); // safety: stack margin. + sp = exitFP; + TRACE_PRINTF("exit frame at %p\n", exitFP); + + // 2. Modify exit code to nullptr (this is where ICStubReg is + // normally saved; the tracing code can skip if null). + PUSHNATIVE(StackValNative(nullptr)); + + // 3. Push args in proper order (they are reversed in our + // downward-growth stack compared to what the calling + // convention expects). + for (uint32_t i = 0; i < totalArgs; i++) { + PUSH(origArgs[i]); + } + + // 4. Push inter-frame content: callee token, descriptor for + // above. + PUSHNATIVE(StackValNative(CalleeToToken(func, constructing))); + PUSHNATIVE(StackValNative( + MakeFrameDescriptorForJitCall(FrameType::BaselineStub, argc))); + + // 5. Push fake return address, set script, push baseline frame. + PUSHNATIVE(StackValNative(nullptr)); + script.set(calleeScript); + BaselineFrame* newFrame = + stack.pushFrame(sp, frameMgr.cxForLocalUseOnly(), + /* envChain = */ func->environment()); + MOZ_ASSERT(newFrame); // safety: stack margin. + TRACE_PRINTF("callee frame at %p\n", newFrame); + frame = newFrame; + frameMgr.switchToFrame(frame); + // 6. Set up PC and SP for callee. + sp = reinterpret_cast<StackVal*>(frame); + pc = calleeScript->code(); + // 7. Check callee stack space for max stack depth. + if (!stack.check(sp, sizeof(StackVal) * calleeScript->nslots())) { + PUSH_EXIT_FRAME(); + ReportOverRecursed(frameMgr.cxForLocalUseOnly()); + goto error; + } + // 8. Push local slots, and set return value to `undefined` by + // default. + uint32_t nfixed = calleeScript->nfixed(); + for (uint32_t i = 0; i < nfixed; i++) { + PUSH(StackVal(UndefinedValue())); + } + ret->setUndefined(); + // 9. Initialize environment objects. + if (func->needsFunctionEnvironmentObjects()) { + PUSH_EXIT_FRAME(); + if (!js::InitFunctionEnvironmentObjects(cx, frame)) { + goto error; + } + } + // 10. Set debug flag, if appropriate. + if (script->isDebuggee()) { + TRACE_PRINTF("Script is debuggee\n"); + frame->setIsDebuggee(); + + PUSH_EXIT_FRAME(); + if (!DebugPrologue(cx, frame)) { + goto error; + } + } + // 11. Check for interrupts. +#ifndef __wasi__ + if (frameMgr.cxForLocalUseOnly()->hasAnyPendingInterrupt()) { + PUSH_EXIT_FRAME(); + if (!InterruptCheck(cx)) { + goto error; + } + } +#endif + // 12. Initialize coverage tables, if needed. + if (!script->hasScriptCounts()) { + if (frameMgr.cxForLocalUseOnly() + ->realm() + ->collectCoverageForDebug()) { + PUSH_EXIT_FRAME(); + if (!script->initScriptCounts(cx)) { + goto error; + } + } + } + COUNT_COVERAGE_MAIN(); + } + + // Everything is switched to callee context now -- dispatch! + DISPATCH(); + } while (0); + + // Slow path: use the IC! + icregs.icVals[0] = argc; + icregs.extraArgs = 2 + constructing; + icregs.spreadCall = false; + INVOKE_IC(Call); + POPN(argc + 2 + constructing); + PUSH(StackVal(Value::fromRawBits(icregs.icResult))); + END_OP(Call); + } + + CASE(SpreadCall) + CASE(SpreadEval) + CASE(StrictSpreadEval) { + static_assert(JSOpLength_SpreadCall == JSOpLength_SpreadEval); + static_assert(JSOpLength_SpreadCall == JSOpLength_StrictSpreadEval); + icregs.icVals[0] = 1; + icregs.extraArgs = 2; + icregs.spreadCall = true; + INVOKE_IC(Call); + POPN(3); + PUSH(StackVal(Value::fromRawBits(icregs.icResult))); + END_OP(SpreadCall); + } + + CASE(SpreadSuperCall) + CASE(SpreadNew) { + static_assert(JSOpLength_SpreadSuperCall == JSOpLength_SpreadNew); + icregs.icVals[0] = 1; + icregs.extraArgs = 3; + icregs.spreadCall = true; + INVOKE_IC(Call); + POPN(4); + PUSH(StackVal(Value::fromRawBits(icregs.icResult))); + END_OP(SpreadSuperCall); + } + + CASE(OptimizeSpreadCall) { + IC_POP_ARG(0); + INVOKE_IC(OptimizeSpreadCall); + IC_PUSH_RESULT(); + END_OP(OptimizeSpreadCall); + } + + CASE(OptimizeGetIterator) { + IC_POP_ARG(0); + INVOKE_IC(OptimizeGetIterator); + IC_PUSH_RESULT(); + END_OP(OptimizeGetIterator); + } + + CASE(ImplicitThis) { + { + ReservedRooted<JSObject*> obj0(&state.obj0, frame->environmentChain()); + ReservedRooted<PropertyName*> name0(&state.name0, script->getName(pc)); + PUSH_EXIT_FRAME(); + if (!ImplicitThisOperation(cx, obj0, name0, &state.res)) { + goto error; + } + } + PUSH(StackVal(state.res)); + state.res.setUndefined(); + END_OP(ImplicitThis); + } + + CASE(CallSiteObj) { + JSObject* cso = script->getObject(pc); + MOZ_ASSERT(!cso->as<ArrayObject>().isExtensible()); + MOZ_ASSERT(cso->as<ArrayObject>().containsPure( + frameMgr.cxForLocalUseOnly()->names().raw)); + PUSH(StackVal(ObjectValue(*cso))); + END_OP(CallSiteObj); + } + + CASE(IsConstructing) { + PUSH(StackVal(MagicValue(JS_IS_CONSTRUCTING))); + END_OP(IsConstructing); + } + + CASE(SuperFun) { + JSObject* superEnvFunc = &POP().asValue().toObject(); + JSObject* superFun = SuperFunOperation(superEnvFunc); + PUSH(StackVal(ObjectOrNullValue(superFun))); + END_OP(SuperFun); + } + + CASE(CheckThis) { + if (sp[0].asValue().isMagic(JS_UNINITIALIZED_LEXICAL)) { + PUSH_EXIT_FRAME(); + MOZ_ALWAYS_FALSE(ThrowUninitializedThis(cx)); + goto error; + } + END_OP(CheckThis); + } + + CASE(CheckThisReinit) { + if (!sp[0].asValue().isMagic(JS_UNINITIALIZED_LEXICAL)) { + PUSH_EXIT_FRAME(); + MOZ_ALWAYS_FALSE(ThrowInitializedThis(cx)); + goto error; + } + END_OP(CheckThisReinit); + } + + CASE(Generator) { + JSObject* generator; + { + PUSH_EXIT_FRAME(); + generator = CreateGeneratorFromFrame(cx, frame); + if (!generator) { + goto error; + } + } + PUSH(StackVal(ObjectValue(*generator))); + END_OP(Generator); + } + + CASE(InitialYield) { + // gen => rval, gen, resumeKind + ReservedRooted<JSObject*> obj0(&state.obj0, &sp[0].asValue().toObject()); + uint32_t frameSize = stack.frameSize(sp, frame); + { + PUSH_EXIT_FRAME(); + if (!NormalSuspend(cx, obj0, frame, frameSize, pc)) { + goto error; + } + } + frame->setReturnValue(sp[0].asValue()); + goto do_return; + } + + CASE(Await) + CASE(Yield) { + // rval1, gen => rval2, gen, resumeKind + ReservedRooted<JSObject*> obj0(&state.obj0, &POP().asValue().toObject()); + uint32_t frameSize = stack.frameSize(sp, frame); + { + PUSH_EXIT_FRAME(); + if (!NormalSuspend(cx, obj0, frame, frameSize, pc)) { + goto error; + } + } + frame->setReturnValue(sp[0].asValue()); + goto do_return; + } + + CASE(FinalYieldRval) { + // gen => + ReservedRooted<JSObject*> obj0(&state.obj0, &POP().asValue().toObject()); + { + PUSH_EXIT_FRAME(); + if (!FinalSuspend(cx, obj0, pc)) { + goto error; + } + } + goto do_return; + } + + CASE(IsGenClosing) { + bool result = sp[0].asValue() == MagicValue(JS_GENERATOR_CLOSING); + PUSH(StackVal(BooleanValue(result))); + END_OP(IsGenClosing); + } + + CASE(AsyncAwait) { + // value, gen => promise + JSObject* promise; + { + ReservedRooted<JSObject*> obj0(&state.obj0, + &POP().asValue().toObject()); // gen + ReservedRooted<Value> value0(&state.value0, POP().asValue()); // value + PUSH_EXIT_FRAME(); + promise = AsyncFunctionAwait( + cx, obj0.as<AsyncFunctionGeneratorObject>(), value0); + if (!promise) { + goto error; + } + } + PUSH(StackVal(ObjectValue(*promise))); + END_OP(AsyncAwait); + } + + CASE(AsyncResolve) { + // value, gen => promise + JSObject* promise; + { + ReservedRooted<JSObject*> obj0(&state.obj0, + &POP().asValue().toObject()); // gen + ReservedRooted<Value> value0(&state.value0, + POP().asValue()); // value + PUSH_EXIT_FRAME(); + promise = AsyncFunctionResolve( + cx, obj0.as<AsyncFunctionGeneratorObject>(), value0); + if (!promise) { + goto error; + } + } + PUSH(StackVal(ObjectValue(*promise))); + END_OP(AsyncResolve); + } + + CASE(AsyncReject) { + // reason, gen => promise + JSObject* promise; + { + ReservedRooted<JSObject*> obj0(&state.obj0, + &POP().asValue().toObject()); // gen + ReservedRooted<Value> value0(&state.value0, + POP().asValue()); // stack + ReservedRooted<Value> value1(&state.value1, + POP().asValue()); // reason + PUSH_EXIT_FRAME(); + promise = AsyncFunctionReject( + cx, obj0.as<AsyncFunctionGeneratorObject>(), value1, value0); + if (!promise) { + goto error; + } + } + PUSH(StackVal(ObjectValue(*promise))); + END_OP(AsyncReject); + } + + CASE(CanSkipAwait) { + // value => value, can_skip + bool result = false; + { + ReservedRooted<Value> value0(&state.value0, sp[0].asValue()); + PUSH_EXIT_FRAME(); + if (!CanSkipAwait(cx, value0, &result)) { + goto error; + } + } + PUSH(StackVal(BooleanValue(result))); + END_OP(CanSkipAwait); + } + + CASE(MaybeExtractAwaitValue) { + // value, can_skip => value_or_resolved, can_skip + { + Value can_skip = POP().asValue(); + ReservedRooted<Value> value0(&state.value0, POP().asValue()); // value + if (can_skip.toBoolean()) { + PUSH_EXIT_FRAME(); + if (!ExtractAwaitValue(cx, value0, &value0)) { + goto error; + } + } + PUSH(StackVal(value0)); + PUSH(StackVal(can_skip)); + } + END_OP(MaybeExtractAwaitValue); + } + + CASE(ResumeKind) { + GeneratorResumeKind resumeKind = ResumeKindFromPC(pc); + PUSH(StackVal(Int32Value(int32_t(resumeKind)))); + END_OP(ResumeKind); + } + + CASE(CheckResumeKind) { + // rval, gen, resumeKind => rval + { + GeneratorResumeKind resumeKind = + IntToResumeKind(POP().asValue().toInt32()); + ReservedRooted<JSObject*> obj0(&state.obj0, + &POP().asValue().toObject()); // gen + ReservedRooted<Value> value0(&state.value0, sp[0].asValue()); // rval + if (resumeKind != GeneratorResumeKind::Next) { + PUSH_EXIT_FRAME(); + MOZ_ALWAYS_FALSE(GeneratorThrowOrReturn( + cx, frame, obj0.as<AbstractGeneratorObject>(), value0, + resumeKind)); + goto error; + } + } + END_OP(CheckResumeKind); + } + + CASE(Resume) { + Value gen = sp[2].asValue(); + Value* callerSP = reinterpret_cast<Value*>(sp); + { + ReservedRooted<Value> value0(&state.value0); + ReservedRooted<JSObject*> obj0(&state.obj0, &gen.toObject()); + { + PUSH_EXIT_FRAME(); + TRACE_PRINTF("Going to C++ interp for Resume\n"); + if (!InterpretResume(cx, obj0, callerSP, &value0)) { + goto error; + } + } + POPN(2); + sp[0] = StackVal(value0); + } + END_OP(Resume); + } + + CASE(JumpTarget) { + int32_t icIndex = GET_INT32(pc); + frame->interpreterICEntry() = frame->icScript()->icEntries() + icIndex; + COUNT_COVERAGE_PC(pc); + END_OP(JumpTarget); + } + CASE(LoopHead) { + int32_t icIndex = GET_INT32(pc); + frame->interpreterICEntry() = frame->icScript()->icEntries() + icIndex; +#ifndef __wasi__ + if (frameMgr.cxForLocalUseOnly()->hasAnyPendingInterrupt()) { + PUSH_EXIT_FRAME(); + if (!InterruptCheck(cx)) { + goto error; + } + } +#endif + COUNT_COVERAGE_PC(pc); + END_OP(LoopHead); + } + CASE(AfterYield) { + int32_t icIndex = GET_INT32(pc); + frame->interpreterICEntry() = frame->icScript()->icEntries() + icIndex; + if (script->isDebuggee()) { + TRACE_PRINTF("doing DebugAfterYield\n"); + PUSH_EXIT_FRAME(); + if (DebugAPI::hasAnyBreakpointsOrStepMode(script) && + !HandleDebugTrap(cx, frame, pc)) { + TRACE_PRINTF("HandleDebugTrap returned error\n"); + goto error; + } + if (!DebugAfterYield(cx, frame)) { + TRACE_PRINTF("DebugAfterYield returned error\n"); + goto error; + } + } + COUNT_COVERAGE_PC(pc); + END_OP(AfterYield); + } + + CASE(Goto) { + ADVANCE(GET_JUMP_OFFSET(pc)); + PREDICT_NEXT(JumpTarget); + PREDICT_NEXT(LoopHead); + DISPATCH(); + } + + CASE(Coalesce) { + if (!sp[0].asValue().isNullOrUndefined()) { + ADVANCE(GET_JUMP_OFFSET(pc)); + DISPATCH(); + } else { + END_OP(Coalesce); + } + } + + CASE(Case) { + bool cond = POP().asValue().toBoolean(); + if (cond) { + POP(); + ADVANCE(GET_JUMP_OFFSET(pc)); + DISPATCH(); + } else { + END_OP(Case); + } + } + + CASE(Default) { + POP(); + ADVANCE(GET_JUMP_OFFSET(pc)); + DISPATCH(); + } + + CASE(TableSwitch) { + int32_t len = GET_JUMP_OFFSET(pc); + int32_t low = GET_JUMP_OFFSET(pc + 1 * JUMP_OFFSET_LEN); + int32_t high = GET_JUMP_OFFSET(pc + 2 * JUMP_OFFSET_LEN); + Value v = POP().asValue(); + int32_t i = 0; + if (v.isInt32()) { + i = v.toInt32(); + } else if (!v.isDouble() || + !mozilla::NumberEqualsInt32(v.toDouble(), &i)) { + ADVANCE(len); + DISPATCH(); + } + + i = uint32_t(i) - uint32_t(low); + if ((uint32_t(i) < uint32_t(high - low + 1))) { + len = script->tableSwitchCaseOffset(pc, uint32_t(i)) - + script->pcToOffset(pc); + } + ADVANCE(len); + DISPATCH(); + } + + CASE(Return) { + frame->setReturnValue(POP().asValue()); + goto do_return; + } + + CASE(GetRval) { + PUSH(StackVal(frame->returnValue())); + END_OP(GetRval); + } + + CASE(SetRval) { + frame->setReturnValue(POP().asValue()); + END_OP(SetRval); + } + + do_return: + CASE(RetRval) { + bool ok = true; + if (frame->isDebuggee() && !from_unwind) { + TRACE_PRINTF("doing DebugEpilogueOnBaselineReturn\n"); + PUSH_EXIT_FRAME(); + ok = DebugEpilogueOnBaselineReturn(cx, frame, pc); + } + from_unwind = false; + + uint32_t argc = frame->numActualArgs(); + sp = stack.popFrame(); + + // If FP is higher than the entry frame now, return; otherwise, + // do an inline state update. + if (stack.fp > entryFrame) { + *ret = frame->returnValue(); + TRACE_PRINTF("ret = %" PRIx64 "\n", ret->asRawBits()); + return ok ? PBIResult::Ok : PBIResult::Error; + } else { + TRACE_PRINTF("Return fastpath\n"); + Value ret = frame->returnValue(); + TRACE_PRINTF("ret = %" PRIx64 "\n", ret.asRawBits()); + + // Pop exit frame as well. + sp = stack.popFrame(); + // Pop fake return address and descriptor. + POPNNATIVE(2); + + // Set PC, frame, and current script. + frame = reinterpret_cast<BaselineFrame*>( + reinterpret_cast<uintptr_t>(stack.fp) - BaselineFrame::Size()); + TRACE_PRINTF(" sp -> %p, fp -> %p, frame -> %p\n", sp, stack.fp, frame); + frameMgr.switchToFrame(frame); + pc = frame->interpreterPC(); + script.set(frame->script()); + + // Adjust caller's stack to complete the call op that PC still points to + // in that frame (pop args, push return value). + JSOp op = JSOp(*pc); + bool constructing = (op == JSOp::New || op == JSOp::NewContent || + op == JSOp::SuperCall); + // Fix-up return value; EnterJit would do this if we hadn't bypassed it. + if (constructing && ret.isPrimitive()) { + ret = sp[argc + constructing].asValue(); + TRACE_PRINTF("updated ret = %" PRIx64 "\n", ret.asRawBits()); + } + // Pop args -- this is 1 more than how many are pushed in the + // `totalArgs` count during the call fastpath because it includes + // the callee. + POPN(argc + 2 + constructing); + // Push return value. + PUSH(StackVal(ret)); + + if (!ok) { + goto error; + } + + // Advance past call instruction, and advance past IC. + NEXT_IC(); + ADVANCE(JSOpLength_Call); + + DISPATCH(); + } + } + + CASE(CheckReturn) { + Value thisval = POP().asValue(); + // inlined version of frame->checkReturn(thisval, result) + // (js/src/vm/Stack.cpp). + HandleValue retVal = frame->returnValue(); + if (retVal.isObject()) { + PUSH(StackVal(retVal)); + } else if (!retVal.isUndefined()) { + PUSH_EXIT_FRAME(); + MOZ_ALWAYS_FALSE(ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, + JSDVG_IGNORE_STACK, retVal, nullptr)); + goto error; + } else if (thisval.isMagic(JS_UNINITIALIZED_LEXICAL)) { + PUSH_EXIT_FRAME(); + MOZ_ALWAYS_FALSE(ThrowUninitializedThis(cx)); + goto error; + } else { + PUSH(StackVal(thisval)); + } + END_OP(CheckReturn); + } + + CASE(Throw) { + { + ReservedRooted<Value> value0(&state.value0, POP().asValue()); + PUSH_EXIT_FRAME(); + MOZ_ALWAYS_FALSE(ThrowOperation(cx, value0)); + goto error; + } + END_OP(Throw); + } + + CASE(ThrowWithStack) { + { + ReservedRooted<Value> value0(&state.value0, POP().asValue()); + ReservedRooted<Value> value1(&state.value1, POP().asValue()); + PUSH_EXIT_FRAME(); + MOZ_ALWAYS_FALSE(ThrowWithStackOperation(cx, value1, value0)); + goto error; + } + END_OP(ThrowWithStack); + } + + CASE(ThrowMsg) { + { + PUSH_EXIT_FRAME(); + MOZ_ALWAYS_FALSE(ThrowMsgOperation(cx, GET_UINT8(pc))); + goto error; + } + END_OP(ThrowMsg); + } + + CASE(ThrowSetConst) { + { + PUSH_EXIT_FRAME(); + ReportRuntimeLexicalError(cx, JSMSG_BAD_CONST_ASSIGN, script, pc); + goto error; + } + END_OP(ThrowSetConst); + } + + CASE(Try) + CASE(TryDestructuring) { + static_assert(JSOpLength_Try == JSOpLength_TryDestructuring); + END_OP(Try); + } + + CASE(Exception) { + { + PUSH_EXIT_FRAME(); + if (!GetAndClearException(cx, &state.res)) { + goto error; + } + } + PUSH(StackVal(state.res)); + state.res.setUndefined(); + END_OP(Exception); + } + + CASE(ExceptionAndStack) { + { + ReservedRooted<Value> value0(&state.value0); + { + PUSH_EXIT_FRAME(); + if (!cx.getCx()->getPendingExceptionStack(&value0)) { + goto error; + } + if (!GetAndClearException(cx, &state.res)) { + goto error; + } + } + PUSH(StackVal(state.res)); + PUSH(StackVal(value0)); + state.res.setUndefined(); + } + END_OP(ExceptionAndStack); + } + + CASE(Finally) { +#ifndef __wasi__ + if (frameMgr.cxForLocalUseOnly()->hasAnyPendingInterrupt()) { + PUSH_EXIT_FRAME(); + if (!InterruptCheck(cx)) { + goto error; + } + } +#endif + END_OP(Finally); + } + + CASE(Uninitialized) { + PUSH(StackVal(MagicValue(JS_UNINITIALIZED_LEXICAL))); + END_OP(Uninitialized); + } + CASE(InitLexical) { + uint32_t i = GET_LOCALNO(pc); + frame->unaliasedLocal(i) = sp[0].asValue(); + END_OP(InitLexical); + } + + CASE(InitAliasedLexical) { + EnvironmentCoordinate ec = EnvironmentCoordinate(pc); + EnvironmentObject& obj = getEnvironmentFromCoordinate(frame, ec); + obj.setAliasedBinding(ec, sp[0].asValue()); + END_OP(InitAliasedLexical); + } + CASE(CheckLexical) { + if (sp[0].asValue().isMagic(JS_UNINITIALIZED_LEXICAL)) { + PUSH_EXIT_FRAME(); + ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, script, pc); + goto error; + } + END_OP(CheckLexical); + } + CASE(CheckAliasedLexical) { + if (sp[0].asValue().isMagic(JS_UNINITIALIZED_LEXICAL)) { + PUSH_EXIT_FRAME(); + ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, script, pc); + goto error; + } + END_OP(CheckAliasedLexical); + } + + CASE(BindGName) { + IC_SET_OBJ_ARG( + 0, &frameMgr.cxForLocalUseOnly()->global()->lexicalEnvironment()); + INVOKE_IC(BindName); + IC_PUSH_RESULT(); + END_OP(BindGName); + } + CASE(BindName) { + IC_SET_OBJ_ARG(0, frame->environmentChain()); + INVOKE_IC(BindName); + IC_PUSH_RESULT(); + END_OP(BindName); + } + CASE(GetGName) { + IC_SET_OBJ_ARG( + 0, &frameMgr.cxForLocalUseOnly()->global()->lexicalEnvironment()); + INVOKE_IC(GetName); + IC_PUSH_RESULT(); + END_OP(GetGName); + } + CASE(GetName) { + IC_SET_OBJ_ARG(0, frame->environmentChain()); + INVOKE_IC(GetName); + IC_PUSH_RESULT(); + END_OP(GetName); + } + + CASE(GetArg) { + unsigned i = GET_ARGNO(pc); + if (script->argsObjAliasesFormals()) { + PUSH(StackVal(frame->argsObj().arg(i))); + } else { + PUSH(StackVal(frame->unaliasedFormal(i))); + } + END_OP(GetArg); + } + + CASE(GetFrameArg) { + uint32_t i = GET_ARGNO(pc); + PUSH(StackVal(frame->unaliasedFormal(i, DONT_CHECK_ALIASING))); + END_OP(GetFrameArg); + } + + CASE(GetLocal) { + uint32_t i = GET_LOCALNO(pc); + TRACE_PRINTF(" -> local: %d\n", int(i)); + PUSH(StackVal(frame->unaliasedLocal(i))); + END_OP(GetLocal); + } + + CASE(ArgumentsLength) { + PUSH(StackVal(Int32Value(frame->numActualArgs()))); + END_OP(ArgumentsLength); + } + + CASE(GetActualArg) { + MOZ_ASSERT(!script->needsArgsObj()); + uint32_t index = sp[0].asValue().toInt32(); + sp[0] = StackVal(frame->unaliasedActual(index)); + END_OP(GetActualArg); + } + + CASE(GetAliasedVar) + CASE(GetAliasedDebugVar) { + static_assert(JSOpLength_GetAliasedVar == JSOpLength_GetAliasedDebugVar); + EnvironmentCoordinate ec = EnvironmentCoordinate(pc); + EnvironmentObject& obj = getEnvironmentFromCoordinate(frame, ec); + PUSH(StackVal(obj.aliasedBinding(ec))); + END_OP(GetAliasedVar); + } + + CASE(GetImport) { + { + ReservedRooted<JSObject*> obj0(&state.obj0, frame->environmentChain()); + ReservedRooted<Value> value0(&state.value0); + { + PUSH_EXIT_FRAME(); + if (!GetImportOperation(cx, obj0, script, pc, &value0)) { + goto error; + } + } + PUSH(StackVal(value0)); + } + END_OP(GetImport); + } + + CASE(GetIntrinsic) { + INVOKE_IC(GetIntrinsic); + IC_PUSH_RESULT(); + END_OP(GetIntrinsic); + } + + CASE(Callee) { + PUSH(StackVal(frame->calleev())); + END_OP(Callee); + } + + CASE(EnvCallee) { + uint8_t numHops = GET_UINT8(pc); + JSObject* env = &frame->environmentChain()->as<EnvironmentObject>(); + for (unsigned i = 0; i < numHops; i++) { + env = &env->as<EnvironmentObject>().enclosingEnvironment(); + } + PUSH(StackVal(ObjectValue(env->as<CallObject>().callee()))); + END_OP(EnvCallee); + } + + CASE(SetProp) + CASE(StrictSetProp) + CASE(SetName) + CASE(StrictSetName) + CASE(SetGName) + CASE(StrictSetGName) { + static_assert(JSOpLength_SetProp == JSOpLength_StrictSetProp); + static_assert(JSOpLength_SetProp == JSOpLength_SetName); + static_assert(JSOpLength_SetProp == JSOpLength_StrictSetName); + static_assert(JSOpLength_SetProp == JSOpLength_SetGName); + static_assert(JSOpLength_SetProp == JSOpLength_StrictSetGName); + IC_POP_ARG(1); + IC_POP_ARG(0); + PUSH(StackVal(icregs.icVals[1])); + INVOKE_IC(SetProp); + END_OP(SetProp); + } + + CASE(InitProp) + CASE(InitHiddenProp) + CASE(InitLockedProp) { + static_assert(JSOpLength_InitProp == JSOpLength_InitHiddenProp); + static_assert(JSOpLength_InitProp == JSOpLength_InitLockedProp); + IC_POP_ARG(1); + IC_SET_ARG_FROM_STACK(0, 0); + INVOKE_IC(SetProp); + END_OP(InitProp); + } + CASE(InitGLexical) { + IC_SET_ARG_FROM_STACK(1, 0); + IC_SET_OBJ_ARG( + 0, &frameMgr.cxForLocalUseOnly()->global()->lexicalEnvironment()); + INVOKE_IC(SetProp); + END_OP(InitGLexical); + } + + CASE(SetArg) { + unsigned i = GET_ARGNO(pc); + if (script->argsObjAliasesFormals()) { + frame->argsObj().setArg(i, sp[0].asValue()); + } else { + frame->unaliasedFormal(i) = sp[0].asValue(); + } + END_OP(SetArg); + } + + CASE(SetLocal) { + uint32_t i = GET_LOCALNO(pc); + TRACE_PRINTF(" -> local: %d\n", int(i)); + frame->unaliasedLocal(i) = sp[0].asValue(); + END_OP(SetLocal); + } + + CASE(SetAliasedVar) { + EnvironmentCoordinate ec = EnvironmentCoordinate(pc); + EnvironmentObject& obj = getEnvironmentFromCoordinate(frame, ec); + MOZ_ASSERT(!IsUninitializedLexical(obj.aliasedBinding(ec))); + obj.setAliasedBinding(ec, sp[0].asValue()); + END_OP(SetAliasedVar); + } + + CASE(SetIntrinsic) { + { + ReservedRooted<Value> value0(&state.value0, sp[0].asValue()); + { + PUSH_EXIT_FRAME(); + if (!SetIntrinsicOperation(cx, script, pc, value0)) { + goto error; + } + } + } + END_OP(SetIntrinsic); + } + + CASE(PushLexicalEnv) { + { + ReservedRooted<Scope*> scope0(&state.scope0, script->getScope(pc)); + { + PUSH_EXIT_FRAME(); + if (!frame->pushLexicalEnvironment(cx, scope0.as<LexicalScope>())) { + goto error; + } + } + } + END_OP(PushLexicalEnv); + } + CASE(PopLexicalEnv) { + if (frame->isDebuggee()) { + TRACE_PRINTF("doing DebugLeaveThenPopLexicalEnv\n"); + PUSH_EXIT_FRAME(); + if (!DebugLeaveThenPopLexicalEnv(cx, frame, pc)) { + goto error; + } + } else { + frame->popOffEnvironmentChain<LexicalEnvironmentObject>(); + } + END_OP(PopLexicalEnv); + } + CASE(DebugLeaveLexicalEnv) { + if (frame->isDebuggee()) { + TRACE_PRINTF("doing DebugLeaveLexicalEnv\n"); + PUSH_EXIT_FRAME(); + if (!DebugLeaveLexicalEnv(cx, frame, pc)) { + goto error; + } + } + END_OP(DebugLeaveLexicalEnv); + } + + CASE(RecreateLexicalEnv) { + { + PUSH_EXIT_FRAME(); + if (frame->isDebuggee()) { + TRACE_PRINTF("doing DebuggeeRecreateLexicalEnv\n"); + if (!DebuggeeRecreateLexicalEnv(cx, frame, pc)) { + goto error; + } + } else { + if (!frame->recreateLexicalEnvironment<false>(cx)) { + goto error; + } + } + } + END_OP(RecreateLexicalEnv); + } + + CASE(FreshenLexicalEnv) { + { + PUSH_EXIT_FRAME(); + if (frame->isDebuggee()) { + TRACE_PRINTF("doing DebuggeeFreshenLexicalEnv\n"); + if (!DebuggeeFreshenLexicalEnv(cx, frame, pc)) { + goto error; + } + } else { + if (!frame->freshenLexicalEnvironment<false>(cx)) { + goto error; + } + } + } + END_OP(FreshenLexicalEnv); + } + CASE(PushClassBodyEnv) { + { + ReservedRooted<Scope*> scope0(&state.scope0, script->getScope(pc)); + PUSH_EXIT_FRAME(); + if (!frame->pushClassBodyEnvironment(cx, scope0.as<ClassBodyScope>())) { + goto error; + } + } + END_OP(PushClassBodyEnv); + } + CASE(PushVarEnv) { + { + ReservedRooted<Scope*> scope0(&state.scope0, script->getScope(pc)); + PUSH_EXIT_FRAME(); + if (!frame->pushVarEnvironment(cx, scope0)) { + goto error; + } + } + END_OP(PushVarEnv); + } + CASE(EnterWith) { + { + ReservedRooted<Scope*> scope0(&state.scope0, script->getScope(pc)); + ReservedRooted<Value> value0(&state.value0, POP().asValue()); + PUSH_EXIT_FRAME(); + if (!EnterWithOperation(cx, frame, value0, scope0.as<WithScope>())) { + goto error; + } + } + END_OP(EnterWith); + } + CASE(LeaveWith) { + frame->popOffEnvironmentChain<WithEnvironmentObject>(); + END_OP(LeaveWith); + } + CASE(BindVar) { + JSObject* varObj; + { + ReservedRooted<JSObject*> obj0(&state.obj0, frame->environmentChain()); + PUSH_EXIT_FRAME(); + varObj = BindVarOperation(cx, obj0); + } + PUSH(StackVal(ObjectValue(*varObj))); + END_OP(BindVar); + } + + CASE(GlobalOrEvalDeclInstantiation) { + GCThingIndex lastFun = GET_GCTHING_INDEX(pc); + { + ReservedRooted<JSObject*> obj0(&state.obj0, frame->environmentChain()); + PUSH_EXIT_FRAME(); + if (!GlobalOrEvalDeclInstantiation(cx, obj0, script, lastFun)) { + goto error; + } + } + END_OP(GlobalOrEvalDeclInstantiation); + } + + CASE(DelName) { + { + ReservedRooted<PropertyName*> name0(&state.name0, script->getName(pc)); + ReservedRooted<JSObject*> obj0(&state.obj0, frame->environmentChain()); + PUSH_EXIT_FRAME(); + if (!DeleteNameOperation(cx, name0, obj0, &state.res)) { + goto error; + } + } + PUSH(StackVal(state.res)); + state.res.setUndefined(); + END_OP(DelName); + } + + CASE(Arguments) { + { + PUSH_EXIT_FRAME(); + if (!NewArgumentsObject(cx, frame, &state.res)) { + goto error; + } + } + PUSH(StackVal(state.res)); + state.res.setUndefined(); + END_OP(Arguments); + } + + CASE(Rest) { + INVOKE_IC(Rest); + IC_PUSH_RESULT(); + END_OP(Rest); + } + + CASE(FunctionThis) { + { + PUSH_EXIT_FRAME(); + if (!js::GetFunctionThis(cx, frame, &state.res)) { + goto error; + } + } + PUSH(StackVal(state.res)); + state.res.setUndefined(); + END_OP(FunctionThis); + } + + CASE(Pop) { + POP(); + END_OP(Pop); + } + CASE(PopN) { + uint32_t n = GET_UINT16(pc); + POPN(n); + END_OP(PopN); + } + CASE(Dup) { + StackVal value = sp[0]; + PUSH(value); + END_OP(Dup); + } + CASE(Dup2) { + StackVal value1 = sp[0]; + StackVal value2 = sp[1]; + PUSH(value2); + PUSH(value1); + END_OP(Dup2); + } + CASE(DupAt) { + unsigned i = GET_UINT24(pc); + StackVal value = sp[i]; + PUSH(value); + END_OP(DupAt); + } + CASE(Swap) { + std::swap(sp[0], sp[1]); + END_OP(Swap); + } + CASE(Pick) { + unsigned i = GET_UINT8(pc); + StackVal tmp = sp[i]; + memmove(&sp[1], &sp[0], sizeof(StackVal) * i); + sp[0] = tmp; + END_OP(Pick); + } + CASE(Unpick) { + unsigned i = GET_UINT8(pc); + StackVal tmp = sp[0]; + memmove(&sp[0], &sp[1], sizeof(StackVal) * i); + sp[i] = tmp; + END_OP(Unpick); + } + CASE(DebugCheckSelfHosted) { + HandleValue val = Stack::handle(&sp[0]); + { + PUSH_EXIT_FRAME(); + if (!Debug_CheckSelfHosted(cx, val)) { + goto error; + } + } + END_OP(DebugCheckSelfHosted); + } + CASE(Lineno) { END_OP(Lineno); } + CASE(NopDestructuring) { END_OP(NopDestructuring); } + CASE(ForceInterpreter) { END_OP(ForceInterpreter); } + CASE(Debugger) { + { + PUSH_EXIT_FRAME(); + if (!OnDebuggerStatement(cx, frame)) { + goto error; + } + } + END_OP(Debugger); + } + + label_default: + MOZ_CRASH("Bad opcode"); + } + +error: + TRACE_PRINTF("HandleException: frame %p\n", frame); + { + ResumeFromException rfe; + { + PUSH_EXIT_FRAME(); + HandleException(&rfe); + } + + switch (rfe.kind) { + case ExceptionResumeKind::EntryFrame: + TRACE_PRINTF(" -> Return from entry frame\n"); + frame->setReturnValue(MagicValue(JS_ION_ERROR)); + stack.fp = reinterpret_cast<StackVal*>(rfe.framePointer); + stack.unwindingSP = reinterpret_cast<StackVal*>(rfe.stackPointer); + goto unwind_error; + case ExceptionResumeKind::Catch: + pc = frame->interpreterPC(); + stack.fp = reinterpret_cast<StackVal*>(rfe.framePointer); + stack.unwindingSP = reinterpret_cast<StackVal*>(rfe.stackPointer); + TRACE_PRINTF(" -> catch to pc %p\n", pc); + goto unwind; + case ExceptionResumeKind::Finally: + pc = frame->interpreterPC(); + stack.fp = reinterpret_cast<StackVal*>(rfe.framePointer); + sp = reinterpret_cast<StackVal*>(rfe.stackPointer); + TRACE_PRINTF(" -> finally to pc %p\n", pc); + PUSH(StackVal(rfe.exception)); + PUSH(StackVal(rfe.exceptionStack)); + PUSH(StackVal(BooleanValue(true))); + stack.unwindingSP = sp; + goto unwind; + case ExceptionResumeKind::ForcedReturnBaseline: + pc = frame->interpreterPC(); + stack.fp = reinterpret_cast<StackVal*>(rfe.framePointer); + stack.unwindingSP = reinterpret_cast<StackVal*>(rfe.stackPointer); + TRACE_PRINTF(" -> forced return\n"); + goto unwind_ret; + case ExceptionResumeKind::ForcedReturnIon: + MOZ_CRASH( + "Unexpected ForcedReturnIon exception-resume kind in Portable " + "Baseline"); + case ExceptionResumeKind::Bailout: + MOZ_CRASH( + "Unexpected Bailout exception-resume kind in Portable Baseline"); + case ExceptionResumeKind::Wasm: + MOZ_CRASH("Unexpected Wasm exception-resume kind in Portable Baseline"); + case ExceptionResumeKind::WasmCatch: + MOZ_CRASH( + "Unexpected WasmCatch exception-resume kind in Portable " + "Baseline"); + } + } + + DISPATCH(); + +unwind: + TRACE_PRINTF("unwind: fp = %p entryFrame = %p\n", stack.fp, entryFrame); + if (reinterpret_cast<uintptr_t>(stack.fp) > + reinterpret_cast<uintptr_t>(entryFrame) + BaselineFrame::Size()) { + TRACE_PRINTF(" -> returning\n"); + return PBIResult::Unwind; + } + sp = stack.unwindingSP; + frame = reinterpret_cast<BaselineFrame*>( + reinterpret_cast<uintptr_t>(stack.fp) - BaselineFrame::Size()); + TRACE_PRINTF(" -> setting sp to %p, frame to %p\n", sp, frame); + frameMgr.switchToFrame(frame); + pc = frame->interpreterPC(); + script.set(frame->script()); + DISPATCH(); +unwind_error: + TRACE_PRINTF("unwind_error: fp = %p entryFrame = %p\n", stack.fp, entryFrame); + if (reinterpret_cast<uintptr_t>(stack.fp) > + reinterpret_cast<uintptr_t>(entryFrame) + BaselineFrame::Size()) { + return PBIResult::UnwindError; + } + if (reinterpret_cast<uintptr_t>(stack.fp) == + reinterpret_cast<uintptr_t>(entryFrame) + BaselineFrame::Size()) { + return PBIResult::Error; + } + sp = stack.unwindingSP; + frame = reinterpret_cast<BaselineFrame*>( + reinterpret_cast<uintptr_t>(stack.fp) - BaselineFrame::Size()); + TRACE_PRINTF(" -> setting sp to %p, frame to %p\n", sp, frame); + frameMgr.switchToFrame(frame); + pc = frame->interpreterPC(); + script.set(frame->script()); + goto error; +unwind_ret: + TRACE_PRINTF("unwind_ret: fp = %p entryFrame = %p\n", stack.fp, entryFrame); + if (reinterpret_cast<uintptr_t>(stack.fp) > + reinterpret_cast<uintptr_t>(entryFrame) + BaselineFrame::Size()) { + return PBIResult::UnwindRet; + } + if (reinterpret_cast<uintptr_t>(stack.fp) == + reinterpret_cast<uintptr_t>(entryFrame) + BaselineFrame::Size()) { + *ret = frame->returnValue(); + return PBIResult::Ok; + } + sp = stack.unwindingSP; + frame = reinterpret_cast<BaselineFrame*>( + reinterpret_cast<uintptr_t>(stack.fp) - BaselineFrame::Size()); + TRACE_PRINTF(" -> setting sp to %p, frame to %p\n", sp, frame); + frameMgr.switchToFrame(frame); + pc = frame->interpreterPC(); + script.set(frame->script()); + from_unwind = true; + goto do_return; + +#ifndef __wasi__ +debug: { + TRACE_PRINTF("hit debug point\n"); + PUSH_EXIT_FRAME(); + if (!HandleDebugTrap(cx, frame, pc)) { + TRACE_PRINTF("HandleDebugTrap returned error\n"); + goto error; + } + pc = frame->interpreterPC(); + TRACE_PRINTF("HandleDebugTrap done\n"); +} + goto dispatch; +#endif +} + +/* + * ----------------------------------------------- + * Entry point + * ----------------------------------------------- + */ + +bool PortableBaselineTrampoline(JSContext* cx, size_t argc, Value* argv, + size_t numFormals, size_t numActuals, + CalleeToken calleeToken, JSObject* envChain, + Value* result) { + State state(cx); + Stack stack(cx->portableBaselineStack()); + StackVal* sp = stack.top; + + TRACE_PRINTF("Trampoline: calleeToken %p env %p\n", calleeToken, envChain); + + // Expected stack frame: + // - argN + // - ... + // - arg1 + // - this + // - calleeToken + // - descriptor + // - "return address" (nullptr for top frame) + + // `argc` is the number of args *including* `this` (`N + 1` + // above). `numFormals` is the minimum `N`; if less, we need to push + // `UndefinedValue`s above. We need to pass an argc (including + // `this`) accoundint for the extra undefs in the descriptor's argc. + // + // If constructing, there is an additional `newTarget` at the end. + // + // Note that `callee`, which is in the stack signature for a `Call` + // JSOp, does *not* appear in this count: it is separately passed in + // the `calleeToken`. + + bool constructing = CalleeTokenIsConstructing(calleeToken); + size_t numCalleeActuals = std::max(numActuals, numFormals); + size_t numUndefs = numCalleeActuals - numActuals; + + // N.B.: we already checked the stack in + // PortableBaselineInterpreterStackCheck; we don't do it here + // because we can't push an exit frame if we don't have an entry + // frame, and we need a full activation to produce the backtrace + // from ReportOverRecursed. + + if (constructing) { + PUSH(StackVal(argv[argc])); + } + for (size_t i = 0; i < numUndefs; i++) { + PUSH(StackVal(UndefinedValue())); + } + for (size_t i = 0; i < argc; i++) { + PUSH(StackVal(argv[argc - 1 - i])); + } + PUSHNATIVE(StackValNative(calleeToken)); + PUSHNATIVE(StackValNative( + MakeFrameDescriptorForJitCall(FrameType::CppToJSJit, numActuals))); + + switch (PortableBaselineInterpret(cx, state, stack, sp, envChain, result)) { + case PBIResult::Ok: + case PBIResult::UnwindRet: + TRACE_PRINTF("PBI returned Ok/UnwindRet with result %" PRIx64 "\n", + result->asRawBits()); + break; + case PBIResult::Error: + case PBIResult::UnwindError: + TRACE_PRINTF("PBI returned Error/UnwindError\n"); + return false; + case PBIResult::Unwind: + MOZ_CRASH("Should not unwind out of top / entry frame"); + } + + return true; +} + +MethodStatus CanEnterPortableBaselineInterpreter(JSContext* cx, + RunState& state) { + if (!JitOptions.portableBaselineInterpreter) { + return MethodStatus::Method_CantCompile; + } + if (state.script()->hasJitScript()) { + return MethodStatus::Method_Compiled; + } + if (state.script()->hasForceInterpreterOp()) { + return MethodStatus::Method_CantCompile; + } + if (cx->runtime()->geckoProfiler().enabled()) { + return MethodStatus::Method_CantCompile; + } + + if (state.isInvoke()) { + InvokeState& invoke = *state.asInvoke(); + if (TooManyActualArguments(invoke.args().length())) { + return MethodStatus::Method_CantCompile; + } + } else { + if (state.asExecute()->isDebuggerEval()) { + return MethodStatus::Method_CantCompile; + } + } + if (state.script()->getWarmUpCount() <= + JitOptions.portableBaselineInterpreterWarmUpThreshold) { + return MethodStatus::Method_Skipped; + } + if (!cx->zone()->ensureJitZoneExists(cx)) { + return MethodStatus::Method_Error; + } + + AutoKeepJitScripts keepJitScript(cx); + if (!state.script()->ensureHasJitScript(cx, keepJitScript)) { + return MethodStatus::Method_Error; + } + state.script()->updateJitCodeRaw(cx->runtime()); + return MethodStatus::Method_Compiled; +} + +bool PortablebaselineInterpreterStackCheck(JSContext* cx, RunState& state, + size_t numActualArgs) { + auto& pbs = cx->portableBaselineStack(); + StackVal* base = reinterpret_cast<StackVal*>(pbs.base); + StackVal* top = reinterpret_cast<StackVal*>(pbs.top); + ssize_t margin = kStackMargin / sizeof(StackVal); + ssize_t needed = numActualArgs + state.script()->nslots() + margin; + return (top - base) >= needed; +} + +} // namespace pbl +} // namespace js |