lib/VM/Interpreter.cpp (3,097 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#define DEBUG_TYPE "vm"
#include "hermes/VM/Interpreter.h"
#include "hermes/VM/Runtime.h"
#include "hermes/Inst/InstDecode.h"
#include "hermes/Support/Conversions.h"
#include "hermes/Support/SlowAssert.h"
#include "hermes/Support/Statistic.h"
#include "hermes/VM/Callable.h"
#include "hermes/VM/CodeBlock.h"
#include "hermes/VM/HandleRootOwner-inline.h"
#include "hermes/VM/JSArray.h"
#include "hermes/VM/JSError.h"
#include "hermes/VM/JSGenerator.h"
#include "hermes/VM/JSProxy.h"
#include "hermes/VM/JSRegExp.h"
#include "hermes/VM/JSTypedArray.h"
#include "hermes/VM/Operations.h"
#include "hermes/VM/Profiler.h"
#include "hermes/VM/Profiler/CodeCoverageProfiler.h"
#include "hermes/VM/PropertyAccessor.h"
#include "hermes/VM/RuntimeModule-inline.h"
#include "hermes/VM/StackFrame-inline.h"
#include "hermes/VM/StringPrimitive.h"
#include "hermes/VM/StringView.h"
#include "llvh/Support/Debug.h"
#include "llvh/Support/Format.h"
#include "llvh/Support/raw_ostream.h"
#include "Interpreter-internal.h"
using llvh::dbgs;
using namespace hermes::inst;
HERMES_SLOW_STATISTIC(
NumGetById,
"NumGetById: Number of property 'read by id' accesses");
HERMES_SLOW_STATISTIC(
NumGetByIdCacheHits,
"NumGetByIdCacheHits: Number of property 'read by id' cache hits");
HERMES_SLOW_STATISTIC(
NumGetByIdProtoHits,
"NumGetByIdProtoHits: Number of property 'read by id' cache hits for the prototype");
HERMES_SLOW_STATISTIC(
NumGetByIdCacheEvicts,
"NumGetByIdCacheEvicts: Number of property 'read by id' cache evictions");
HERMES_SLOW_STATISTIC(
NumGetByIdFastPaths,
"NumGetByIdFastPaths: Number of property 'read by id' fast paths");
HERMES_SLOW_STATISTIC(
NumGetByIdAccessor,
"NumGetByIdAccessor: Number of property 'read by id' accessors");
HERMES_SLOW_STATISTIC(
NumGetByIdProto,
"NumGetByIdProto: Number of property 'read by id' in the prototype chain");
HERMES_SLOW_STATISTIC(
NumGetByIdNotFound,
"NumGetByIdNotFound: Number of property 'read by id' not found");
HERMES_SLOW_STATISTIC(
NumGetByIdTransient,
"NumGetByIdTransient: Number of property 'read by id' of non-objects");
HERMES_SLOW_STATISTIC(
NumGetByIdDict,
"NumGetByIdDict: Number of property 'read by id' of dictionaries");
HERMES_SLOW_STATISTIC(
NumGetByIdSlow,
"NumGetByIdSlow: Number of property 'read by id' slow path");
HERMES_SLOW_STATISTIC(
NumPutById,
"NumPutById: Number of property 'write by id' accesses");
HERMES_SLOW_STATISTIC(
NumPutByIdCacheHits,
"NumPutByIdCacheHits: Number of property 'write by id' cache hits");
HERMES_SLOW_STATISTIC(
NumPutByIdCacheEvicts,
"NumPutByIdCacheEvicts: Number of property 'write by id' cache evictions");
HERMES_SLOW_STATISTIC(
NumPutByIdFastPaths,
"NumPutByIdFastPaths: Number of property 'write by id' fast paths");
HERMES_SLOW_STATISTIC(
NumPutByIdTransient,
"NumPutByIdTransient: Number of property 'write by id' to non-objects");
HERMES_SLOW_STATISTIC(
NumNativeFunctionCalls,
"NumNativeFunctionCalls: Number of native function calls");
HERMES_SLOW_STATISTIC(
NumBoundFunctionCalls,
"NumBoundCalls: Number of bound function calls");
// Ensure that instructions declared as having matching layouts actually do.
#include "InstLayout.inc"
#if defined(HERMESVM_PROFILER_EXTERN)
// External profiler mode wraps calls to each JS function with a unique native
// function that recursively calls the interpreter. See Profiler.{h,cpp} for how
// these symbols are subsequently patched with JS function names.
#define INTERP_WRAPPER(name) \
__attribute__((__noinline__)) static llvh::CallResult<llvh::HermesValue> \
name(hermes::vm::Runtime &runtime, hermes::vm::CodeBlock *newCodeBlock) { \
return runtime.interpretFunctionImpl(newCodeBlock); \
}
PROFILER_SYMBOLS(INTERP_WRAPPER)
#endif
namespace hermes {
namespace vm {
#if defined(HERMESVM_PROFILER_EXTERN)
typedef CallResult<HermesValue> (*WrapperFunc)(Runtime &, CodeBlock *);
#define LIST_ITEM(name) name,
static const WrapperFunc interpWrappers[] = {PROFILER_SYMBOLS(LIST_ITEM)};
#endif
/// Initialize the state of some internal variables based on the current
/// code block.
#define INIT_STATE_FOR_CODEBLOCK(codeBlock) \
do { \
strictMode = (codeBlock)->isStrictMode(); \
defaultPropOpFlags = DEFAULT_PROP_OP_FLAGS(strictMode); \
if (EnableCrashTrace) { \
auto *bc = (codeBlock)->getRuntimeModule()->getBytecode(); \
bytecodeFileStart = bc->getRawBuffer().data(); \
auto hash = bc->getSourceHash(); \
runtime.crashTrace_.recordModule( \
bc->getSegmentID(), \
(codeBlock)->getRuntimeModule()->getSourceURL(), \
llvh::StringRef((const char *)&hash, sizeof(hash))); \
} \
} while (0)
CallResult<PseudoHandle<JSGenerator>> Interpreter::createGenerator_RJS(
Runtime &runtime,
RuntimeModule *runtimeModule,
unsigned funcIndex,
Handle<Environment> envHandle,
NativeArgs args) {
auto gifRes = GeneratorInnerFunction::create(
runtime,
runtimeModule->getDomain(runtime),
Handle<JSObject>::vmcast(&runtime.functionPrototype),
envHandle,
runtimeModule->getCodeBlockMayAllocate(funcIndex),
args);
if (LLVM_UNLIKELY(gifRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto generatorFunction = runtime.makeHandle(vmcast<JSGeneratorFunction>(
runtime.getCurrentFrame().getCalleeClosureUnsafe()));
auto prototypeProp = JSObject::getNamed_RJS(
generatorFunction,
runtime,
Predefined::getSymbolID(Predefined::prototype));
if (LLVM_UNLIKELY(prototypeProp == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
Handle<JSObject> prototype = vmisa<JSObject>(prototypeProp->get())
? runtime.makeHandle<JSObject>(prototypeProp->get())
: Handle<JSObject>::vmcast(&runtime.generatorPrototype);
return JSGenerator::create(runtime, *gifRes, prototype);
}
CallResult<Handle<Arguments>> Interpreter::reifyArgumentsSlowPath(
Runtime &runtime,
Handle<Callable> curFunction,
bool strictMode) {
auto frame = runtime.getCurrentFrame();
uint32_t argCount = frame.getArgCount();
// Define each JavaScript argument.
auto argRes = Arguments::create(runtime, argCount, curFunction, strictMode);
if (LLVM_UNLIKELY(argRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
Handle<Arguments> args = *argRes;
for (uint32_t argIndex = 0; argIndex < argCount; ++argIndex) {
Arguments::unsafeSetExistingElementAt(
*args, runtime, argIndex, frame.getArgRef(argIndex));
}
// The returned value should already be set from the create call.
return args;
}
CallResult<PseudoHandle<>> Interpreter::getArgumentsPropByValSlowPath_RJS(
Runtime &runtime,
PinnedHermesValue *lazyReg,
PinnedHermesValue *valueReg,
Handle<Callable> curFunction,
bool strictMode) {
auto frame = runtime.getCurrentFrame();
// If the arguments object has already been created.
if (!lazyReg->isUndefined()) {
// The arguments object has been created, so this is a regular property
// get.
assert(lazyReg->isObject() && "arguments lazy register is not an object");
return JSObject::getComputed_RJS(
Handle<JSObject>::vmcast(lazyReg), runtime, Handle<>(valueReg));
}
if (!valueReg->isSymbol()) {
// Attempt a fast path in the case that the key is not a symbol.
// If it is a symbol, force reification for now.
// Convert the value to a string.
auto strRes = toString_RJS(runtime, Handle<>(valueReg));
if (strRes == ExecutionStatus::EXCEPTION)
return ExecutionStatus::EXCEPTION;
auto strPrim = runtime.makeHandle(std::move(*strRes));
// Check if the string is a valid argument index.
if (auto index = toArrayIndex(runtime, strPrim)) {
if (*index < frame.getArgCount()) {
return createPseudoHandle(frame.getArgRef(*index));
}
auto objectPrototype = Handle<JSObject>::vmcast(&runtime.objectPrototype);
// OK, they are requesting an index that either doesn't exist or is
// somewhere up in the prototype chain. Since we want to avoid reifying,
// check which it is:
MutableHandle<JSObject> inObject{runtime};
MutableHandle<SymbolID> inNameTmpStorage{runtime};
ComputedPropertyDescriptor desc;
JSObject::getComputedPrimitiveDescriptor(
objectPrototype, runtime, strPrim, inObject, inNameTmpStorage, desc);
// If we couldn't find the property, just return 'undefined'.
if (!inObject)
return createPseudoHandle(HermesValue::encodeUndefinedValue());
// If the property isn't an accessor, we can just return it without
// reifying.
if (!desc.flags.accessor) {
return JSObject::getComputedSlotValue(
createPseudoHandle(inObject.get()),
runtime,
inNameTmpStorage,
desc);
}
}
// Are they requesting "arguments.length"?
if (runtime.symbolEqualsToStringPrim(
Predefined::getSymbolID(Predefined::length), *strPrim)) {
return createPseudoHandle(
HermesValue::encodeDoubleValue(frame.getArgCount()));
}
}
// Looking for an accessor or a property that needs reification.
auto argRes = reifyArgumentsSlowPath(runtime, curFunction, strictMode);
if (argRes == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
// Update the register with the reified value.
*lazyReg = argRes->getHermesValue();
// For simplicity, call ourselves again.
return getArgumentsPropByValSlowPath_RJS(
runtime, lazyReg, valueReg, curFunction, strictMode);
}
CallResult<PseudoHandle<>> Interpreter::handleCallSlowPath(
Runtime &runtime,
PinnedHermesValue *callTarget) {
if (auto *native = dyn_vmcast<NativeFunction>(*callTarget)) {
++NumNativeFunctionCalls;
// Call the native function directly
return NativeFunction::_nativeCall(native, runtime);
} else if (auto *bound = dyn_vmcast<BoundFunction>(*callTarget)) {
++NumBoundFunctionCalls;
// Call the bound function.
return BoundFunction::_boundCall(bound, runtime.getCurrentIP(), runtime);
} else {
return runtime.raiseTypeErrorForValue(
Handle<>(callTarget), " is not a function");
}
}
inline PseudoHandle<> Interpreter::tryGetPrimitiveOwnPropertyById(
Runtime &runtime,
Handle<> base,
SymbolID id) {
if (base->isString() && id == Predefined::getSymbolID(Predefined::length)) {
return createPseudoHandle(
HermesValue::encodeNumberValue(base->getString()->getStringLength()));
}
return createPseudoHandle(HermesValue::encodeEmptyValue());
}
CallResult<PseudoHandle<>> Interpreter::getByIdTransient_RJS(
Runtime &runtime,
Handle<> base,
SymbolID id) {
// This is similar to what ES5.1 8.7.1 special [[Get]] internal
// method did, but that section doesn't exist in ES9 anymore.
// Instead, the [[Get]] Receiver argument serves a similar purpose.
// Fast path: try to get primitive own property directly first.
PseudoHandle<> valOpt = tryGetPrimitiveOwnPropertyById(runtime, base, id);
if (!valOpt->isEmpty()) {
return valOpt;
}
// get the property descriptor from primitive prototype without
// boxing with vm::toObject(). This is where any properties will
// be.
CallResult<Handle<JSObject>> primitivePrototypeResult =
getPrimitivePrototype(runtime, base);
if (primitivePrototypeResult == ExecutionStatus::EXCEPTION) {
// If an exception is thrown, likely we are trying to read property on
// undefined/null. Passing over the name of the property
// so that we could emit more meaningful error messages.
return amendPropAccessErrorMsgWithPropName(runtime, base, "read", id);
}
return JSObject::getNamedWithReceiver_RJS(
*primitivePrototypeResult, runtime, id, base);
}
PseudoHandle<> Interpreter::getByValTransientFast(
Runtime &runtime,
Handle<> base,
Handle<> nameHandle) {
if (base->isString()) {
// Handle most common fast path -- array index property for string
// primitive.
// Since primitive string cannot have index like property we can
// skip ObjectFlags::fastIndexProperties checking and directly
// checking index storage from StringPrimitive.
OptValue<uint32_t> arrayIndex = toArrayIndexFastPath(*nameHandle);
// Get character directly from primitive if arrayIndex is within range.
// Otherwise we need to fall back to prototype lookup.
if (arrayIndex &&
arrayIndex.getValue() < base->getString()->getStringLength()) {
return createPseudoHandle(
runtime
.getCharacterString(base->getString()->at(arrayIndex.getValue()))
.getHermesValue());
}
}
return createPseudoHandle(HermesValue::encodeEmptyValue());
}
CallResult<PseudoHandle<>> Interpreter::getByValTransient_RJS(
Runtime &runtime,
Handle<> base,
Handle<> name) {
// This is similar to what ES5.1 8.7.1 special [[Get]] internal
// method did, but that section doesn't exist in ES9 anymore.
// Instead, the [[Get]] Receiver argument serves a similar purpose.
// Optimization: check fast path first.
PseudoHandle<> fastRes = getByValTransientFast(runtime, base, name);
if (!fastRes->isEmpty()) {
return fastRes;
}
auto res = toObject(runtime, base);
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION))
return ExecutionStatus::EXCEPTION;
return JSObject::getComputedWithReceiver_RJS(
runtime.makeHandle<JSObject>(res.getValue()), runtime, name, base);
}
static ExecutionStatus
transientObjectPutErrorMessage(Runtime &runtime, Handle<> base, SymbolID id) {
// Emit an error message that looks like:
// "Cannot create property '%{id}' on ${typeof base} '${String(base)}'".
StringView propName = runtime.getIdentifierTable().getStringView(runtime, id);
Handle<StringPrimitive> baseType =
runtime.makeHandle(vmcast<StringPrimitive>(typeOf(runtime, base)));
StringView baseTypeAsString =
StringPrimitive::createStringView(runtime, baseType);
MutableHandle<StringPrimitive> valueAsString{runtime};
if (base->isSymbol()) {
// Special workaround for Symbol which can't be stringified.
auto str = symbolDescriptiveString(runtime, Handle<SymbolID>::vmcast(base));
if (str != ExecutionStatus::EXCEPTION) {
valueAsString = *str;
} else {
runtime.clearThrownValue();
valueAsString = StringPrimitive::createNoThrow(
runtime, "<<Exception occurred getting the value>>");
}
} else {
auto str = toString_RJS(runtime, base);
assert(
str != ExecutionStatus::EXCEPTION &&
"Primitives should be convertible to string without exceptions");
valueAsString = std::move(*str);
}
StringView valueAsStringPrintable =
StringPrimitive::createStringView(runtime, valueAsString);
SmallU16String<32> tmp1;
SmallU16String<32> tmp2;
return runtime.raiseTypeError(
TwineChar16("Cannot create property '") + propName + "' on " +
baseTypeAsString.getUTF16Ref(tmp1) + " '" +
valueAsStringPrintable.getUTF16Ref(tmp2) + "'");
}
ExecutionStatus Interpreter::putByIdTransient_RJS(
Runtime &runtime,
Handle<> base,
SymbolID id,
Handle<> value,
bool strictMode) {
// ES5.1 8.7.2 special [[Get]] internal method.
// TODO: avoid boxing primitives unless we are calling an accessor.
// 1. Let O be ToObject(base)
auto res = toObject(runtime, base);
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
// If an exception is thrown, likely we are trying to convert
// undefined/null to an object. Passing over the name of the property
// so that we could emit more meaningful error messages.
return amendPropAccessErrorMsgWithPropName(runtime, base, "set", id);
}
auto O = runtime.makeHandle<JSObject>(res.getValue());
NamedPropertyDescriptor desc;
JSObject *propObj = JSObject::getNamedDescriptorUnsafe(O, runtime, id, desc);
// Is this a missing property, or a data property defined in the prototype
// chain? In both cases we would need to create an own property on the
// transient object, which is prohibited.
if (!propObj ||
(propObj != O.get() &&
(!desc.flags.accessor && !desc.flags.proxyObject))) {
if (strictMode) {
return transientObjectPutErrorMessage(runtime, base, id);
}
return ExecutionStatus::RETURNED;
}
// Modifying an own data property in a transient object is prohibited.
if (!desc.flags.accessor && !desc.flags.proxyObject) {
if (strictMode) {
return runtime.raiseTypeError(
"Cannot modify a property in a transient object");
}
return ExecutionStatus::RETURNED;
}
if (desc.flags.accessor) {
// This is an accessor.
auto *accessor = vmcast<PropertyAccessor>(
JSObject::getNamedSlotValueUnsafe(propObj, runtime, desc)
.getObject(runtime));
// It needs to have a setter.
if (!accessor->setter) {
if (strictMode) {
return runtime.raiseTypeError("Cannot modify a read-only accessor");
}
return ExecutionStatus::RETURNED;
}
CallResult<PseudoHandle<>> setRes =
accessor->setter.getNonNull(runtime)->executeCall1(
runtime.makeHandle(accessor->setter), runtime, base, *value);
if (setRes == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
} else {
assert(desc.flags.proxyObject && "descriptor flags are impossible");
CallResult<bool> setRes = JSProxy::setNamed(
runtime.makeHandle(propObj), runtime, id, value, base);
if (setRes == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
if (!*setRes && strictMode) {
return runtime.raiseTypeError("transient proxy set returned false");
}
}
return ExecutionStatus::RETURNED;
}
ExecutionStatus Interpreter::putByValTransient_RJS(
Runtime &runtime,
Handle<> base,
Handle<> name,
Handle<> value,
bool strictMode) {
auto idRes = valueToSymbolID(runtime, name);
if (idRes == ExecutionStatus::EXCEPTION)
return ExecutionStatus::EXCEPTION;
return putByIdTransient_RJS(runtime, base, **idRes, value, strictMode);
}
static Handle<HiddenClass> getHiddenClassForBuffer(
Runtime &runtime,
CodeBlock *curCodeBlock,
unsigned numLiterals,
unsigned keyBufferIndex) {
RuntimeModule *runtimeModule = curCodeBlock->getRuntimeModule();
if (auto clazzOpt = runtimeModule->findCachedLiteralHiddenClass(
runtime, keyBufferIndex, numLiterals))
return *clazzOpt;
MutableHandle<> tmpHandleKey{runtime};
MutableHandle<HiddenClass> clazz =
runtime.makeMutableHandle(runtime.getHiddenClassForPrototypeRaw(
vmcast<JSObject>(runtime.objectPrototype),
JSObject::numOverlapSlots<JSObject>()));
GCScopeMarkerRAII marker{runtime};
auto keyGen =
curCodeBlock->getObjectBufferKeyIter(keyBufferIndex, numLiterals);
while (keyGen.hasNext()) {
auto key = keyGen.get(runtime);
SymbolID sym = [&] {
if (key.isSymbol())
return ID(key.getSymbol().unsafeGetIndex());
assert(key.isNumber() && "Key must be symbol or number");
tmpHandleKey = key;
// Note that since this handle has been created, the associated symbol
// will be automatically kept alive until we flush the marker.
// valueToSymbolID cannot fail because the key is known to be uint32.
Handle<SymbolID> symHandle = *valueToSymbolID(runtime, tmpHandleKey);
return *symHandle;
}();
auto addResult = HiddenClass::addProperty(
clazz, runtime, sym, PropertyFlags::defaultNewNamedPropertyFlags());
clazz = addResult->first;
marker.flush();
}
if (LLVM_LIKELY(!clazz->isDictionary())) {
assert(
numLiterals == clazz->getNumProperties() &&
"numLiterals should match hidden class property count.");
assert(
clazz->getNumProperties() < 256 &&
"cached hidden class should have property count less than 256");
runtimeModule->tryCacheLiteralHiddenClass(runtime, keyBufferIndex, *clazz);
}
return {clazz};
}
CallResult<PseudoHandle<>> Interpreter::createObjectFromBuffer(
Runtime &runtime,
CodeBlock *curCodeBlock,
unsigned numLiterals,
unsigned keyBufferIndex,
unsigned valBufferIndex) {
// Create a new object using the built-in constructor or cached hidden class.
// Note that the built-in constructor is empty, so we don't actually need to
// call it.
auto clazz = getHiddenClassForBuffer(
runtime, curCodeBlock, numLiterals, keyBufferIndex);
auto obj = runtime.makeHandle(JSObject::create(runtime, clazz));
auto valGen =
curCodeBlock->getObjectBufferValueIter(valBufferIndex, numLiterals);
#ifndef NDEBUG
auto keyGen =
curCodeBlock->getObjectBufferKeyIter(keyBufferIndex, numLiterals);
#endif
uint32_t propIndex = 0;
// keyGen should always have the same amount of elements as valGen
while (valGen.hasNext()) {
#ifndef NDEBUG
{
GCScopeMarkerRAII marker{runtime};
// keyGen points to an element in the key buffer, which means it will
// only ever generate a Number or a Symbol. This means it will never
// allocate memory, and it is safe to not use a Handle.
SymbolID stringIdResult{};
auto key = keyGen.get(runtime);
if (key.isSymbol()) {
stringIdResult = ID(key.getSymbol().unsafeGetIndex());
} else {
auto keyHandle =
runtime.makeHandle(HermesValue::encodeDoubleValue(key.getNumber()));
auto idRes = valueToSymbolID(runtime, keyHandle);
assert(
idRes != ExecutionStatus::EXCEPTION &&
"valueToIdentifier() failed for uint32_t value");
stringIdResult = **idRes;
}
NamedPropertyDescriptor desc;
auto pos = HiddenClass::findProperty(
clazz,
runtime,
stringIdResult,
PropertyFlags::defaultNewNamedPropertyFlags(),
desc);
assert(
pos &&
"Should find this property in cached hidden class property table.");
assert(
desc.slot == propIndex &&
"propIndex should be the same as recorded in hidden class table.");
}
#endif
// Explicitly make sure valGen.get() is called before obj.get() so that
// any allocation in valGen.get() won't invalidate the raw pointer
// returned from obj.get().
auto val = valGen.get(runtime);
auto shv = SmallHermesValue::encodeHermesValue(val, runtime);
// We made this object, it's not a Proxy.
JSObject::setNamedSlotValueUnsafe(obj.get(), runtime, propIndex, shv);
++propIndex;
}
return createPseudoHandle(HermesValue::encodeObjectValue(*obj));
}
CallResult<PseudoHandle<>> Interpreter::createArrayFromBuffer(
Runtime &runtime,
CodeBlock *curCodeBlock,
unsigned numElements,
unsigned numLiterals,
unsigned bufferIndex) {
// Create a new array using the built-in constructor, and initialize
// the elements from a literal array buffer.
auto arrRes = JSArray::create(runtime, numElements, numElements);
if (arrRes == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
// Resize the array storage in advance.
auto arr = *arrRes;
JSArray::setStorageEndIndex(arr, runtime, numElements);
auto iter = curCodeBlock->getArrayBufferIter(bufferIndex, numLiterals);
JSArray::size_type i = 0;
while (iter.hasNext()) {
// NOTE: we must get the value in a separate step to guarantee ordering.
auto value = iter.get(runtime);
JSArray::unsafeSetExistingElementAt(*arr, runtime, i++, value);
}
return createPseudoHandle(HermesValue::encodeObjectValue(*arr));
}
#ifndef NDEBUG
llvh::raw_ostream &operator<<(llvh::raw_ostream &OS, DumpHermesValue dhv) {
OS << dhv.hv;
// If it is a string, dump the contents, truncated to 8 characters.
if (dhv.hv.isString()) {
SmallU16String<32> str;
dhv.hv.getString()->appendUTF16String(str);
UTF16Ref ref = str.arrayRef();
if (str.size() <= 8) {
OS << ":'" << ref << "'";
} else {
OS << ":'" << ref.slice(0, 8) << "'";
OS << "...[" << str.size() << "]";
}
}
return OS;
}
void dumpCallArguments(
llvh::raw_ostream &OS,
Runtime &runtime,
StackFramePtr calleeFrame) {
OS << "arguments:\n";
OS << " " << 0 << " " << DumpHermesValue(calleeFrame.getThisArgRef())
<< "\n";
for (unsigned i = 0; i < calleeFrame.getArgCount(); ++i) {
OS << " " << (i + 1) << " " << DumpHermesValue(calleeFrame.getArgRef(i))
<< "\n";
}
}
LLVM_ATTRIBUTE_UNUSED
static void printDebugInfo(
CodeBlock *curCodeBlock,
PinnedHermesValue *frameRegs,
const Inst *ip) {
// Check if LLVm debugging is enabled for us.
bool debug = false;
SLOW_DEBUG(debug = true);
if (!debug)
return;
DecodedInstruction decoded = decodeInstruction(ip);
dbgs() << llvh::format_decimal((const uint8_t *)ip - curCodeBlock->begin(), 4)
<< " OpCode::" << getOpCodeString(decoded.meta.opCode);
for (unsigned i = 0; i < decoded.meta.numOperands; ++i) {
auto operandType = decoded.meta.operandType[i];
auto value = decoded.operandValue[i];
dbgs() << (i == 0 ? " " : ", ");
dumpOperand(dbgs(), operandType, value);
if (operandType == OperandType::Reg8 || operandType == OperandType::Reg32) {
// Print the register value, if source.
if (i != 0 || decoded.meta.numOperands == 1)
dbgs() << "="
<< DumpHermesValue(REG(static_cast<uint32_t>(value.integer)));
}
}
dbgs() << "\n";
}
/// \return whether \p opcode is a call opcode (Call, CallDirect, Construct,
/// CallLongIndex, etc). Note CallBuiltin is not really a Call.
LLVM_ATTRIBUTE_UNUSED
static bool isCallType(OpCode opcode) {
switch (opcode) {
#define DEFINE_RET_TARGET(name) \
case OpCode::name: \
return true;
#include "hermes/BCGen/HBC/BytecodeList.def"
default:
return false;
}
}
#endif
/// \return the address of the next instruction after \p ip, which must be a
/// call-type instruction.
LLVM_ATTRIBUTE_ALWAYS_INLINE
static inline const Inst *nextInstCall(const Inst *ip) {
HERMES_SLOW_ASSERT(isCallType(ip->opCode) && "ip is not of call type");
// To avoid needing a large lookup table or switchcase, the following packs
// information about the size of each call opcode into a uint32_t. Each call
// type is represented with two bits, representing how much larger it is than
// the smallest call instruction.
// If we used 64 bits, we could fit the actual size of each call, without
// needing the offset, and this may be necessary if new call instructions are
// added in the future. For now however, due to limitations on loading large
// immediates in ARM, it is significantly more efficient to use a uint32_t
// than a uint64_t.
constexpr auto firstCall = std::min({
#define DEFINE_RET_TARGET(name) static_cast<uint8_t>(OpCode::name),
#include "hermes/BCGen/HBC/BytecodeList.def"
});
constexpr auto lastCall = std::max({
#define DEFINE_RET_TARGET(name) static_cast<uint8_t>(OpCode::name),
#include "hermes/BCGen/HBC/BytecodeList.def"
});
constexpr auto minSize = std::min({
#define DEFINE_RET_TARGET(name) sizeof(inst::name##Inst),
#include "hermes/BCGen/HBC/BytecodeList.def"
});
constexpr auto maxSize = std::max({
#define DEFINE_RET_TARGET(name) sizeof(inst::name##Inst),
#include "hermes/BCGen/HBC/BytecodeList.def"
});
constexpr uint32_t W = 2;
constexpr uint32_t mask = (1 << W) - 1;
static_assert(llvh::isUInt<W>(maxSize - minSize), "Size range too large.");
static_assert((lastCall - firstCall + 1) * W <= 32, "Too many call opcodes.");
constexpr uint32_t callSizes = 0
#define DEFINE_RET_TARGET(name) \
| \
((sizeof(inst::name##Inst) - minSize) \
<< (((uint8_t)OpCode::name - firstCall) * W))
#include "hermes/BCGen/HBC/BytecodeList.def"
;
#undef DEFINE_RET_TARGET
const uint8_t offset = static_cast<uint8_t>(ip->opCode) - firstCall;
return IPADD(((callSizes >> (offset * W)) & mask) + minSize);
}
CallResult<HermesValue> Runtime::interpretFunctionImpl(
CodeBlock *newCodeBlock) {
newCodeBlock->lazyCompile(*this);
#if defined(HERMES_ENABLE_ALLOCATION_LOCATION_TRACES) || !defined(NDEBUG)
// We always call getCurrentIP() in a debug build as this has the effect
// of asserting the IP is correctly set (not invalidated) at this point.
// This allows us to leverage our whole test-suite to find missing cases
// of CAPTURE_IP* macros in the interpreter loop.
const inst::Inst *ip = getCurrentIP();
(void)ip;
#endif
#ifdef HERMES_ENABLE_ALLOCATION_LOCATION_TRACES
if (ip) {
const CodeBlock *codeBlock;
std::tie(codeBlock, ip) = getCurrentInterpreterLocation(ip);
// All functions end in a Ret so we must match this with a pushCallStack()
// before executing.
if (codeBlock) {
// Push a call entry at the last location we were executing bytecode.
// This will correctly attribute things like eval().
pushCallStack(codeBlock, ip);
} else {
// Push a call entry at the entry at the top of interpreted code.
pushCallStack(newCodeBlock, (const Inst *)newCodeBlock->begin());
}
} else {
// Push a call entry at the entry at the top of interpreted code.
pushCallStack(newCodeBlock, (const Inst *)newCodeBlock->begin());
}
#endif
InterpreterState state{newCodeBlock, 0};
if (HERMESVM_CRASH_TRACE &&
(getVMExperimentFlags() & experiments::CrashTrace)) {
return Interpreter::interpretFunction<false, true>(*this, state);
} else {
return Interpreter::interpretFunction<false, false>(*this, state);
}
}
CallResult<HermesValue> Runtime::interpretFunction(CodeBlock *newCodeBlock) {
#ifdef HERMESVM_PROFILER_EXTERN
auto id = getProfilerID(newCodeBlock);
if (id >= NUM_PROFILER_SYMBOLS) {
id = NUM_PROFILER_SYMBOLS - 1; // Overflow entry.
}
return interpWrappers[id](this, newCodeBlock);
#else
return interpretFunctionImpl(newCodeBlock);
#endif
}
#ifdef HERMES_ENABLE_DEBUGGER
ExecutionStatus Runtime::stepFunction(InterpreterState &state) {
if (HERMESVM_CRASH_TRACE &&
(getVMExperimentFlags() & experiments::CrashTrace))
return Interpreter::interpretFunction<true, true>(*this, state).getStatus();
else
return Interpreter::interpretFunction<true, false>(*this, state)
.getStatus();
}
#endif
/// \return the quotient of x divided by y.
static double doDiv(double x, double y)
LLVM_NO_SANITIZE("float-divide-by-zero");
static inline double doDiv(double x, double y) {
// UBSan will complain about float divide by zero as our implementation
// of OpCode::Div depends on IEEE 754 float divide by zero. All modern
// compilers implement this and there is no trivial work-around without
// sacrificing performance and readability.
// NOTE: This was pulled out of the interpreter to avoid putting the sanitize
// silencer on the entire interpreter function.
return x / y;
}
/// \return the product of x multiplied by y.
static inline double doMult(double x, double y) {
return x * y;
}
/// \return the difference of y subtracted from x.
static inline double doSub(double x, double y) {
return x - y;
}
template <bool SingleStep, bool EnableCrashTrace>
CallResult<HermesValue> Interpreter::interpretFunction(
Runtime &runtime,
InterpreterState &state) {
// The interpreter is re-entrant and also saves/restores its IP via the
// runtime whenever a call out is made (see the CAPTURE_IP_* macros). As such,
// failure to preserve the IP across calls to interpreterFunction() disrupt
// interpreter calls further up the C++ callstack. The RAII utility class
// below makes sure we always do this correctly.
//
// TODO: The IPs stored in the C++ callstack via this holder will generally be
// the same as in the JS stack frames via the Saved IP field. We can probably
// get rid of one of these redundant stores. Doing this isn't completely
// trivial as there are currently cases where we re-enter the interpreter
// without calling Runtime::saveCallerIPInStackFrame(), and there are features
// (I think mostly the debugger + stack traces) which implicitly rely on
// this behavior. At least their tests break if this behavior is not
// preserved.
struct IPSaver {
IPSaver(Runtime &runtime)
: ip_(runtime.getCurrentIP()), runtime_(runtime) {}
~IPSaver() {
runtime_.setCurrentIP(ip_);
}
private:
const Inst *ip_;
Runtime &runtime_;
};
IPSaver ipSaver(runtime);
#ifndef HERMES_ENABLE_DEBUGGER
static_assert(!SingleStep, "can't use single-step mode without the debugger");
#endif
// Make sure that the cache can use an optimization by avoiding a branch to
// access the property storage.
static_assert(
HiddenClass::kDictionaryThreshold <=
SegmentedArray::kValueToSegmentThreshold,
"Cannot avoid branches in cache check if the dictionary "
"crossover point is larger than the inline storage");
CodeBlock *curCodeBlock = state.codeBlock;
const Inst *ip = nullptr;
// Points to the first local register in the current frame.
// This eliminates the indirect load from Runtime and the -1 offset.
PinnedHermesValue *frameRegs;
// Strictness of current function.
bool strictMode;
// Default flags when accessing properties.
PropOpFlags defaultPropOpFlags;
// These CAPTURE_IP* macros should wrap around any major calls out of the
// interpreter loop. They stash and retrieve the IP via the current Runtime
// allowing the IP to be externally observed and even altered to change the flow
// of execution. Explicitly saving AND restoring the IP from the Runtime in this
// way means the C++ compiler will keep IP in a register within the rest of the
// interpreter loop.
//
// When assertions are enabled we take the extra step of "invalidating" the IP
// between captures so we can detect if it's erroneously accessed.
//
#ifdef NDEBUG
#define CAPTURE_IP(expr) \
runtime.setCurrentIP(ip); \
(void)(expr); \
ip = runtime.getCurrentIP();
// Used when we want to declare a new variable and assign the expression to it.
#define CAPTURE_IP_ASSIGN(decl, expr) \
runtime.setCurrentIP(ip); \
decl = (expr); \
ip = runtime.getCurrentIP();
#else // !NDEBUG
#define CAPTURE_IP(expr) \
runtime.setCurrentIP(ip); \
(void)(expr); \
ip = runtime.getCurrentIP(); \
runtime.invalidateCurrentIP();
// Used when we want to declare a new variable and assign the expression to it.
#define CAPTURE_IP_ASSIGN(decl, expr) \
runtime.setCurrentIP(ip); \
decl = (expr); \
ip = runtime.getCurrentIP(); \
runtime.invalidateCurrentIP();
#endif // NDEBUG
/// \def DONT_CAPTURE_IP(expr)
/// \param expr A call expression to a function external to the interpreter. The
/// expression should not make any allocations and the IP should be set
/// immediately following this macro.
#define DONT_CAPTURE_IP(expr) \
do { \
NoAllocScope noAlloc(runtime); \
(void)expr; \
} while (false)
// When performing a tail call, we need to set the runtime IP and leave it set.
#define CAPTURE_IP_SET() runtime.setCurrentIP(ip)
LLVM_DEBUG(dbgs() << "interpretFunction() called\n");
ScopedNativeDepthTracker depthTracker{runtime};
if (LLVM_UNLIKELY(depthTracker.overflowed())) {
return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack);
}
GCScope gcScope(runtime);
// Avoid allocating a handle dynamically by reusing this one.
MutableHandle<> tmpHandle(runtime);
CallResult<HermesValue> res{ExecutionStatus::EXCEPTION};
CallResult<PseudoHandle<>> resPH{ExecutionStatus::EXCEPTION};
CallResult<Handle<Arguments>> resArgs{ExecutionStatus::EXCEPTION};
CallResult<bool> boolRes{ExecutionStatus::EXCEPTION};
// Start of the bytecode file, used to calculate IP offset in crash traces.
const uint8_t *bytecodeFileStart;
// Mark the gcScope so we can clear all allocated handles.
// Remember how many handles the scope has so we can clear them in the loop.
static constexpr unsigned KEEP_HANDLES = 1;
assert(
gcScope.getHandleCountDbg() == KEEP_HANDLES &&
"scope has unexpected number of handles");
INIT_OPCODE_PROFILER;
#if !defined(HERMESVM_PROFILER_EXTERN)
tailCall:
#endif
PROFILER_ENTER_FUNCTION(curCodeBlock);
#ifdef HERMES_ENABLE_DEBUGGER
runtime.getDebugger().willEnterCodeBlock(curCodeBlock);
#endif
runtime.getCodeCoverageProfiler().markExecuted(curCodeBlock);
if (!SingleStep) {
auto newFrame = runtime.setCurrentFrameToTopOfStack();
runtime.saveCallerIPInStackFrame();
#ifndef NDEBUG
runtime.invalidateCurrentIP();
#endif
// Point frameRegs to the first register in the new frame. Note that at this
// moment technically it points above the top of the stack, but we are never
// going to access it.
frameRegs = &newFrame.getFirstLocalRef();
#ifndef NDEBUG
LLVM_DEBUG(
dbgs() << "function entry: stackLevel=" << runtime.getStackLevel()
<< ", argCount=" << runtime.getCurrentFrame().getArgCount()
<< ", frameSize=" << curCodeBlock->getFrameSize() << "\n");
LLVM_DEBUG(
dbgs() << " callee "
<< DumpHermesValue(
runtime.getCurrentFrame().getCalleeClosureOrCBRef())
<< "\n");
LLVM_DEBUG(
dbgs() << " this "
<< DumpHermesValue(runtime.getCurrentFrame().getThisArgRef())
<< "\n");
for (uint32_t i = 0; i != runtime.getCurrentFrame()->getArgCount(); ++i) {
LLVM_DEBUG(
dbgs() << " " << llvh::format_decimal(i, 4) << " "
<< DumpHermesValue(runtime.getCurrentFrame().getArgRef(i))
<< "\n");
}
#endif
// Allocate the registers for the new frame.
if (LLVM_UNLIKELY(!runtime.checkAndAllocStack(
curCodeBlock->getFrameSize() +
StackFrameLayout::CalleeExtraRegistersAtStart,
HermesValue::encodeUndefinedValue())))
goto stackOverflow;
ip = (Inst const *)curCodeBlock->begin();
// Check for invalid invocation.
if (LLVM_UNLIKELY(curCodeBlock->getHeaderFlags().isCallProhibited(
newFrame.isConstructorCall()))) {
if (!newFrame.isConstructorCall()) {
CAPTURE_IP(
runtime.raiseTypeError("Class constructor invoked without new"));
} else {
CAPTURE_IP(runtime.raiseTypeError("Function is not a constructor"));
}
goto handleExceptionInParent;
}
} else {
// Point frameRegs to the first register in the frame.
frameRegs = &runtime.getCurrentFrame().getFirstLocalRef();
ip = (Inst const *)(curCodeBlock->begin() + state.offset);
}
assert((const uint8_t *)ip < curCodeBlock->end() && "CodeBlock is empty");
INIT_STATE_FOR_CODEBLOCK(curCodeBlock);
#define BEFORE_OP_CODE \
{ \
UPDATE_OPCODE_TIME_SPENT; \
HERMES_SLOW_ASSERT( \
curCodeBlock->contains(ip) && "curCodeBlock must contain ip"); \
HERMES_SLOW_ASSERT((printDebugInfo(curCodeBlock, frameRegs, ip), true)); \
HERMES_SLOW_ASSERT( \
gcScope.getHandleCountDbg() == KEEP_HANDLES && \
"unaccounted handles were created"); \
HERMES_SLOW_ASSERT(tmpHandle->isUndefined() && "tmpHandle not cleared"); \
RECORD_OPCODE_START_TIME; \
INC_OPCODE_COUNT; \
if (EnableCrashTrace) { \
runtime.crashTrace_.recordInst( \
(uint32_t)((const uint8_t *)ip - bytecodeFileStart), ip->opCode); \
} \
}
#ifdef HERMESVM_INDIRECT_THREADING
static void *opcodeDispatch[] = {
#define DEFINE_OPCODE(name) &&case_##name,
#include "hermes/BCGen/HBC/BytecodeList.def"
&&case__last};
#define CASE(name) case_##name:
// For indirect threading, there is no way to specify a default, leave it as
// an empty label.
#define DEFAULT_CASE
#define DISPATCH \
BEFORE_OP_CODE; \
if (SingleStep) { \
state.codeBlock = curCodeBlock; \
state.offset = CUROFFSET; \
return HermesValue::encodeUndefinedValue(); \
} \
goto *opcodeDispatch[(unsigned)ip->opCode]
#else // HERMESVM_INDIRECT_THREADING
#define CASE(name) case OpCode::name:
#define DEFAULT_CASE default:
#define DISPATCH \
if (SingleStep) { \
state.codeBlock = curCodeBlock; \
state.offset = CUROFFSET; \
return HermesValue::encodeUndefinedValue(); \
} \
continue
#endif // HERMESVM_INDIRECT_THREADING
#define RUN_DEBUGGER_ASYNC_BREAK(flags) \
do { \
CAPTURE_IP_ASSIGN( \
auto dRes, \
runDebuggerUpdatingState( \
(uint8_t)(flags) & \
(uint8_t)Runtime::AsyncBreakReasonBits::DebuggerExplicit \
? Debugger::RunReason::AsyncBreakExplicit \
: Debugger::RunReason::AsyncBreakImplicit, \
runtime, \
curCodeBlock, \
ip, \
frameRegs)); \
if (dRes == ExecutionStatus::EXCEPTION) \
goto exception; \
} while (0)
for (;;) {
BEFORE_OP_CODE;
#ifdef HERMESVM_INDIRECT_THREADING
goto *opcodeDispatch[(unsigned)ip->opCode];
#else
switch (ip->opCode)
#endif
{
const Inst *nextIP;
uint32_t idVal;
bool tryProp;
uint32_t callArgCount;
// This is HermesValue::getRaw(), since HermesValue cannot be assigned
// to. It is meant to be used only for very short durations, in the
// dispatch of call instructions, when there is definitely no possibility
// of a GC.
HermesValue::RawType callNewTarget;
/// Handle an opcode \p name with an out-of-line implementation in a function
/// ExecutionStatus caseName(
/// Runtime &,
/// PinnedHermesValue *frameRegs,
/// Inst *ip)
#define CASE_OUTOFLINE(name) \
CASE(name) { \
CAPTURE_IP_ASSIGN(auto res, case##name(runtime, frameRegs, ip)); \
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { \
goto exception; \
} \
gcScope.flushToSmallCount(KEEP_HANDLES); \
ip = NEXTINST(name); \
DISPATCH; \
}
/// Implement a binary arithmetic instruction with a fast path where both
/// operands are numbers.
/// \param name the name of the instruction. The fast path case will have a
/// "n" appended to the name.
/// \param oper the C++ operator to use to actually perform the arithmetic
/// operation.
#define BINOP(name, oper) \
CASE(name) { \
if (LLVM_LIKELY(O2REG(name).isNumber() && O3REG(name).isNumber())) { \
/* Fast-path. */ \
CASE(name##N) { \
O1REG(name) = HermesValue::encodeDoubleValue( \
oper(O2REG(name).getNumber(), O3REG(name).getNumber())); \
ip = NEXTINST(name); \
DISPATCH; \
} \
} \
CAPTURE_IP(res = toNumber_RJS(runtime, Handle<>(&O2REG(name)))); \
if (res == ExecutionStatus::EXCEPTION) \
goto exception; \
double left = res->getDouble(); \
CAPTURE_IP(res = toNumber_RJS(runtime, Handle<>(&O3REG(name)))); \
if (res == ExecutionStatus::EXCEPTION) \
goto exception; \
O1REG(name) = \
HermesValue::encodeDoubleValue(oper(left, res->getDouble())); \
gcScope.flushToSmallCount(KEEP_HANDLES); \
ip = NEXTINST(name); \
DISPATCH; \
}
#define INCDECOP(name, oper) \
CASE(name) { \
O1REG(name) = \
HermesValue::encodeDoubleValue(O2REG(name).getNumber() oper 1); \
ip = NEXTINST(name); \
DISPATCH; \
}
/// Implement a shift instruction with a fast path where both
/// operands are numbers.
/// \param name the name of the instruction.
/// \param oper the C++ operator to use to actually perform the shift
/// operation.
/// \param lConv the conversion function for the LHS of the expression.
/// \param lType the type of the LHS operand.
/// \param returnType the type of the return value.
#define SHIFTOP(name, oper, lConv, lType, returnType) \
CASE(name) { \
if (LLVM_LIKELY( \
O2REG(name).isNumber() && \
O3REG(name).isNumber())) { /* Fast-path. */ \
auto lnum = static_cast<lType>( \
hermes::truncateToInt32(O2REG(name).getNumber())); \
auto rnum = static_cast<uint32_t>( \
hermes::truncateToInt32(O3REG(name).getNumber())) & \
0x1f; \
O1REG(name) = HermesValue::encodeDoubleValue( \
static_cast<returnType>(lnum oper rnum)); \
ip = NEXTINST(name); \
DISPATCH; \
} \
CAPTURE_IP(res = lConv(runtime, Handle<>(&O2REG(name)))); \
if (res == ExecutionStatus::EXCEPTION) { \
goto exception; \
} \
auto lnum = static_cast<lType>(res->getNumber()); \
CAPTURE_IP(res = toUInt32_RJS(runtime, Handle<>(&O3REG(name)))); \
if (res == ExecutionStatus::EXCEPTION) { \
goto exception; \
} \
auto rnum = static_cast<uint32_t>(res->getNumber()) & 0x1f; \
gcScope.flushToSmallCount(KEEP_HANDLES); \
O1REG(name) = HermesValue::encodeDoubleValue( \
static_cast<returnType>(lnum oper rnum)); \
ip = NEXTINST(name); \
DISPATCH; \
}
/// Implement a binary bitwise instruction with a fast path where both
/// operands are numbers.
/// \param name the name of the instruction.
/// \param oper the C++ operator to use to actually perform the bitwise
/// operation.
#define BITWISEBINOP(name, oper) \
CASE(name) { \
if (LLVM_LIKELY(O2REG(name).isNumber() && O3REG(name).isNumber())) { \
/* Fast-path. */ \
O1REG(name) = HermesValue::encodeDoubleValue( \
hermes::truncateToInt32(O2REG(name).getNumber()) \
oper hermes::truncateToInt32(O3REG(name).getNumber())); \
ip = NEXTINST(name); \
DISPATCH; \
} \
CAPTURE_IP(res = toInt32_RJS(runtime, Handle<>(&O2REG(name)))); \
if (res == ExecutionStatus::EXCEPTION) { \
goto exception; \
} \
int32_t left = res->getNumberAs<int32_t>(); \
CAPTURE_IP(res = toInt32_RJS(runtime, Handle<>(&O3REG(name)))); \
if (res == ExecutionStatus::EXCEPTION) { \
goto exception; \
} \
O1REG(name) = \
HermesValue::encodeNumberValue(left oper res->getNumberAs<int32_t>()); \
gcScope.flushToSmallCount(KEEP_HANDLES); \
ip = NEXTINST(name); \
DISPATCH; \
}
/// Implement a comparison instruction.
/// \param name the name of the instruction.
/// \param oper the C++ operator to use to actually perform the fast arithmetic
/// comparison.
/// \param operFuncName function to call for the slow-path comparison.
#define CONDOP(name, oper, operFuncName) \
CASE(name) { \
if (LLVM_LIKELY(O2REG(name).isNumber() && O3REG(name).isNumber())) { \
/* Fast-path. */ \
O1REG(name) = HermesValue::encodeBoolValue( \
O2REG(name).getNumber() oper O3REG(name).getNumber()); \
ip = NEXTINST(name); \
DISPATCH; \
} \
CAPTURE_IP( \
boolRes = operFuncName( \
runtime, Handle<>(&O2REG(name)), Handle<>(&O3REG(name)))); \
if (boolRes == ExecutionStatus::EXCEPTION) \
goto exception; \
gcScope.flushToSmallCount(KEEP_HANDLES); \
O1REG(name) = HermesValue::encodeBoolValue(boolRes.getValue()); \
ip = NEXTINST(name); \
DISPATCH; \
}
/// Implement a comparison conditional jump with a fast path where both
/// operands are numbers.
/// \param name the name of the instruction. The fast path case will have a
/// "N" appended to the name.
/// \param suffix Optional suffix to be added to the end (e.g. Long)
/// \param oper the C++ operator to use to actually perform the fast arithmetic
/// comparison.
/// \param operFuncName function to call for the slow-path comparison.
/// \param trueDest ip value if the conditional evaluates to true
/// \param falseDest ip value if the conditional evaluates to false
#define JCOND_IMPL(name, suffix, oper, operFuncName, trueDest, falseDest) \
CASE(name##suffix) { \
if (LLVM_LIKELY( \
O2REG(name##suffix).isNumber() && \
O3REG(name##suffix).isNumber())) { \
/* Fast-path. */ \
CASE(name##N##suffix) { \
if (O2REG(name##N##suffix) \
.getNumber() oper O3REG(name##N##suffix) \
.getNumber()) { \
ip = trueDest; \
DISPATCH; \
} \
ip = falseDest; \
DISPATCH; \
} \
} \
CAPTURE_IP( \
boolRes = operFuncName( \
runtime, \
Handle<>(&O2REG(name##suffix)), \
Handle<>(&O3REG(name##suffix)))); \
if (boolRes == ExecutionStatus::EXCEPTION) \
goto exception; \
gcScope.flushToSmallCount(KEEP_HANDLES); \
if (boolRes.getValue()) { \
ip = trueDest; \
DISPATCH; \
} \
ip = falseDest; \
DISPATCH; \
}
/// Implement a strict equality conditional jump
/// \param name the name of the instruction.
/// \param suffix Optional suffix to be added to the end (e.g. Long)
/// \param trueDest ip value if the conditional evaluates to true
/// \param falseDest ip value if the conditional evaluates to false
#define JCOND_STRICT_EQ_IMPL(name, suffix, trueDest, falseDest) \
CASE(name##suffix) { \
if (strictEqualityTest(O2REG(name##suffix), O3REG(name##suffix))) { \
ip = trueDest; \
DISPATCH; \
} \
ip = falseDest; \
DISPATCH; \
}
/// Implement an equality conditional jump
/// \param name the name of the instruction.
/// \param suffix Optional suffix to be added to the end (e.g. Long)
/// \param trueDest ip value if the conditional evaluates to true
/// \param falseDest ip value if the conditional evaluates to false
#define JCOND_EQ_IMPL(name, suffix, trueDest, falseDest) \
CASE(name##suffix) { \
CAPTURE_IP_ASSIGN( \
auto eqRes, \
abstractEqualityTest_RJS( \
runtime, \
Handle<>(&O2REG(name##suffix)), \
Handle<>(&O3REG(name##suffix)))); \
if (eqRes == ExecutionStatus::EXCEPTION) { \
goto exception; \
} \
gcScope.flushToSmallCount(KEEP_HANDLES); \
if (*eqRes) { \
ip = trueDest; \
DISPATCH; \
} \
ip = falseDest; \
DISPATCH; \
}
/// Implement the long and short forms of a conditional jump, and its negation.
#define JCOND(name, oper, operFuncName) \
JCOND_IMPL( \
J##name, \
, \
oper, \
operFuncName, \
IPADD(ip->iJ##name.op1), \
NEXTINST(J##name)); \
JCOND_IMPL( \
J##name, \
Long, \
oper, \
operFuncName, \
IPADD(ip->iJ##name##Long.op1), \
NEXTINST(J##name##Long)); \
JCOND_IMPL( \
JNot##name, \
, \
oper, \
operFuncName, \
NEXTINST(JNot##name), \
IPADD(ip->iJNot##name.op1)); \
JCOND_IMPL( \
JNot##name, \
Long, \
oper, \
operFuncName, \
NEXTINST(JNot##name##Long), \
IPADD(ip->iJNot##name##Long.op1));
/// Load a constant.
/// \param value is the value to store in the output register.
#define LOAD_CONST(name, value) \
CASE(name) { \
O1REG(name) = value; \
ip = NEXTINST(name); \
DISPATCH; \
}
#define LOAD_CONST_CAPTURE_IP(name, value) \
CASE(name) { \
CAPTURE_IP(O1REG(name) = value); \
ip = NEXTINST(name); \
DISPATCH; \
}
CASE(Mov) {
O1REG(Mov) = O2REG(Mov);
ip = NEXTINST(Mov);
DISPATCH;
}
CASE(MovLong) {
O1REG(MovLong) = O2REG(MovLong);
ip = NEXTINST(MovLong);
DISPATCH;
}
CASE(LoadParam) {
if (LLVM_LIKELY(ip->iLoadParam.op2 <= FRAME.getArgCount())) {
// index 0 must load 'this'. Index 1 the first argument, etc.
O1REG(LoadParam) = FRAME.getArgRef((int32_t)ip->iLoadParam.op2 - 1);
ip = NEXTINST(LoadParam);
DISPATCH;
}
O1REG(LoadParam) = HermesValue::encodeUndefinedValue();
ip = NEXTINST(LoadParam);
DISPATCH;
}
CASE(LoadParamLong) {
if (LLVM_LIKELY(ip->iLoadParamLong.op2 <= FRAME.getArgCount())) {
// index 0 must load 'this'. Index 1 the first argument, etc.
O1REG(LoadParamLong) =
FRAME.getArgRef((int32_t)ip->iLoadParamLong.op2 - 1);
ip = NEXTINST(LoadParamLong);
DISPATCH;
}
O1REG(LoadParamLong) = HermesValue::encodeUndefinedValue();
ip = NEXTINST(LoadParamLong);
DISPATCH;
}
CASE(CoerceThisNS) {
if (LLVM_LIKELY(O2REG(CoerceThisNS).isObject())) {
O1REG(CoerceThisNS) = O2REG(CoerceThisNS);
} else if (
O2REG(CoerceThisNS).isNull() || O2REG(CoerceThisNS).isUndefined()) {
O1REG(CoerceThisNS) = runtime.global_;
} else {
tmpHandle = O2REG(CoerceThisNS);
nextIP = NEXTINST(CoerceThisNS);
goto coerceThisSlowPath;
}
ip = NEXTINST(CoerceThisNS);
DISPATCH;
}
CASE(LoadThisNS) {
if (LLVM_LIKELY(FRAME.getThisArgRef().isObject())) {
O1REG(LoadThisNS) = FRAME.getThisArgRef();
} else if (
FRAME.getThisArgRef().isNull() ||
FRAME.getThisArgRef().isUndefined()) {
O1REG(LoadThisNS) = runtime.global_;
} else {
tmpHandle = FRAME.getThisArgRef();
nextIP = NEXTINST(LoadThisNS);
goto coerceThisSlowPath;
}
ip = NEXTINST(LoadThisNS);
DISPATCH;
}
coerceThisSlowPath : {
CAPTURE_IP(res = toObject(runtime, tmpHandle));
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(CoerceThisNS) = res.getValue();
tmpHandle.clear();
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = nextIP;
DISPATCH;
}
CASE(ConstructLong) {
callArgCount = (uint32_t)ip->iConstructLong.op3;
nextIP = NEXTINST(ConstructLong);
callNewTarget = O2REG(ConstructLong).getRaw();
goto doCall;
}
CASE(CallLong) {
callArgCount = (uint32_t)ip->iCallLong.op3;
nextIP = NEXTINST(CallLong);
callNewTarget = HermesValue::encodeUndefinedValue().getRaw();
goto doCall;
}
// Note in Call1 through Call4, the first argument is 'this' which has
// argument index -1.
// Also note that we are writing to callNewTarget last, to avoid the
// possibility of it being aliased by the arg writes.
CASE(Call1) {
callArgCount = 1;
nextIP = NEXTINST(Call1);
StackFramePtr fr{runtime.stackPointer_};
fr.getArgRefUnsafe(-1) = O3REG(Call1);
callNewTarget = HermesValue::encodeUndefinedValue().getRaw();
goto doCall;
}
CASE(Call2) {
callArgCount = 2;
nextIP = NEXTINST(Call2);
StackFramePtr fr{runtime.stackPointer_};
fr.getArgRefUnsafe(-1) = O3REG(Call2);
fr.getArgRefUnsafe(0) = O4REG(Call2);
callNewTarget = HermesValue::encodeUndefinedValue().getRaw();
goto doCall;
}
CASE(Call3) {
callArgCount = 3;
nextIP = NEXTINST(Call3);
StackFramePtr fr{runtime.stackPointer_};
fr.getArgRefUnsafe(-1) = O3REG(Call3);
fr.getArgRefUnsafe(0) = O4REG(Call3);
fr.getArgRefUnsafe(1) = O5REG(Call3);
callNewTarget = HermesValue::encodeUndefinedValue().getRaw();
goto doCall;
}
CASE(Call4) {
callArgCount = 4;
nextIP = NEXTINST(Call4);
StackFramePtr fr{runtime.stackPointer_};
fr.getArgRefUnsafe(-1) = O3REG(Call4);
fr.getArgRefUnsafe(0) = O4REG(Call4);
fr.getArgRefUnsafe(1) = O5REG(Call4);
fr.getArgRefUnsafe(2) = O6REG(Call4);
callNewTarget = HermesValue::encodeUndefinedValue().getRaw();
goto doCall;
}
CASE(Construct) {
callArgCount = (uint32_t)ip->iConstruct.op3;
nextIP = NEXTINST(Construct);
callNewTarget = O2REG(Construct).getRaw();
goto doCall;
}
CASE(Call) {
callArgCount = (uint32_t)ip->iCall.op3;
nextIP = NEXTINST(Call);
callNewTarget = HermesValue::encodeUndefinedValue().getRaw();
goto doCall;
}
doCall : {
#ifdef HERMES_ENABLE_DEBUGGER
// Check for an async debugger request.
if (uint8_t asyncFlags =
runtime.testAndClearDebuggerAsyncBreakRequest()) {
RUN_DEBUGGER_ASYNC_BREAK(asyncFlags);
gcScope.flushToSmallCount(KEEP_HANDLES);
DISPATCH;
}
#endif
// Subtract 1 from callArgCount as 'this' is considered an argument in the
// instruction, but not in the frame.
auto newFrame = StackFramePtr::initFrame(
runtime.stackPointer_,
FRAME,
ip,
curCodeBlock,
callArgCount - 1,
O2REG(Call),
HermesValue::fromRaw(callNewTarget));
(void)newFrame;
SLOW_DEBUG(dumpCallArguments(dbgs(), runtime, newFrame));
if (auto *func = dyn_vmcast<JSFunction>(O2REG(Call))) {
assert(!SingleStep && "can't single-step a call");
#ifdef HERMES_ENABLE_ALLOCATION_LOCATION_TRACES
runtime.pushCallStack(curCodeBlock, ip);
#endif
CodeBlock *calleeBlock = func->getCodeBlock();
CAPTURE_IP(calleeBlock->lazyCompile(runtime));
#if defined(HERMESVM_PROFILER_EXTERN)
CAPTURE_IP(res = runtime.interpretFunction(calleeBlock));
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(Call) = *res;
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = nextIP;
DISPATCH;
#else
curCodeBlock = calleeBlock;
CAPTURE_IP_SET();
goto tailCall;
#endif
}
CAPTURE_IP(
resPH = Interpreter::handleCallSlowPath(runtime, &O2REG(Call)));
if (LLVM_UNLIKELY(resPH == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(Call) = std::move(resPH->get());
SLOW_DEBUG(
dbgs() << "native return value r" << (unsigned)ip->iCall.op1 << "="
<< DumpHermesValue(O1REG(Call)) << "\n");
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = nextIP;
DISPATCH;
}
CASE(CallDirect)
CASE(CallDirectLongIndex) {
#ifdef HERMES_ENABLE_DEBUGGER
// Check for an async debugger request.
if (uint8_t asyncFlags =
runtime.testAndClearDebuggerAsyncBreakRequest()) {
RUN_DEBUGGER_ASYNC_BREAK(asyncFlags);
gcScope.flushToSmallCount(KEEP_HANDLES);
DISPATCH;
}
#endif
CAPTURE_IP_ASSIGN(
CodeBlock * calleeBlock,
ip->opCode == OpCode::CallDirect
? curCodeBlock->getRuntimeModule()->getCodeBlockMayAllocate(
ip->iCallDirect.op3)
: curCodeBlock->getRuntimeModule()->getCodeBlockMayAllocate(
ip->iCallDirectLongIndex.op3));
auto newFrame = StackFramePtr::initFrame(
runtime.stackPointer_,
FRAME,
ip,
curCodeBlock,
(uint32_t)ip->iCallDirect.op2 - 1,
HermesValue::encodeNativePointer(calleeBlock),
HermesValue::encodeUndefinedValue());
(void)newFrame;
LLVM_DEBUG(dumpCallArguments(dbgs(), runtime, newFrame));
assert(!SingleStep && "can't single-step a call");
CAPTURE_IP(calleeBlock->lazyCompile(runtime));
#if defined(HERMESVM_PROFILER_EXTERN)
CAPTURE_IP(res = runtime.interpretFunction(calleeBlock));
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(CallDirect) = *res;
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = ip->opCode == OpCode::CallDirect ? NEXTINST(CallDirect)
: NEXTINST(CallDirectLongIndex);
DISPATCH;
#else
curCodeBlock = calleeBlock;
CAPTURE_IP_SET();
goto tailCall;
#endif
}
CASE(GetBuiltinClosure) {
uint8_t methodIndex = ip->iCallBuiltin.op2;
Callable *closure = runtime.getBuiltinCallable(methodIndex);
O1REG(GetBuiltinClosure) = HermesValue::encodeObjectValue(closure);
ip = NEXTINST(GetBuiltinClosure);
DISPATCH;
}
CASE(CallBuiltin) {
CAPTURE_IP_ASSIGN(
auto cres,
implCallBuiltin(
runtime, frameRegs, curCodeBlock, ip->iCallBuiltin.op3));
if (LLVM_UNLIKELY(cres == ExecutionStatus::EXCEPTION))
goto exception;
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(CallBuiltin);
DISPATCH;
}
CASE(CallBuiltinLong) {
CAPTURE_IP_ASSIGN(
auto cres,
implCallBuiltin(
runtime, frameRegs, curCodeBlock, ip->iCallBuiltinLong.op3));
if (LLVM_UNLIKELY(cres == ExecutionStatus::EXCEPTION))
goto exception;
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(CallBuiltinLong);
DISPATCH;
}
CASE(CompleteGenerator) {
auto *innerFn = vmcast<GeneratorInnerFunction>(
runtime.getCurrentFrame().getCalleeClosureUnsafe());
innerFn->setState(GeneratorInnerFunction::State::Completed);
ip = NEXTINST(CompleteGenerator);
DISPATCH;
}
CASE(SaveGenerator) {
DONT_CAPTURE_IP(
saveGenerator(runtime, frameRegs, IPADD(ip->iSaveGenerator.op1)));
ip = NEXTINST(SaveGenerator);
DISPATCH;
}
CASE(SaveGeneratorLong) {
DONT_CAPTURE_IP(saveGenerator(
runtime, frameRegs, IPADD(ip->iSaveGeneratorLong.op1)));
ip = NEXTINST(SaveGeneratorLong);
DISPATCH;
}
CASE(StartGenerator) {
auto *innerFn = vmcast<GeneratorInnerFunction>(
runtime.getCurrentFrame().getCalleeClosureUnsafe());
if (innerFn->getState() ==
GeneratorInnerFunction::State::SuspendedStart) {
nextIP = NEXTINST(StartGenerator);
} else {
nextIP = innerFn->getNextIP();
innerFn->restoreStack(runtime);
}
innerFn->setState(GeneratorInnerFunction::State::Executing);
ip = nextIP;
DISPATCH;
}
CASE(ResumeGenerator) {
auto *innerFn = vmcast<GeneratorInnerFunction>(
runtime.getCurrentFrame().getCalleeClosureUnsafe());
O1REG(ResumeGenerator) = innerFn->getResult().unboxToHV(runtime);
O2REG(ResumeGenerator) = HermesValue::encodeBoolValue(
innerFn->getAction() == GeneratorInnerFunction::Action::Return);
innerFn->clearResult(runtime);
if (innerFn->getAction() == GeneratorInnerFunction::Action::Throw) {
runtime.setThrownValue(O1REG(ResumeGenerator));
goto exception;
}
ip = NEXTINST(ResumeGenerator);
DISPATCH;
}
CASE(Ret) {
#ifdef HERMES_ENABLE_DEBUGGER
// Check for an async debugger request.
if (uint8_t asyncFlags =
runtime.testAndClearDebuggerAsyncBreakRequest()) {
RUN_DEBUGGER_ASYNC_BREAK(asyncFlags);
gcScope.flushToSmallCount(KEEP_HANDLES);
DISPATCH;
}
#endif
PROFILER_EXIT_FUNCTION(curCodeBlock);
#ifdef HERMES_ENABLE_ALLOCATION_LOCATION_TRACES
runtime.popCallStack();
#endif
// Store the return value.
res = O1REG(Ret);
ip = FRAME.getSavedIP();
curCodeBlock = FRAME.getSavedCodeBlock();
frameRegs =
&runtime.restoreStackAndPreviousFrame(FRAME).getFirstLocalRef();
SLOW_DEBUG(
dbgs() << "function exit: restored stackLevel="
<< runtime.getStackLevel() << "\n");
// Are we returning to native code?
if (!curCodeBlock) {
SLOW_DEBUG(dbgs() << "function exit: returning to native code\n");
return res;
}
// Return because of recursive calling structure
#if defined(HERMESVM_PROFILER_EXTERN)
return res;
#endif
INIT_STATE_FOR_CODEBLOCK(curCodeBlock);
O1REG(Call) = res.getValue();
ip = nextInstCall(ip);
DISPATCH;
}
CASE(Catch) {
assert(!runtime.thrownValue_.isEmpty() && "Invalid thrown value");
assert(
!isUncatchableError(runtime.thrownValue_) &&
"Uncatchable thrown value was caught");
O1REG(Catch) = runtime.thrownValue_;
runtime.clearThrownValue();
#ifdef HERMES_ENABLE_DEBUGGER
// Signal to the debugger that we're done unwinding an exception,
// and we can resume normal debugging flow.
runtime.debugger_.finishedUnwindingException();
#endif
ip = NEXTINST(Catch);
DISPATCH;
}
CASE(Throw) {
runtime.thrownValue_ = O1REG(Throw);
SLOW_DEBUG(
dbgs() << "Exception thrown: "
<< DumpHermesValue(runtime.thrownValue_) << "\n");
goto exception;
}
CASE(ThrowIfEmpty) {
if (LLVM_UNLIKELY(O2REG(ThrowIfEmpty).isEmpty())) {
SLOW_DEBUG(dbgs() << "Throwing ReferenceError for empty variable");
CAPTURE_IP(runtime.raiseReferenceError(
"accessing an uninitialized variable"));
goto exception;
}
O1REG(ThrowIfEmpty) = O2REG(ThrowIfEmpty);
ip = NEXTINST(ThrowIfEmpty);
DISPATCH;
}
CASE(Debugger) {
SLOW_DEBUG(dbgs() << "debugger statement executed\n");
#ifdef HERMES_ENABLE_DEBUGGER
{
if (!runtime.debugger_.isDebugging()) {
// Only run the debugger if we're not already debugging.
// Don't want to call it again and mess with its state.
CAPTURE_IP_ASSIGN(
auto res,
runDebuggerUpdatingState(
Debugger::RunReason::Opcode,
runtime,
curCodeBlock,
ip,
frameRegs));
if (res == ExecutionStatus::EXCEPTION) {
// If one of the internal steps threw,
// then handle that here by jumping to where we're supposed to go.
// If we're in mid-step, the breakpoint at the catch point
// will have been set by the debugger.
// We don't want to execute this instruction because it's already
// thrown.
goto exception;
}
}
auto breakpointOpt = runtime.debugger_.getBreakpointLocation(ip);
if (breakpointOpt.hasValue()) {
// We're on a breakpoint but we're supposed to continue.
curCodeBlock->uninstallBreakpointAtOffset(
CUROFFSET, breakpointOpt->opCode);
if (ip->opCode == OpCode::Debugger) {
// Breakpointed a debugger instruction, so move past it
// since we've already called the debugger on this instruction.
ip = NEXTINST(Debugger);
} else {
InterpreterState newState{curCodeBlock, (uint32_t)CUROFFSET};
CAPTURE_IP_ASSIGN(
ExecutionStatus status, runtime.stepFunction(newState));
curCodeBlock->installBreakpointAtOffset(CUROFFSET);
if (status == ExecutionStatus::EXCEPTION) {
goto exception;
}
curCodeBlock = newState.codeBlock;
ip = newState.codeBlock->getOffsetPtr(newState.offset);
INIT_STATE_FOR_CODEBLOCK(curCodeBlock);
// Single-stepping should handle call stack management for us.
frameRegs = &runtime.getCurrentFrame().getFirstLocalRef();
}
} else if (ip->opCode == OpCode::Debugger) {
// No breakpoint here and we've already run the debugger,
// just continue on.
// If the current instruction is no longer a debugger instruction,
// we're just going to keep executing from the current IP.
ip = NEXTINST(Debugger);
}
gcScope.flushToSmallCount(KEEP_HANDLES);
}
DISPATCH;
#else
ip = NEXTINST(Debugger);
DISPATCH;
#endif
}
CASE(AsyncBreakCheck) {
if (LLVM_UNLIKELY(runtime.hasAsyncBreak())) {
#ifdef HERMES_ENABLE_DEBUGGER
if (uint8_t asyncFlags =
runtime.testAndClearDebuggerAsyncBreakRequest()) {
RUN_DEBUGGER_ASYNC_BREAK(asyncFlags);
}
#endif
if (runtime.testAndClearTimeoutAsyncBreakRequest()) {
CAPTURE_IP_ASSIGN(auto nRes, runtime.notifyTimeout());
if (nRes == ExecutionStatus::EXCEPTION) {
goto exception;
}
}
}
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(AsyncBreakCheck);
DISPATCH;
}
CASE(ProfilePoint) {
#ifdef HERMESVM_PROFILER_BB
auto pointIndex = ip->iProfilePoint.op1;
SLOW_DEBUG(llvh::dbgs() << "ProfilePoint: " << pointIndex << "\n");
CAPTURE_IP(runtime.getBasicBlockExecutionInfo().executeBlock(
curCodeBlock, pointIndex));
#endif
ip = NEXTINST(ProfilePoint);
DISPATCH;
}
// Use a macro here to avoid clang-format issues with a literal default:
// label.
DEFAULT_CASE
CASE(Unreachable) {
hermes_fatal("Unreachable instruction encountered");
// The fatal call doesn't return, no need to set the IP differently and
// dispatch.
}
CASE(CreateClosure) {
idVal = ip->iCreateClosure.op3;
nextIP = NEXTINST(CreateClosure);
goto createClosure;
}
CASE(CreateClosureLongIndex) {
idVal = ip->iCreateClosureLongIndex.op3;
nextIP = NEXTINST(CreateClosureLongIndex);
goto createClosure;
}
createClosure : {
auto *runtimeModule = curCodeBlock->getRuntimeModule();
CAPTURE_IP(
O1REG(CreateClosure) =
JSFunction::create(
runtime,
runtimeModule->getDomain(runtime),
Handle<JSObject>::vmcast(&runtime.functionPrototype),
Handle<Environment>::vmcast(&O2REG(CreateClosure)),
runtimeModule->getCodeBlockMayAllocate(idVal))
.getHermesValue());
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = nextIP;
DISPATCH;
}
CASE(CreateAsyncClosure) {
idVal = ip->iCreateAsyncClosure.op3;
nextIP = NEXTINST(CreateAsyncClosure);
goto createAsyncClosure;
}
CASE(CreateAsyncClosureLongIndex) {
idVal = ip->iCreateAsyncClosureLongIndex.op3;
nextIP = NEXTINST(CreateAsyncClosureLongIndex);
goto createAsyncClosure;
}
createAsyncClosure : {
auto *runtimeModule = curCodeBlock->getRuntimeModule();
CAPTURE_IP_ASSIGN(
O1REG(CreateAsyncClosure),
JSAsyncFunction::create(
runtime,
runtimeModule->getDomain(runtime),
Handle<JSObject>::vmcast(&runtime.asyncFunctionPrototype),
Handle<Environment>::vmcast(&O2REG(CreateAsyncClosure)),
runtimeModule->getCodeBlockMayAllocate(idVal))
.getHermesValue());
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = nextIP;
DISPATCH;
}
CASE(CreateGeneratorClosure) {
idVal = ip->iCreateGeneratorClosure.op3;
nextIP = NEXTINST(CreateGeneratorClosure);
goto createGeneratorClosure;
}
CASE(CreateGeneratorClosureLongIndex) {
idVal = ip->iCreateGeneratorClosureLongIndex.op3;
nextIP = NEXTINST(CreateGeneratorClosureLongIndex);
goto createGeneratorClosure;
}
createGeneratorClosure : {
auto *runtimeModule = curCodeBlock->getRuntimeModule();
CAPTURE_IP_ASSIGN(
O1REG(CreateGeneratorClosure),
JSGeneratorFunction::create(
runtime,
runtimeModule->getDomain(runtime),
Handle<JSObject>::vmcast(&runtime.generatorFunctionPrototype),
Handle<Environment>::vmcast(&O2REG(CreateGeneratorClosure)),
runtimeModule->getCodeBlockMayAllocate(idVal))
.getHermesValue());
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = nextIP;
DISPATCH;
}
CASE(CreateGenerator) {
CAPTURE_IP_ASSIGN(
auto res,
createGenerator_RJS(
runtime,
curCodeBlock->getRuntimeModule(),
ip->iCreateGenerator.op3,
Handle<Environment>::vmcast(&O2REG(CreateGenerator)),
FRAME.getNativeArgs()));
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(CreateGenerator) = res->getHermesValue();
res->invalidate();
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(CreateGenerator);
DISPATCH;
}
CASE(CreateGeneratorLongIndex) {
CAPTURE_IP_ASSIGN(
auto res,
createGenerator_RJS(
runtime,
curCodeBlock->getRuntimeModule(),
ip->iCreateGeneratorLongIndex.op3,
Handle<Environment>::vmcast(&O2REG(CreateGeneratorLongIndex)),
FRAME.getNativeArgs()));
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(CreateGeneratorLongIndex) = res->getHermesValue();
res->invalidate();
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(CreateGeneratorLongIndex);
DISPATCH;
}
CASE(GetEnvironment) {
// The currently executing function must exist, so get the environment.
Environment *curEnv =
FRAME.getCalleeClosureUnsafe()->getEnvironment(runtime);
for (unsigned level = ip->iGetEnvironment.op2; level; --level) {
assert(curEnv && "invalid environment relative level");
curEnv = curEnv->getParentEnvironment(runtime);
}
O1REG(GetEnvironment) = HermesValue::encodeObjectValue(curEnv);
ip = NEXTINST(GetEnvironment);
DISPATCH;
}
CASE(CreateEnvironment) {
tmpHandle = HermesValue::encodeObjectValueUnsafe(
FRAME.getCalleeClosureUnsafe()->getEnvironment(runtime));
CAPTURE_IP(
res = Environment::create(
runtime,
Handle<Environment>::vmcast_or_null(tmpHandle),
curCodeBlock->getEnvironmentSize()));
if (res == ExecutionStatus::EXCEPTION) {
goto exception;
}
O1REG(CreateEnvironment) = *res;
#ifdef HERMES_ENABLE_DEBUGGER
FRAME.getDebugEnvironmentRef() = *res;
#endif
tmpHandle = HermesValue::encodeUndefinedValue();
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(CreateEnvironment);
DISPATCH;
}
CASE(StoreToEnvironment) {
vmcast<Environment>(O1REG(StoreToEnvironment))
->slot(ip->iStoreToEnvironment.op2)
.set(O3REG(StoreToEnvironment), &runtime.getHeap());
ip = NEXTINST(StoreToEnvironment);
DISPATCH;
}
CASE(StoreToEnvironmentL) {
vmcast<Environment>(O1REG(StoreToEnvironmentL))
->slot(ip->iStoreToEnvironmentL.op2)
.set(O3REG(StoreToEnvironmentL), &runtime.getHeap());
ip = NEXTINST(StoreToEnvironmentL);
DISPATCH;
}
CASE(StoreNPToEnvironment) {
vmcast<Environment>(O1REG(StoreNPToEnvironment))
->slot(ip->iStoreNPToEnvironment.op2)
.setNonPtr(O3REG(StoreNPToEnvironment), &runtime.getHeap());
ip = NEXTINST(StoreNPToEnvironment);
DISPATCH;
}
CASE(StoreNPToEnvironmentL) {
vmcast<Environment>(O1REG(StoreNPToEnvironmentL))
->slot(ip->iStoreNPToEnvironmentL.op2)
.setNonPtr(O3REG(StoreNPToEnvironmentL), &runtime.getHeap());
ip = NEXTINST(StoreNPToEnvironmentL);
DISPATCH;
}
CASE(LoadFromEnvironment) {
O1REG(LoadFromEnvironment) =
vmcast<Environment>(O2REG(LoadFromEnvironment))
->slot(ip->iLoadFromEnvironment.op3);
ip = NEXTINST(LoadFromEnvironment);
DISPATCH;
}
CASE(LoadFromEnvironmentL) {
O1REG(LoadFromEnvironmentL) =
vmcast<Environment>(O2REG(LoadFromEnvironmentL))
->slot(ip->iLoadFromEnvironmentL.op3);
ip = NEXTINST(LoadFromEnvironmentL);
DISPATCH;
}
CASE(GetGlobalObject) {
O1REG(GetGlobalObject) = runtime.global_;
ip = NEXTINST(GetGlobalObject);
DISPATCH;
}
CASE(GetNewTarget) {
O1REG(GetNewTarget) = FRAME.getNewTargetRef();
ip = NEXTINST(GetNewTarget);
DISPATCH;
}
CASE(DeclareGlobalVar) {
DefinePropertyFlags dpf =
DefinePropertyFlags::getDefaultNewPropertyFlags();
dpf.configurable = 0;
// Do not overwrite existing globals with undefined.
dpf.setValue = 0;
CAPTURE_IP_ASSIGN(
auto res,
JSObject::defineOwnProperty(
runtime.getGlobal(),
runtime,
ID(ip->iDeclareGlobalVar.op1),
dpf,
Runtime::getUndefinedValue(),
PropOpFlags().plusThrowOnError()));
if (res == ExecutionStatus::EXCEPTION) {
assert(
!runtime.getGlobal()->isProxyObject() &&
"global can't be a proxy object");
// If the property already exists, this should be a noop.
// Instead of incurring the cost to check every time, do it
// only if an exception is thrown, and swallow the exception
// if it exists, since we didn't want to make the call,
// anyway. This most likely means the property is
// non-configurable.
NamedPropertyDescriptor desc;
CAPTURE_IP_ASSIGN(
auto res,
JSObject::getOwnNamedDescriptor(
runtime.getGlobal(),
runtime,
ID(ip->iDeclareGlobalVar.op1),
desc));
if (!res) {
goto exception;
} else {
runtime.clearThrownValue();
}
// fall through
}
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(DeclareGlobalVar);
DISPATCH;
}
CASE(TryGetByIdLong) {
tryProp = true;
idVal = ip->iTryGetByIdLong.op4;
nextIP = NEXTINST(TryGetByIdLong);
goto getById;
}
CASE(GetByIdLong) {
tryProp = false;
idVal = ip->iGetByIdLong.op4;
nextIP = NEXTINST(GetByIdLong);
goto getById;
}
CASE(GetByIdShort) {
tryProp = false;
idVal = ip->iGetByIdShort.op4;
nextIP = NEXTINST(GetByIdShort);
goto getById;
}
CASE(TryGetById) {
tryProp = true;
idVal = ip->iTryGetById.op4;
nextIP = NEXTINST(TryGetById);
goto getById;
}
CASE(GetById) {
tryProp = false;
idVal = ip->iGetById.op4;
nextIP = NEXTINST(GetById);
}
getById : {
++NumGetById;
// NOTE: it is safe to use OnREG(GetById) here because all instructions
// have the same layout: opcode, registers, non-register operands, i.e.
// they only differ in the width of the last "identifier" field.
CallResult<HermesValue> propRes{ExecutionStatus::EXCEPTION};
if (LLVM_LIKELY(O2REG(GetById).isObject())) {
auto *obj = vmcast<JSObject>(O2REG(GetById));
auto cacheIdx = ip->iGetById.op3;
auto *cacheEntry = curCodeBlock->getReadCacheEntry(cacheIdx);
#ifdef HERMESVM_PROFILER_BB
{
HERMES_SLOW_ASSERT(
gcScope.getHandleCountDbg() == KEEP_HANDLES &&
"unaccounted handles were created");
auto objHandle = runtime.makeHandle(obj);
auto cacheHCPtr = vmcast_or_null<HiddenClass>(static_cast<GCCell *>(
cacheEntry->clazz.get(runtime, &runtime.getHeap())));
CAPTURE_IP(runtime.recordHiddenClass(
curCodeBlock, ip, ID(idVal), obj->getClass(runtime), cacheHCPtr));
// obj may be moved by GC due to recordHiddenClass
obj = objHandle.get();
}
gcScope.flushToSmallCount(KEEP_HANDLES);
#endif
CompressedPointer clazzPtr{obj->getClassGCPtr()};
#ifndef NDEBUG
if (vmcast<HiddenClass>(clazzPtr.getNonNull(runtime))->isDictionary())
++NumGetByIdDict;
#else
(void)NumGetByIdDict;
#endif
// If we have a cache hit, reuse the cached offset and immediately
// return the property.
if (LLVM_LIKELY(cacheEntry->clazz == clazzPtr)) {
++NumGetByIdCacheHits;
CAPTURE_IP(
O1REG(GetById) =
JSObject::getNamedSlotValueUnsafe<PropStorage::Inline::Yes>(
obj, runtime, cacheEntry->slot)
.unboxToHV(runtime));
ip = nextIP;
DISPATCH;
}
auto id = ID(idVal);
NamedPropertyDescriptor desc;
CAPTURE_IP_ASSIGN(
OptValue<bool> fastPathResult,
JSObject::tryGetOwnNamedDescriptorFast(obj, runtime, id, desc));
if (LLVM_LIKELY(
fastPathResult.hasValue() && fastPathResult.getValue()) &&
!desc.flags.accessor) {
++NumGetByIdFastPaths;
// cacheIdx == 0 indicates no caching so don't update the cache in
// those cases.
HiddenClass *clazz =
vmcast<HiddenClass>(clazzPtr.getNonNull(runtime));
if (LLVM_LIKELY(!clazz->isDictionaryNoCache()) &&
LLVM_LIKELY(cacheIdx != hbc::PROPERTY_CACHING_DISABLED)) {
#ifdef HERMES_SLOW_DEBUG
if (cacheEntry->clazz && cacheEntry->clazz != clazzPtr)
++NumGetByIdCacheEvicts;
#else
(void)NumGetByIdCacheEvicts;
#endif
// Cache the class, id and property slot.
cacheEntry->clazz = clazzPtr;
cacheEntry->slot = desc.slot;
}
assert(
!obj->isProxyObject() &&
"tryGetOwnNamedDescriptorFast returned true on Proxy");
CAPTURE_IP(
O1REG(GetById) =
JSObject::getNamedSlotValueUnsafe(obj, runtime, desc)
.unboxToHV(runtime));
ip = nextIP;
DISPATCH;
}
// The cache may also be populated via the prototype of the object.
// This value is only reliable if the fast path was a definite
// not-found.
if (fastPathResult.hasValue() && !fastPathResult.getValue() &&
LLVM_LIKELY(!obj->isProxyObject())) {
CAPTURE_IP_ASSIGN(JSObject * parent, obj->getParent(runtime));
// TODO: This isLazy check is because a lazy object is reported as
// having no properties and therefore cannot contain the property.
// This check does not belong here, it should be merged into
// tryGetOwnNamedDescriptorFast().
if (parent && cacheEntry->clazz == parent->getClassGCPtr() &&
LLVM_LIKELY(!obj->isLazy())) {
++NumGetByIdProtoHits;
// We've already checked that this isn't a Proxy.
CAPTURE_IP(
O1REG(GetById) = JSObject::getNamedSlotValueUnsafe(
parent, runtime, cacheEntry->slot)
.unboxToHV(runtime));
ip = nextIP;
DISPATCH;
}
}
#ifdef HERMES_SLOW_DEBUG
// Call to getNamedDescriptorUnsafe is safe because `id` is kept alive
// by the IdentifierTable.
CAPTURE_IP_ASSIGN(
JSObject * propObj,
JSObject::getNamedDescriptorUnsafe(
Handle<JSObject>::vmcast(&O2REG(GetById)), runtime, id, desc));
if (propObj) {
if (desc.flags.accessor)
++NumGetByIdAccessor;
else if (propObj != vmcast<JSObject>(O2REG(GetById)))
++NumGetByIdProto;
} else {
++NumGetByIdNotFound;
}
#else
(void)NumGetByIdAccessor;
(void)NumGetByIdProto;
(void)NumGetByIdNotFound;
#endif
#ifdef HERMES_SLOW_DEBUG
auto *savedClass = cacheIdx != hbc::PROPERTY_CACHING_DISABLED
? cacheEntry->clazz.get(runtime, &runtime.getHeap())
: nullptr;
#endif
++NumGetByIdSlow;
CAPTURE_IP(
resPH = JSObject::getNamed_RJS(
Handle<JSObject>::vmcast(&O2REG(GetById)),
runtime,
id,
!tryProp ? defaultPropOpFlags
: defaultPropOpFlags.plusMustExist(),
cacheIdx != hbc::PROPERTY_CACHING_DISABLED ? cacheEntry
: nullptr));
if (LLVM_UNLIKELY(resPH == ExecutionStatus::EXCEPTION)) {
goto exception;
}
#ifdef HERMES_SLOW_DEBUG
if (cacheIdx != hbc::PROPERTY_CACHING_DISABLED && savedClass &&
cacheEntry->clazz.get(runtime, &runtime.getHeap()) != savedClass) {
++NumGetByIdCacheEvicts;
}
#endif
} else {
++NumGetByIdTransient;
assert(!tryProp && "TryGetById can only be used on the global object");
/* Slow path. */
CAPTURE_IP(
resPH = Interpreter::getByIdTransient_RJS(
runtime, Handle<>(&O2REG(GetById)), ID(idVal)));
if (LLVM_UNLIKELY(resPH == ExecutionStatus::EXCEPTION)) {
goto exception;
}
}
O1REG(GetById) = resPH->get();
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = nextIP;
DISPATCH;
}
CASE(TryPutByIdLong) {
tryProp = true;
idVal = ip->iTryPutByIdLong.op4;
nextIP = NEXTINST(TryPutByIdLong);
goto putById;
}
CASE(PutByIdLong) {
tryProp = false;
idVal = ip->iPutByIdLong.op4;
nextIP = NEXTINST(PutByIdLong);
goto putById;
}
CASE(TryPutById) {
tryProp = true;
idVal = ip->iTryPutById.op4;
nextIP = NEXTINST(TryPutById);
goto putById;
}
CASE(PutById) {
tryProp = false;
idVal = ip->iPutById.op4;
nextIP = NEXTINST(PutById);
}
putById : {
++NumPutById;
if (LLVM_LIKELY(O1REG(PutById).isObject())) {
CAPTURE_IP_ASSIGN(
SmallHermesValue shv,
SmallHermesValue::encodeHermesValue(O2REG(PutById), runtime));
auto *obj = vmcast<JSObject>(O1REG(PutById));
auto cacheIdx = ip->iPutById.op3;
auto *cacheEntry = curCodeBlock->getWriteCacheEntry(cacheIdx);
#ifdef HERMESVM_PROFILER_BB
{
HERMES_SLOW_ASSERT(
gcScope.getHandleCountDbg() == KEEP_HANDLES &&
"unaccounted handles were created");
auto shvHandle = runtime.makeHandle(shv.toHV(runtime));
auto objHandle = runtime.makeHandle(obj);
auto cacheHCPtr = vmcast_or_null<HiddenClass>(static_cast<GCCell *>(
cacheEntry->clazz.get(runtime, &runtime.getHeap())));
CAPTURE_IP(runtime.recordHiddenClass(
curCodeBlock, ip, ID(idVal), obj->getClass(runtime), cacheHCPtr));
// shv/obj may be invalidated by recordHiddenClass
if (shv.isPointer())
shv.unsafeUpdatePointer(
static_cast<GCCell *>(shvHandle->getPointer()), runtime);
obj = objHandle.get();
}
gcScope.flushToSmallCount(KEEP_HANDLES);
#endif
CompressedPointer clazzPtr{obj->getClassGCPtr()};
// If we have a cache hit, reuse the cached offset and immediately
// return the property.
if (LLVM_LIKELY(cacheEntry->clazz == clazzPtr)) {
++NumPutByIdCacheHits;
CAPTURE_IP(
JSObject::setNamedSlotValueUnsafe<PropStorage::Inline::Yes>(
obj, runtime, cacheEntry->slot, shv));
ip = nextIP;
DISPATCH;
}
auto id = ID(idVal);
NamedPropertyDescriptor desc;
CAPTURE_IP_ASSIGN(
OptValue<bool> hasOwnProp,
JSObject::tryGetOwnNamedDescriptorFast(obj, runtime, id, desc));
if (LLVM_LIKELY(hasOwnProp.hasValue() && hasOwnProp.getValue()) &&
!desc.flags.accessor && desc.flags.writable &&
!desc.flags.internalSetter) {
++NumPutByIdFastPaths;
// cacheIdx == 0 indicates no caching so don't update the cache in
// those cases.
HiddenClass *clazz =
vmcast<HiddenClass>(clazzPtr.getNonNull(runtime));
if (LLVM_LIKELY(!clazz->isDictionary()) &&
LLVM_LIKELY(cacheIdx != hbc::PROPERTY_CACHING_DISABLED)) {
#ifdef HERMES_SLOW_DEBUG
if (cacheEntry->clazz && cacheEntry->clazz != clazzPtr)
++NumPutByIdCacheEvicts;
#else
(void)NumPutByIdCacheEvicts;
#endif
// Cache the class and property slot.
cacheEntry->clazz = clazzPtr;
cacheEntry->slot = desc.slot;
}
// This must be valid because an own property was already found.
CAPTURE_IP(
JSObject::setNamedSlotValueUnsafe(obj, runtime, desc.slot, shv));
ip = nextIP;
DISPATCH;
}
CAPTURE_IP_ASSIGN(
auto putRes,
JSObject::putNamed_RJS(
Handle<JSObject>::vmcast(&O1REG(PutById)),
runtime,
id,
Handle<>(&O2REG(PutById)),
!tryProp ? defaultPropOpFlags
: defaultPropOpFlags.plusMustExist()));
if (LLVM_UNLIKELY(putRes == ExecutionStatus::EXCEPTION)) {
goto exception;
}
} else {
++NumPutByIdTransient;
assert(!tryProp && "TryPutById can only be used on the global object");
CAPTURE_IP_ASSIGN(
auto retStatus,
Interpreter::putByIdTransient_RJS(
runtime,
Handle<>(&O1REG(PutById)),
ID(idVal),
Handle<>(&O2REG(PutById)),
strictMode));
if (retStatus == ExecutionStatus::EXCEPTION) {
goto exception;
}
}
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = nextIP;
DISPATCH;
}
CASE(GetByVal) {
CallResult<HermesValue> propRes{ExecutionStatus::EXCEPTION};
if (LLVM_LIKELY(O2REG(GetByVal).isObject())) {
CAPTURE_IP(
resPH = JSObject::getComputed_RJS(
Handle<JSObject>::vmcast(&O2REG(GetByVal)),
runtime,
Handle<>(&O3REG(GetByVal))));
if (LLVM_UNLIKELY(resPH == ExecutionStatus::EXCEPTION)) {
goto exception;
}
} else {
// This is the "slow path".
CAPTURE_IP(
resPH = Interpreter::getByValTransient_RJS(
runtime,
Handle<>(&O2REG(GetByVal)),
Handle<>(&O3REG(GetByVal))));
if (LLVM_UNLIKELY(resPH == ExecutionStatus::EXCEPTION)) {
goto exception;
}
}
gcScope.flushToSmallCount(KEEP_HANDLES);
O1REG(GetByVal) = resPH->get();
ip = NEXTINST(GetByVal);
DISPATCH;
}
CASE(PutByVal) {
if (LLVM_LIKELY(O1REG(PutByVal).isObject())) {
CAPTURE_IP_ASSIGN(
auto putRes,
JSObject::putComputed_RJS(
Handle<JSObject>::vmcast(&O1REG(PutByVal)),
runtime,
Handle<>(&O2REG(PutByVal)),
Handle<>(&O3REG(PutByVal)),
defaultPropOpFlags));
if (LLVM_UNLIKELY(putRes == ExecutionStatus::EXCEPTION)) {
goto exception;
}
} else {
// This is the "slow path".
CAPTURE_IP_ASSIGN(
auto retStatus,
Interpreter::putByValTransient_RJS(
runtime,
Handle<>(&O1REG(PutByVal)),
Handle<>(&O2REG(PutByVal)),
Handle<>(&O3REG(PutByVal)),
strictMode));
if (LLVM_UNLIKELY(retStatus == ExecutionStatus::EXCEPTION)) {
goto exception;
}
}
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(PutByVal);
DISPATCH;
}
CASE(PutOwnByIndexL) {
nextIP = NEXTINST(PutOwnByIndexL);
idVal = ip->iPutOwnByIndexL.op3;
goto putOwnByIndex;
}
CASE(PutOwnByIndex) {
nextIP = NEXTINST(PutOwnByIndex);
idVal = ip->iPutOwnByIndex.op3;
}
putOwnByIndex : {
tmpHandle = HermesValue::encodeDoubleValue(idVal);
CAPTURE_IP(JSObject::defineOwnComputedPrimitive(
Handle<JSObject>::vmcast(&O1REG(PutOwnByIndex)),
runtime,
tmpHandle,
DefinePropertyFlags::getDefaultNewPropertyFlags(),
Handle<>(&O2REG(PutOwnByIndex))));
gcScope.flushToSmallCount(KEEP_HANDLES);
tmpHandle.clear();
ip = nextIP;
DISPATCH;
}
CASE_OUTOFLINE(GetPNameList);
CASE(GetNextPName) {
{
assert(
vmisa<BigStorage>(O2REG(GetNextPName)) &&
"GetNextPName's second op must be BigStorage");
auto obj = Handle<JSObject>::vmcast(&O3REG(GetNextPName));
auto arr = Handle<BigStorage>::vmcast(&O2REG(GetNextPName));
uint32_t idx = O4REG(GetNextPName).getNumber();
uint32_t size = O5REG(GetNextPName).getNumber();
MutableHandle<JSObject> propObj{runtime};
MutableHandle<SymbolID> tmpPropNameStorage{runtime};
// Loop until we find a property which is present.
while (idx < size) {
tmpHandle = arr->at(idx);
ComputedPropertyDescriptor desc;
CAPTURE_IP_ASSIGN(
ExecutionStatus status,
JSObject::getComputedPrimitiveDescriptor(
obj,
runtime,
tmpHandle,
propObj,
tmpPropNameStorage,
desc));
if (LLVM_UNLIKELY(status == ExecutionStatus::EXCEPTION)) {
goto exception;
}
if (LLVM_LIKELY(propObj))
break;
++idx;
}
if (idx < size) {
// We must return the property as a string
if (tmpHandle->isNumber()) {
CAPTURE_IP_ASSIGN(auto status, toString_RJS(runtime, tmpHandle));
assert(
status == ExecutionStatus::RETURNED &&
"toString on number cannot fail");
tmpHandle = status->getHermesValue();
}
O1REG(GetNextPName) = tmpHandle.get();
O4REG(GetNextPName) = HermesValue::encodeNumberValue(idx + 1);
} else {
O1REG(GetNextPName) = HermesValue::encodeUndefinedValue();
}
}
gcScope.flushToSmallCount(KEEP_HANDLES);
tmpHandle.clear();
ip = NEXTINST(GetNextPName);
DISPATCH;
}
CASE(ToNumber) {
if (LLVM_LIKELY(O2REG(ToNumber).isNumber())) {
O1REG(ToNumber) = O2REG(ToNumber);
ip = NEXTINST(ToNumber);
} else {
CAPTURE_IP(res = toNumber_RJS(runtime, Handle<>(&O2REG(ToNumber))));
if (res == ExecutionStatus::EXCEPTION)
goto exception;
gcScope.flushToSmallCount(KEEP_HANDLES);
O1REG(ToNumber) = res.getValue();
ip = NEXTINST(ToNumber);
}
DISPATCH;
}
CASE(ToInt32) {
CAPTURE_IP(res = toInt32_RJS(runtime, Handle<>(&O2REG(ToInt32))));
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION))
goto exception;
gcScope.flushToSmallCount(KEEP_HANDLES);
O1REG(ToInt32) = res.getValue();
ip = NEXTINST(ToInt32);
DISPATCH;
}
CASE(AddEmptyString) {
if (LLVM_LIKELY(O2REG(AddEmptyString).isString())) {
O1REG(AddEmptyString) = O2REG(AddEmptyString);
ip = NEXTINST(AddEmptyString);
} else {
CAPTURE_IP(
res = toPrimitive_RJS(
runtime,
Handle<>(&O2REG(AddEmptyString)),
PreferredType::NONE));
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION))
goto exception;
tmpHandle = res.getValue();
CAPTURE_IP_ASSIGN(auto strRes, toString_RJS(runtime, tmpHandle));
if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION))
goto exception;
tmpHandle.clear();
gcScope.flushToSmallCount(KEEP_HANDLES);
O1REG(AddEmptyString) = strRes->getHermesValue();
ip = NEXTINST(AddEmptyString);
}
DISPATCH;
}
CASE(Jmp) {
ip = IPADD(ip->iJmp.op1);
DISPATCH;
}
CASE(JmpLong) {
ip = IPADD(ip->iJmpLong.op1);
DISPATCH;
}
CASE(JmpTrue) {
if (toBoolean(O2REG(JmpTrue)))
ip = IPADD(ip->iJmpTrue.op1);
else
ip = NEXTINST(JmpTrue);
DISPATCH;
}
CASE(JmpTrueLong) {
if (toBoolean(O2REG(JmpTrueLong)))
ip = IPADD(ip->iJmpTrueLong.op1);
else
ip = NEXTINST(JmpTrueLong);
DISPATCH;
}
CASE(JmpFalse) {
if (!toBoolean(O2REG(JmpFalse)))
ip = IPADD(ip->iJmpFalse.op1);
else
ip = NEXTINST(JmpFalse);
DISPATCH;
}
CASE(JmpFalseLong) {
if (!toBoolean(O2REG(JmpFalseLong)))
ip = IPADD(ip->iJmpFalseLong.op1);
else
ip = NEXTINST(JmpFalseLong);
DISPATCH;
}
CASE(JmpUndefined) {
if (O2REG(JmpUndefined).isUndefined())
ip = IPADD(ip->iJmpUndefined.op1);
else
ip = NEXTINST(JmpUndefined);
DISPATCH;
}
CASE(JmpUndefinedLong) {
if (O2REG(JmpUndefinedLong).isUndefined())
ip = IPADD(ip->iJmpUndefinedLong.op1);
else
ip = NEXTINST(JmpUndefinedLong);
DISPATCH;
}
INCDECOP(Inc, +)
INCDECOP(Dec, -)
CASE(Add) {
if (LLVM_LIKELY(
O2REG(Add).isNumber() &&
O3REG(Add).isNumber())) { /* Fast-path. */
CASE(AddN) {
O1REG(Add) = HermesValue::encodeDoubleValue(
O2REG(Add).getNumber() + O3REG(Add).getNumber());
ip = NEXTINST(Add);
DISPATCH;
}
}
CAPTURE_IP(
res = addOp_RJS(
runtime, Handle<>(&O2REG(Add)), Handle<>(&O3REG(Add))));
if (res == ExecutionStatus::EXCEPTION) {
goto exception;
}
gcScope.flushToSmallCount(KEEP_HANDLES);
O1REG(Add) = res.getValue();
ip = NEXTINST(Add);
DISPATCH;
}
CASE(BitNot) {
if (LLVM_LIKELY(O2REG(BitNot).isNumber())) { /* Fast-path. */
O1REG(BitNot) = HermesValue::encodeDoubleValue(
~hermes::truncateToInt32(O2REG(BitNot).getNumber()));
ip = NEXTINST(BitNot);
DISPATCH;
}
CAPTURE_IP(res = toInt32_RJS(runtime, Handle<>(&O2REG(BitNot))));
if (res == ExecutionStatus::EXCEPTION) {
goto exception;
}
gcScope.flushToSmallCount(KEEP_HANDLES);
O1REG(BitNot) = HermesValue::encodeDoubleValue(
~static_cast<int32_t>(res->getNumber()));
ip = NEXTINST(BitNot);
DISPATCH;
}
CASE(GetArgumentsLength) {
// If the arguments object hasn't been created yet.
if (O2REG(GetArgumentsLength).isUndefined()) {
O1REG(GetArgumentsLength) =
HermesValue::encodeNumberValue(FRAME.getArgCount());
ip = NEXTINST(GetArgumentsLength);
DISPATCH;
}
// The arguments object has been created, so this is a regular property
// get.
assert(
O2REG(GetArgumentsLength).isObject() &&
"arguments lazy register is not an object");
CAPTURE_IP(
resPH = JSObject::getNamed_RJS(
Handle<JSObject>::vmcast(&O2REG(GetArgumentsLength)),
runtime,
Predefined::getSymbolID(Predefined::length)));
if (resPH == ExecutionStatus::EXCEPTION) {
goto exception;
}
gcScope.flushToSmallCount(KEEP_HANDLES);
O1REG(GetArgumentsLength) = resPH->get();
ip = NEXTINST(GetArgumentsLength);
DISPATCH;
}
CASE(GetArgumentsPropByVal) {
// If the arguments object hasn't been created yet and we have a
// valid integer index, we use the fast path.
if (O3REG(GetArgumentsPropByVal).isUndefined()) {
// If this is an integer index.
if (auto index = toArrayIndexFastPath(O2REG(GetArgumentsPropByVal))) {
// Is this an existing argument?
if (*index < FRAME.getArgCount()) {
O1REG(GetArgumentsPropByVal) = FRAME.getArgRef(*index);
ip = NEXTINST(GetArgumentsPropByVal);
DISPATCH;
}
}
}
// Slow path.
CAPTURE_IP_ASSIGN(
auto res,
getArgumentsPropByValSlowPath_RJS(
runtime,
&O3REG(GetArgumentsPropByVal),
&O2REG(GetArgumentsPropByVal),
FRAME.getCalleeClosureHandleUnsafe(),
strictMode));
if (res == ExecutionStatus::EXCEPTION) {
goto exception;
}
gcScope.flushToSmallCount(KEEP_HANDLES);
O1REG(GetArgumentsPropByVal) = res->getHermesValue();
ip = NEXTINST(GetArgumentsPropByVal);
DISPATCH;
}
CASE(ReifyArguments) {
// If the arguments object was already created, do nothing.
if (!O1REG(ReifyArguments).isUndefined()) {
assert(
O1REG(ReifyArguments).isObject() &&
"arguments lazy register is not an object");
ip = NEXTINST(ReifyArguments);
DISPATCH;
}
CAPTURE_IP(
resArgs = reifyArgumentsSlowPath(
runtime, FRAME.getCalleeClosureHandleUnsafe(), strictMode));
if (LLVM_UNLIKELY(resArgs == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(ReifyArguments) = resArgs->getHermesValue();
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(ReifyArguments);
DISPATCH;
}
CASE(NewObject) {
// Create a new object using the built-in constructor. Note that the
// built-in constructor is empty, so we don't actually need to call
// it.
CAPTURE_IP(
O1REG(NewObject) = JSObject::create(runtime).getHermesValue());
assert(
gcScope.getHandleCountDbg() == KEEP_HANDLES &&
"Should not create handles.");
ip = NEXTINST(NewObject);
DISPATCH;
}
CASE(NewObjectWithParent) {
CAPTURE_IP(
O1REG(NewObjectWithParent) =
JSObject::create(
runtime,
O2REG(NewObjectWithParent).isObject()
? Handle<JSObject>::vmcast(&O2REG(NewObjectWithParent))
: O2REG(NewObjectWithParent).isNull()
? Runtime::makeNullHandle<JSObject>()
: Handle<JSObject>::vmcast(&runtime.objectPrototype))
.getHermesValue());
assert(
gcScope.getHandleCountDbg() == KEEP_HANDLES &&
"Should not create handles.");
ip = NEXTINST(NewObjectWithParent);
DISPATCH;
}
CASE(NewObjectWithBuffer) {
CAPTURE_IP(
resPH = Interpreter::createObjectFromBuffer(
runtime,
curCodeBlock,
ip->iNewObjectWithBuffer.op3,
ip->iNewObjectWithBuffer.op4,
ip->iNewObjectWithBuffer.op5));
if (LLVM_UNLIKELY(resPH == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(NewObjectWithBuffer) = resPH->get();
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(NewObjectWithBuffer);
DISPATCH;
}
CASE(NewObjectWithBufferLong) {
CAPTURE_IP(
resPH = Interpreter::createObjectFromBuffer(
runtime,
curCodeBlock,
ip->iNewObjectWithBufferLong.op3,
ip->iNewObjectWithBufferLong.op4,
ip->iNewObjectWithBufferLong.op5));
if (LLVM_UNLIKELY(resPH == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(NewObjectWithBufferLong) = resPH->get();
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(NewObjectWithBufferLong);
DISPATCH;
}
CASE(NewArray) {
// Create a new array using the built-in constructor. Note that the
// built-in constructor is empty, so we don't actually need to call
// it.
{
CAPTURE_IP_ASSIGN(
auto createRes,
JSArray::create(runtime, ip->iNewArray.op2, ip->iNewArray.op2));
if (createRes == ExecutionStatus::EXCEPTION) {
goto exception;
}
O1REG(NewArray) = createRes->getHermesValue();
}
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(NewArray);
DISPATCH;
}
CASE(NewArrayWithBuffer) {
CAPTURE_IP(
resPH = Interpreter::createArrayFromBuffer(
runtime,
curCodeBlock,
ip->iNewArrayWithBuffer.op2,
ip->iNewArrayWithBuffer.op3,
ip->iNewArrayWithBuffer.op4));
if (LLVM_UNLIKELY(resPH == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(NewArrayWithBuffer) = resPH->get();
gcScope.flushToSmallCount(KEEP_HANDLES);
tmpHandle.clear();
ip = NEXTINST(NewArrayWithBuffer);
DISPATCH;
}
CASE(NewArrayWithBufferLong) {
CAPTURE_IP(
resPH = Interpreter::createArrayFromBuffer(
runtime,
curCodeBlock,
ip->iNewArrayWithBufferLong.op2,
ip->iNewArrayWithBufferLong.op3,
ip->iNewArrayWithBufferLong.op4));
if (LLVM_UNLIKELY(resPH == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(NewArrayWithBufferLong) = resPH->get();
gcScope.flushToSmallCount(KEEP_HANDLES);
tmpHandle.clear();
ip = NEXTINST(NewArrayWithBufferLong);
DISPATCH;
}
CASE(CreateThis) {
// Registers: output, prototype, closure.
if (LLVM_UNLIKELY(!vmisa<Callable>(O3REG(CreateThis)))) {
CAPTURE_IP(runtime.raiseTypeError("constructor is not callable"));
goto exception;
}
CAPTURE_IP_ASSIGN(
auto res,
Callable::newObject(
Handle<Callable>::vmcast(&O3REG(CreateThis)),
runtime,
Handle<JSObject>::vmcast(
O2REG(CreateThis).isObject() ? &O2REG(CreateThis)
: &runtime.objectPrototype)));
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
goto exception;
}
gcScope.flushToSmallCount(KEEP_HANDLES);
O1REG(CreateThis) = res->getHermesValue();
ip = NEXTINST(CreateThis);
DISPATCH;
}
CASE(SelectObject) {
// Registers: output, thisObject, constructorReturnValue.
O1REG(SelectObject) = O3REG(SelectObject).isObject()
? O3REG(SelectObject)
: O2REG(SelectObject);
ip = NEXTINST(SelectObject);
DISPATCH;
}
CASE(Eq)
CASE(Neq) {
CAPTURE_IP_ASSIGN(
auto eqRes,
abstractEqualityTest_RJS(
runtime, Handle<>(&O2REG(Eq)), Handle<>(&O3REG(Eq))));
if (eqRes == ExecutionStatus::EXCEPTION) {
goto exception;
}
gcScope.flushToSmallCount(KEEP_HANDLES);
O1REG(Eq) = HermesValue::encodeBoolValue(
ip->opCode == OpCode::Eq ? *eqRes : !*eqRes);
ip = NEXTINST(Eq);
DISPATCH;
}
CASE(StrictEq) {
O1REG(StrictEq) = HermesValue::encodeBoolValue(
strictEqualityTest(O2REG(StrictEq), O3REG(StrictEq)));
ip = NEXTINST(StrictEq);
DISPATCH;
}
CASE(StrictNeq) {
O1REG(StrictNeq) = HermesValue::encodeBoolValue(
!strictEqualityTest(O2REG(StrictNeq), O3REG(StrictNeq)));
ip = NEXTINST(StrictNeq);
DISPATCH;
}
CASE(Not) {
O1REG(Not) = HermesValue::encodeBoolValue(!toBoolean(O2REG(Not)));
ip = NEXTINST(Not);
DISPATCH;
}
CASE(Negate) {
if (LLVM_LIKELY(O2REG(Negate).isNumber())) {
O1REG(Negate) =
HermesValue::encodeDoubleValue(-O2REG(Negate).getNumber());
} else {
CAPTURE_IP(res = toNumber_RJS(runtime, Handle<>(&O2REG(Negate))));
if (res == ExecutionStatus::EXCEPTION)
goto exception;
gcScope.flushToSmallCount(KEEP_HANDLES);
O1REG(Negate) = HermesValue::encodeDoubleValue(-res->getNumber());
}
ip = NEXTINST(Negate);
DISPATCH;
}
CASE(TypeOf) {
CAPTURE_IP(O1REG(TypeOf) = typeOf(runtime, Handle<>(&O2REG(TypeOf))));
ip = NEXTINST(TypeOf);
DISPATCH;
}
CASE(Mod) {
// We use fmod here for simplicity. Theoretically fmod behaves slightly
// differently than the ECMAScript Spec. fmod applies round-towards-zero
// for the remainder when it's not representable by a double; while the
// spec requires round-to-nearest. As an example, 5 % 0.7 will give
// 0.10000000000000031 using fmod, but using the rounding style
// described
// by the spec, the output should really be 0.10000000000000053.
// Such difference can be ignored in practice.
if (LLVM_LIKELY(O2REG(Mod).isNumber() && O3REG(Mod).isNumber())) {
/* Fast-path. */
O1REG(Mod) = HermesValue::encodeDoubleValue(
std::fmod(O2REG(Mod).getNumber(), O3REG(Mod).getNumber()));
ip = NEXTINST(Mod);
DISPATCH;
}
CAPTURE_IP(res = toNumber_RJS(runtime, Handle<>(&O2REG(Mod))));
if (res == ExecutionStatus::EXCEPTION)
goto exception;
double left = res->getDouble();
CAPTURE_IP(res = toNumber_RJS(runtime, Handle<>(&O3REG(Mod))));
if (res == ExecutionStatus::EXCEPTION)
goto exception;
O1REG(Mod) =
HermesValue::encodeDoubleValue(std::fmod(left, res->getDouble()));
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(Mod);
DISPATCH;
}
CASE(InstanceOf) {
CAPTURE_IP_ASSIGN(
auto result,
instanceOfOperator_RJS(
runtime,
Handle<>(&O2REG(InstanceOf)),
Handle<>(&O3REG(InstanceOf))));
if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(InstanceOf) = HermesValue::encodeBoolValue(*result);
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(InstanceOf);
DISPATCH;
}
CASE(IsIn) {
{
if (LLVM_UNLIKELY(!O3REG(IsIn).isObject())) {
CAPTURE_IP(runtime.raiseTypeError(
"right operand of 'in' is not an object"));
goto exception;
}
CAPTURE_IP_ASSIGN(
auto cr,
JSObject::hasComputed(
Handle<JSObject>::vmcast(&O3REG(IsIn)),
runtime,
Handle<>(&O2REG(IsIn))));
if (cr == ExecutionStatus::EXCEPTION) {
goto exception;
}
O1REG(IsIn) = HermesValue::encodeBoolValue(*cr);
}
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(IsIn);
DISPATCH;
}
CASE(PutNewOwnByIdShort) {
nextIP = NEXTINST(PutNewOwnByIdShort);
idVal = ip->iPutNewOwnByIdShort.op3;
goto putOwnById;
}
CASE(PutNewOwnNEByIdLong)
CASE(PutNewOwnByIdLong) {
nextIP = NEXTINST(PutNewOwnByIdLong);
idVal = ip->iPutNewOwnByIdLong.op3;
goto putOwnById;
}
CASE(PutNewOwnNEById)
CASE(PutNewOwnById) {
nextIP = NEXTINST(PutNewOwnById);
idVal = ip->iPutNewOwnById.op3;
}
putOwnById : {
assert(
O1REG(PutNewOwnById).isObject() &&
"Object argument of PutNewOwnById must be an object");
CAPTURE_IP_ASSIGN(
auto res,
JSObject::defineNewOwnProperty(
Handle<JSObject>::vmcast(&O1REG(PutNewOwnById)),
runtime,
ID(idVal),
ip->opCode <= OpCode::PutNewOwnByIdLong
? PropertyFlags::defaultNewNamedPropertyFlags()
: PropertyFlags::nonEnumerablePropertyFlags(),
Handle<>(&O2REG(PutNewOwnById))));
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
goto exception;
}
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = nextIP;
DISPATCH;
}
CASE(DelByIdLong) {
idVal = ip->iDelByIdLong.op3;
nextIP = NEXTINST(DelByIdLong);
goto DelById;
}
CASE(DelById) {
idVal = ip->iDelById.op3;
nextIP = NEXTINST(DelById);
}
DelById : {
if (LLVM_LIKELY(O2REG(DelById).isObject())) {
CAPTURE_IP_ASSIGN(
auto status,
JSObject::deleteNamed(
Handle<JSObject>::vmcast(&O2REG(DelById)),
runtime,
ID(idVal),
defaultPropOpFlags));
if (LLVM_UNLIKELY(status == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(DelById) = HermesValue::encodeBoolValue(status.getValue());
} else {
// This is the "slow path".
CAPTURE_IP(res = toObject(runtime, Handle<>(&O2REG(DelById))));
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
// If an exception is thrown, likely we are trying to convert
// undefined/null to an object. Passing over the name of the property
// so that we could emit more meaningful error messages.
CAPTURE_IP(amendPropAccessErrorMsgWithPropName(
runtime, Handle<>(&O2REG(DelById)), "delete", ID(idVal)));
goto exception;
}
tmpHandle = res.getValue();
CAPTURE_IP_ASSIGN(
auto status,
JSObject::deleteNamed(
Handle<JSObject>::vmcast(tmpHandle),
runtime,
ID(idVal),
defaultPropOpFlags));
if (LLVM_UNLIKELY(status == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(DelById) = HermesValue::encodeBoolValue(status.getValue());
tmpHandle.clear();
}
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = nextIP;
DISPATCH;
}
CASE(DelByVal) {
if (LLVM_LIKELY(O2REG(DelByVal).isObject())) {
CAPTURE_IP_ASSIGN(
auto status,
JSObject::deleteComputed(
Handle<JSObject>::vmcast(&O2REG(DelByVal)),
runtime,
Handle<>(&O3REG(DelByVal)),
defaultPropOpFlags));
if (LLVM_UNLIKELY(status == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(DelByVal) = HermesValue::encodeBoolValue(status.getValue());
} else {
// This is the "slow path".
CAPTURE_IP(res = toObject(runtime, Handle<>(&O2REG(DelByVal))));
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
goto exception;
}
tmpHandle = res.getValue();
CAPTURE_IP_ASSIGN(
auto status,
JSObject::deleteComputed(
Handle<JSObject>::vmcast(tmpHandle),
runtime,
Handle<>(&O3REG(DelByVal)),
defaultPropOpFlags));
if (LLVM_UNLIKELY(status == ExecutionStatus::EXCEPTION)) {
goto exception;
}
O1REG(DelByVal) = HermesValue::encodeBoolValue(status.getValue());
}
gcScope.flushToSmallCount(KEEP_HANDLES);
tmpHandle.clear();
ip = NEXTINST(DelByVal);
DISPATCH;
}
CASE(CreateRegExp) {
{
// Create the RegExp object.
CAPTURE_IP(
O1REG(CreateRegExp) = JSRegExp::create(runtime).getHermesValue());
auto re = Handle<JSRegExp>::vmcast(&O1REG(CreateRegExp));
// Initialize the regexp.
CAPTURE_IP_ASSIGN(
auto pattern,
runtime.makeHandle(curCodeBlock->getRuntimeModule()
->getStringPrimFromStringIDMayAllocate(
ip->iCreateRegExp.op2)));
CAPTURE_IP_ASSIGN(
auto flags,
runtime.makeHandle(curCodeBlock->getRuntimeModule()
->getStringPrimFromStringIDMayAllocate(
ip->iCreateRegExp.op3)));
CAPTURE_IP_ASSIGN(
auto bytecode,
curCodeBlock->getRuntimeModule()->getRegExpBytecodeFromRegExpID(
ip->iCreateRegExp.op4));
CAPTURE_IP(
JSRegExp::initialize(re, runtime, pattern, flags, bytecode));
}
gcScope.flushToSmallCount(KEEP_HANDLES);
ip = NEXTINST(CreateRegExp);
DISPATCH;
}
CASE(SwitchImm) {
if (LLVM_LIKELY(O1REG(SwitchImm).isNumber())) {
double numVal = O1REG(SwitchImm).getNumber();
uint32_t uintVal = (uint32_t)numVal;
if (LLVM_LIKELY(numVal == uintVal) && // Only integers.
LLVM_LIKELY(uintVal >= ip->iSwitchImm.op4) && // Bounds checking.
LLVM_LIKELY(uintVal <= ip->iSwitchImm.op5)) // Bounds checking.
{
// Calculate the offset into the bytecode where the jump table for
// this SwitchImm starts.
const uint8_t *tablestart = (const uint8_t *)llvh::alignAddr(
(const uint8_t *)ip + ip->iSwitchImm.op2, sizeof(uint32_t));
// Read the offset from the table.
// Must be signed to account for backwards branching.
const int32_t *loc =
(const int32_t *)tablestart + uintVal - ip->iSwitchImm.op4;
ip = IPADD(*loc);
DISPATCH;
}
}
// Wrong type or out of range, jump to default.
ip = IPADD(ip->iSwitchImm.op3);
DISPATCH;
}
LOAD_CONST(
LoadConstUInt8,
HermesValue::encodeDoubleValue(ip->iLoadConstUInt8.op2));
LOAD_CONST(
LoadConstInt, HermesValue::encodeDoubleValue(ip->iLoadConstInt.op2));
LOAD_CONST(
LoadConstDouble,
HermesValue::encodeDoubleValue(ip->iLoadConstDouble.op2));
LOAD_CONST_CAPTURE_IP(
LoadConstString,
HermesValue::encodeStringValue(
curCodeBlock->getRuntimeModule()
->getStringPrimFromStringIDMayAllocate(
ip->iLoadConstString.op2)));
LOAD_CONST_CAPTURE_IP(
LoadConstStringLongIndex,
HermesValue::encodeStringValue(
curCodeBlock->getRuntimeModule()
->getStringPrimFromStringIDMayAllocate(
ip->iLoadConstStringLongIndex.op2)));
LOAD_CONST(LoadConstEmpty, HermesValue::encodeEmptyValue());
LOAD_CONST(LoadConstUndefined, HermesValue::encodeUndefinedValue());
LOAD_CONST(LoadConstNull, HermesValue::encodeNullValue());
LOAD_CONST(LoadConstTrue, HermesValue::encodeBoolValue(true));
LOAD_CONST(LoadConstFalse, HermesValue::encodeBoolValue(false));
LOAD_CONST(LoadConstZero, HermesValue::encodeDoubleValue(0));
BINOP(Sub, doSub);
BINOP(Mul, doMult);
BINOP(Div, doDiv);
BITWISEBINOP(BitAnd, &);
BITWISEBINOP(BitOr, |);
BITWISEBINOP(BitXor, ^);
// For LShift, we need to use toUInt32 first because lshift on negative
// numbers is undefined behavior in theory.
SHIFTOP(LShift, <<, toUInt32_RJS, uint32_t, int32_t);
SHIFTOP(RShift, >>, toInt32_RJS, int32_t, int32_t);
SHIFTOP(URshift, >>, toUInt32_RJS, uint32_t, uint32_t);
CONDOP(Less, <, lessOp_RJS);
CONDOP(LessEq, <=, lessEqualOp_RJS);
CONDOP(Greater, >, greaterOp_RJS);
CONDOP(GreaterEq, >=, greaterEqualOp_RJS);
JCOND(Less, <, lessOp_RJS);
JCOND(LessEqual, <=, lessEqualOp_RJS);
JCOND(Greater, >, greaterOp_RJS);
JCOND(GreaterEqual, >=, greaterEqualOp_RJS);
JCOND_STRICT_EQ_IMPL(
JStrictEqual, , IPADD(ip->iJStrictEqual.op1), NEXTINST(JStrictEqual));
JCOND_STRICT_EQ_IMPL(
JStrictEqual,
Long,
IPADD(ip->iJStrictEqualLong.op1),
NEXTINST(JStrictEqualLong));
JCOND_STRICT_EQ_IMPL(
JStrictNotEqual,
,
NEXTINST(JStrictNotEqual),
IPADD(ip->iJStrictNotEqual.op1));
JCOND_STRICT_EQ_IMPL(
JStrictNotEqual,
Long,
NEXTINST(JStrictNotEqualLong),
IPADD(ip->iJStrictNotEqualLong.op1));
JCOND_EQ_IMPL(JEqual, , IPADD(ip->iJEqual.op1), NEXTINST(JEqual));
JCOND_EQ_IMPL(
JEqual, Long, IPADD(ip->iJEqualLong.op1), NEXTINST(JEqualLong));
JCOND_EQ_IMPL(
JNotEqual, , NEXTINST(JNotEqual), IPADD(ip->iJNotEqual.op1));
JCOND_EQ_IMPL(
JNotEqual,
Long,
NEXTINST(JNotEqualLong),
IPADD(ip->iJNotEqualLong.op1));
CASE_OUTOFLINE(PutOwnByVal);
CASE_OUTOFLINE(PutOwnGetterSetterByVal);
CASE_OUTOFLINE(DirectEval);
CASE_OUTOFLINE(IteratorBegin);
CASE_OUTOFLINE(IteratorNext);
CASE(IteratorClose) {
if (LLVM_UNLIKELY(O1REG(IteratorClose).isObject())) {
// The iterator must be closed if it's still an object.
// That means it was never an index and is not done iterating (a state
// which is indicated by `undefined`).
CAPTURE_IP_ASSIGN(
auto res,
iteratorClose(
runtime,
Handle<JSObject>::vmcast(&O1REG(IteratorClose)),
Runtime::getEmptyValue()));
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
if (ip->iIteratorClose.op2 &&
!isUncatchableError(runtime.thrownValue_)) {
// Ignore inner exception.
runtime.clearThrownValue();
} else {
goto exception;
}
}
gcScope.flushToSmallCount(KEEP_HANDLES);
}
ip = NEXTINST(IteratorClose);
DISPATCH;
}
#ifdef HERMES_RUN_WASM
// Asm.js/Wasm Intrinsics
CASE(Add32) {
O1REG(Add32) = HermesValue::encodeDoubleValue((
int32_t)(int64_t)(O2REG(Add32).getNumber() + O3REG(Add32).getNumber()));
ip = NEXTINST(Add32);
DISPATCH;
}
CASE(Sub32) {
O1REG(Sub32) = HermesValue::encodeDoubleValue((
int32_t)(int64_t)(O2REG(Sub32).getNumber() - O3REG(Sub32).getNumber()));
ip = NEXTINST(Sub32);
DISPATCH;
}
CASE(Mul32) {
// Signedness matters for multiplication, but low 32 bits are the same
// regardless of signedness.
const uint32_t arg0 = (uint32_t)(int32_t)(O2REG(Mul32).getNumber());
const uint32_t arg1 = (uint32_t)(int32_t)(O3REG(Mul32).getNumber());
O1REG(Mul32) = HermesValue::encodeDoubleValue((int32_t)(arg0 * arg1));
ip = NEXTINST(Mul32);
DISPATCH;
}
CASE(Divi32) {
const int32_t arg0 = (int32_t)(O2REG(Divi32).getNumber());
const int32_t arg1 = (int32_t)(O3REG(Divi32).getNumber());
O1REG(Divi32) = HermesValue::encodeDoubleValue(arg0 / arg1);
ip = NEXTINST(Divi32);
DISPATCH;
}
CASE(Divu32) {
const uint32_t arg0 = (uint32_t)(int32_t)(O2REG(Divu32).getNumber());
const uint32_t arg1 = (uint32_t)(int32_t)(O3REG(Divu32).getNumber());
O1REG(Divu32) = HermesValue::encodeDoubleValue((int32_t)(arg0 / arg1));
ip = NEXTINST(Divu32);
DISPATCH;
}
CASE(Loadi8) {
auto *mem = vmcast<JSTypedArrayBase>(O2REG(Loadi8));
int8_t *basePtr = reinterpret_cast<int8_t *>(mem->begin(runtime));
const uint32_t addr = (uint32_t)(int32_t)(O3REG(Loadi8).getNumber());
O1REG(Loadi8) = HermesValue::encodeNumberValue(basePtr[addr]);
ip = NEXTINST(Loadi8);
DISPATCH;
}
CASE(Loadu8) {
auto *mem = vmcast<JSTypedArrayBase>(O2REG(Loadu8));
uint8_t *basePtr = reinterpret_cast<uint8_t *>(mem->begin(runtime));
const uint32_t addr = (uint32_t)(int32_t)(O3REG(Loadu8).getNumber());
O1REG(Loadu8) = HermesValue::encodeNumberValue(basePtr[addr]);
ip = NEXTINST(Loadu8);
DISPATCH;
}
CASE(Loadi16) {
auto *mem = vmcast<JSTypedArrayBase>(O2REG(Loadi16));
int16_t *basePtr = reinterpret_cast<int16_t *>(mem->begin(runtime));
const uint32_t addr = (uint32_t)(int32_t)(O3REG(Loadi16).getNumber());
O1REG(Loadi16) = HermesValue::encodeNumberValue(basePtr[addr >> 1]);
ip = NEXTINST(Loadi16);
DISPATCH;
}
CASE(Loadu16) {
auto *mem = vmcast<JSTypedArrayBase>(O2REG(Loadu16));
uint16_t *basePtr = reinterpret_cast<uint16_t *>(mem->begin(runtime));
const uint32_t addr = (uint32_t)(int32_t)(O3REG(Loadu16).getNumber());
O1REG(Loadu16) = HermesValue::encodeNumberValue(basePtr[addr >> 1]);
ip = NEXTINST(Loadu16);
DISPATCH;
}
CASE(Loadi32) {
auto *mem = vmcast<JSTypedArrayBase>(O2REG(Loadi32));
int32_t *basePtr = reinterpret_cast<int32_t *>(mem->begin(runtime));
const uint32_t addr = (uint32_t)(int32_t)(O3REG(Loadi32).getNumber());
O1REG(Loadi32) = HermesValue::encodeNumberValue(basePtr[addr >> 2]);
ip = NEXTINST(Loadi32);
DISPATCH;
}
CASE(Loadu32) {
auto *mem = vmcast<JSTypedArrayBase>(O2REG(Loadu32));
uint32_t *basePtr = reinterpret_cast<uint32_t *>(mem->begin(runtime));
const uint32_t addr = (uint32_t)(int32_t)(O3REG(Loadu32).getNumber());
O1REG(Loadu32) =
HermesValue::encodeNumberValue((int32_t)(basePtr[addr >> 2]));
ip = NEXTINST(Loadu32);
DISPATCH;
}
CASE(Store8) {
auto *mem = vmcast<JSTypedArrayBase>(O1REG(Store8));
int8_t *basePtr = reinterpret_cast<int8_t *>(mem->begin(runtime));
const uint32_t addr = (uint32_t)(int32_t)(O2REG(Store8).getNumber());
basePtr[addr] = (int8_t)(int32_t)(O3REG(Store8).getNumber());
ip = NEXTINST(Store8);
DISPATCH;
}
CASE(Store16) {
auto *mem = vmcast<JSTypedArrayBase>(O1REG(Store16));
int16_t *basePtr = reinterpret_cast<int16_t *>(mem->begin(runtime));
const uint32_t addr = (uint32_t)(int32_t)(O2REG(Store16).getNumber());
basePtr[addr >> 1] = (int16_t)(int32_t)(O3REG(Store16).getNumber());
ip = NEXTINST(Store16);
DISPATCH;
}
CASE(Store32) {
auto *mem = vmcast<JSTypedArrayBase>(O1REG(Store32));
int32_t *basePtr = reinterpret_cast<int32_t *>(mem->begin(runtime));
const uint32_t addr = (uint32_t)(int32_t)(O2REG(Store32).getNumber());
basePtr[addr >> 2] = (int32_t)(O3REG(Store32).getNumber());
// A nop for now.
ip = NEXTINST(Store32);
DISPATCH;
}
#endif
CASE(_last) {
hermes_fatal("Invalid opcode _last");
}
}
hermes_fatal(
"All opcodes should dispatch to the next and not fallthrough "
"to here");
// We arrive here if we couldn't allocate the registers for the current frame.
stackOverflow:
CAPTURE_IP(runtime.raiseStackOverflow(
Runtime::StackOverflowKind::JSRegisterStack));
// We arrive here when we raised an exception in a callee, but we don't want
// the callee to be able to handle it.
handleExceptionInParent:
// Restore the caller code block and IP.
curCodeBlock = FRAME.getSavedCodeBlock();
ip = FRAME.getSavedIP();
// Pop to the previous frame where technically the error happened.
frameRegs = &runtime.restoreStackAndPreviousFrame(FRAME).getFirstLocalRef();
// If we are coming from native code, return.
if (!curCodeBlock)
return ExecutionStatus::EXCEPTION;
// Return because of recursive calling structure
#ifdef HERMESVM_PROFILER_EXTERN
return ExecutionStatus::EXCEPTION;
#endif
// Handle the exception.
exception:
UPDATE_OPCODE_TIME_SPENT;
assert(
!runtime.thrownValue_.isEmpty() &&
"thrownValue unavailable at exception");
bool catchable = true;
// If this is an Error object that was thrown internally, it didn't have
// access to the current codeblock and IP, so collect the stack trace here.
if (auto *jsError = dyn_vmcast<JSError>(runtime.thrownValue_)) {
catchable = jsError->catchable();
if (!jsError->getStackTrace()) {
// Temporarily clear the thrown value for following operations.
CAPTURE_IP_ASSIGN(
auto errorHandle,
runtime.makeHandle(vmcast<JSError>(runtime.thrownValue_)));
runtime.clearThrownValue();
CAPTURE_IP(JSError::recordStackTrace(
errorHandle, runtime, false, curCodeBlock, ip));
// Restore the thrown value.
runtime.setThrownValue(errorHandle.getHermesValue());
}
}
gcScope.flushToSmallCount(KEEP_HANDLES);
tmpHandle.clear();
#ifdef HERMES_ENABLE_DEBUGGER
if (SingleStep) {
// If we're single stepping, don't bother with any more checks,
// and simply signal that we should continue execution with an exception.
state.codeBlock = curCodeBlock;
state.offset = CUROFFSET;
return ExecutionStatus::EXCEPTION;
}
using PauseOnThrowMode = facebook::hermes::debugger::PauseOnThrowMode;
auto mode = runtime.debugger_.getPauseOnThrowMode();
if (mode != PauseOnThrowMode::None) {
if (!runtime.debugger_.isDebugging()) {
// Determine whether the PauseOnThrowMode requires us to stop here.
bool caught =
runtime.debugger_
.findCatchTarget(InterpreterState(curCodeBlock, CUROFFSET))
.hasValue();
bool shouldStop = mode == PauseOnThrowMode::All ||
(mode == PauseOnThrowMode::Uncaught && !caught);
if (shouldStop) {
// When runDebugger is invoked after an exception,
// stepping should never happen internally.
// Any step is a step to an exception handler, which we do
// directly here in the interpreter.
// Thus, the result state should be the same as the input state.
InterpreterState tmpState{curCodeBlock, (uint32_t)CUROFFSET};
CAPTURE_IP_ASSIGN(
ExecutionStatus resultStatus,
runtime.debugger_.runDebugger(
Debugger::RunReason::Exception, tmpState));
(void)resultStatus;
assert(
tmpState == InterpreterState(curCodeBlock, CUROFFSET) &&
"not allowed to step internally in a pauseOnThrow");
gcScope.flushToSmallCount(KEEP_HANDLES);
}
}
}
#endif
int32_t handlerOffset = 0;
// If the exception is not catchable, skip found catch blocks.
while (((handlerOffset = curCodeBlock->findCatchTargetOffset(CUROFFSET)) ==
-1) ||
!catchable) {
PROFILER_EXIT_FUNCTION(curCodeBlock);
#ifdef HERMES_ENABLE_ALLOCATION_LOCATION_TRACES
runtime.popCallStack();
#endif
// Restore the code block and IP.
curCodeBlock = FRAME.getSavedCodeBlock();
ip = FRAME.getSavedIP();
// Pop a stack frame.
frameRegs =
&runtime.restoreStackAndPreviousFrame(FRAME).getFirstLocalRef();
SLOW_DEBUG(
dbgs() << "function exit with exception: restored stackLevel="
<< runtime.getStackLevel() << "\n");
// Are we returning to native code?
if (!curCodeBlock) {
SLOW_DEBUG(
dbgs()
<< "function exit with exception: returning to native code\n");
return ExecutionStatus::EXCEPTION;
}
assert(
isCallType(ip->opCode) &&
"return address is not Call-type instruction");
// Return because of recursive calling structure
#ifdef HERMESVM_PROFILER_EXTERN
return ExecutionStatus::EXCEPTION;
#endif
}
INIT_STATE_FOR_CODEBLOCK(curCodeBlock);
ip = IPADD(handlerOffset - CUROFFSET);
}
}
} // namespace vm
} // namespace hermes
#undef DEBUG_TYPE