Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jit/WarpCacheIRTranspiler.h"
#include "mozilla/Casting.h"
#include "mozilla/Maybe.h"
#include "jsmath.h"
#include "builtin/DataViewObject.h"
#include "builtin/MapObject.h"
#include "jit/AtomicOp.h"
#include "jit/CacheIR.h"
#include "jit/CacheIRCompiler.h"
#include "jit/CacheIROpsGenerated.h"
#include "jit/CacheIRReader.h"
#include "jit/LIR.h"
#include "jit/MIR.h"
#include "jit/MIRGenerator.h"
#include "jit/MIRGraph.h"
#include "jit/WarpBuilder.h"
#include "jit/WarpBuilderShared.h"
#include "jit/WarpSnapshot.h"
#include "js/ScalarType.h" // js::Scalar::Type
#include "vm/ArgumentsObject.h"
#include "vm/BytecodeLocation.h"
#include "wasm/WasmCode.h"
#include "gc/ObjectKind-inl.h"
#include "vm/NativeObject-inl.h"
#include "wasm/WasmInstance-inl.h"
using namespace js;
using namespace js::jit;
// The CacheIR transpiler generates MIR from Baseline CacheIR.
class MOZ_RAII WarpCacheIRTranspiler : public WarpBuilderShared {
WarpBuilder* builder_;
BytecodeLocation loc_;
const CacheIRStubInfo* stubInfo_;
const uint8_t* stubData_;
// Vector mapping OperandId to corresponding MDefinition.
using MDefinitionStackVector = Vector<MDefinition*, 8, SystemAllocPolicy>;
MDefinitionStackVector operands_;
CallInfo* callInfo_;
// Array mapping call arguments to OperandId.
using ArgumentKindArray =
mozilla::EnumeratedArray<ArgumentKind, ArgumentKind::NumKinds, OperandId>;
ArgumentKindArray argumentOperandIds_;
void setArgumentId(ArgumentKind kind, OperandId id) {
MOZ_ASSERT(kind != ArgumentKind::Callee);
MOZ_ASSERT(!argumentOperandIds_[kind].valid());
argumentOperandIds_[kind] = id;
}
void updateArgumentsFromOperands();
#ifdef DEBUG
// Used to assert that there is only one effectful instruction
// per stub. And that this instruction has a resume point.
MInstruction* effectful_ = nullptr;
bool pushedResult_ = false;
#endif
inline void addUnchecked(MInstruction* ins) {
current->add(ins);
// If we have not set a more specific bailout kind, mark this instruction
// as transpiled CacheIR. If one of these instructions bails out, we
// expect to hit the baseline fallback stub and invalidate the Warp script
// in tryAttach.
if (ins->bailoutKind() == BailoutKind::Unknown) {
ins->setBailoutKind(BailoutKind::TranspiledCacheIR);
}
}
inline void add(MInstruction* ins) {
MOZ_ASSERT(!ins->isEffectful());
addUnchecked(ins);
}
inline void addEffectful(MInstruction* ins) {
MOZ_ASSERT(ins->isEffectful());
MOZ_ASSERT(!effectful_, "Can only have one effectful instruction");
addUnchecked(ins);
#ifdef DEBUG
effectful_ = ins;
#endif
}
// Bypasses all checks in addEffectful. Only used for testing functions.
inline void addEffectfulUnsafe(MInstruction* ins) {
MOZ_ASSERT(ins->isEffectful());
addUnchecked(ins);
}
[[nodiscard]] bool resumeAfterUnchecked(MInstruction* ins) {
return WarpBuilderShared::resumeAfter(ins, loc_);
}
[[nodiscard]] bool resumeAfter(MInstruction* ins) {
MOZ_ASSERT(effectful_ == ins);
return resumeAfterUnchecked(ins);
}
// CacheIR instructions writing to the IC's result register (the *Result
// instructions) must call this to push the result onto the virtual stack.
void pushResult(MDefinition* result) {
MOZ_ASSERT(!pushedResult_, "Can't have more than one result");
current->push(result);
#ifdef DEBUG
pushedResult_ = true;
#endif
}
MDefinition* getOperand(OperandId id) const { return operands_[id.id()]; }
void setOperand(OperandId id, MDefinition* def) { operands_[id.id()] = def; }
[[nodiscard]] bool defineOperand(OperandId id, MDefinition* def) {
MOZ_ASSERT(id.id() == operands_.length());
return operands_.append(def);
}
uintptr_t readStubWord(uint32_t offset) {
return stubInfo_->getStubRawWord(stubData_, offset);
}
Shape* shapeStubField(uint32_t offset) {
return reinterpret_cast<Shape*>(readStubWord(offset));
}
GetterSetter* getterSetterStubField(uint32_t offset) {
return reinterpret_cast<GetterSetter*>(readStubWord(offset));
}
const JSClass* classStubField(uint32_t offset) {
return reinterpret_cast<const JSClass*>(readStubWord(offset));
}
JSString* stringStubField(uint32_t offset) {
return reinterpret_cast<JSString*>(readStubWord(offset));
}
JS::Symbol* symbolStubField(uint32_t offset) {
return reinterpret_cast<JS::Symbol*>(readStubWord(offset));
}
BaseScript* baseScriptStubField(uint32_t offset) {
return reinterpret_cast<BaseScript*>(readStubWord(offset));
}
const JSJitInfo* jitInfoStubField(uint32_t offset) {
return reinterpret_cast<const JSJitInfo*>(readStubWord(offset));
}
JSNative jsnativeStubField(uint32_t offset) {
return reinterpret_cast<JSNative>(readStubWord(offset));
}
JS::ExpandoAndGeneration* expandoAndGenerationField(uint32_t offset) {
return reinterpret_cast<JS::ExpandoAndGeneration*>(readStubWord(offset));
}
const wasm::FuncExport* wasmFuncExportField(uint32_t offset) {
return reinterpret_cast<const wasm::FuncExport*>(readStubWord(offset));
}
NativeIteratorListHead* nativeIteratorListHeadStubField(uint32_t offset) {
return reinterpret_cast<NativeIteratorListHead*>(readStubWord(offset));
}
gc::Heap allocSiteInitialHeapField(uint32_t offset) {
uintptr_t word = readStubWord(offset);
MOZ_ASSERT(word == uintptr_t(gc::Heap::Default) ||
word == uintptr_t(gc::Heap::Tenured));
return gc::Heap(word);
}
const void* rawPointerField(uint32_t offset) {
return reinterpret_cast<const void*>(readStubWord(offset));
}
jsid idStubField(uint32_t offset) {
return jsid::fromRawBits(readStubWord(offset));
}
int32_t int32StubField(uint32_t offset) {
return static_cast<int32_t>(readStubWord(offset));
}
uint32_t uint32StubField(uint32_t offset) {
return static_cast<uint32_t>(readStubWord(offset));
}
uint64_t uint64StubField(uint32_t offset) {
return static_cast<uint64_t>(stubInfo_->getStubRawInt64(stubData_, offset));
}
Value valueStubField(uint32_t offset) {
uint64_t raw =
static_cast<uint64_t>(stubInfo_->getStubRawInt64(stubData_, offset));
Value val = Value::fromRawBits(raw);
MOZ_ASSERT_IF(val.isGCThing(), val.toGCThing()->isTenured());
return val;
}
double doubleStubField(uint32_t offset) {
uint64_t raw =
static_cast<uint64_t>(stubInfo_->getStubRawInt64(stubData_, offset));
return mozilla::BitwiseCast<double>(raw);
}
// This must only be called when the caller knows the object is tenured and
// not a nursery index.
JSObject* tenuredObjectStubField(uint32_t offset) {
WarpObjectField field = WarpObjectField::fromData(readStubWord(offset));
return field.toObject();
}
// Returns either MConstant or MNurseryIndex. See WarpObjectField.
MInstruction* objectStubField(uint32_t offset);
const JSClass* classForGuardClassKind(GuardClassKind kind);
[[nodiscard]] bool emitGuardTo(ValOperandId inputId, MIRType type);
[[nodiscard]] bool emitToString(OperandId inputId, StringOperandId resultId);
template <typename T>
[[nodiscard]] bool emitDoubleBinaryArithResult(NumberOperandId lhsId,
NumberOperandId rhsId);
template <typename T>
[[nodiscard]] bool emitInt32BinaryArithResult(Int32OperandId lhsId,
Int32OperandId rhsId);
template <typename T>
[[nodiscard]] bool emitBigIntBinaryArithResult(BigIntOperandId lhsId,
BigIntOperandId rhsId);
template <typename T>
[[nodiscard]] bool emitBigIntBinaryArithEffectfulResult(
BigIntOperandId lhsId, BigIntOperandId rhsId);
template <typename T>
[[nodiscard]] bool emitBigIntUnaryArithResult(BigIntOperandId inputId);
[[nodiscard]] bool emitCompareResult(JSOp op, OperandId lhsId,
OperandId rhsId,
MCompare::CompareType compareType);
[[nodiscard]] bool emitTruthyResult(OperandId inputId);
[[nodiscard]] bool emitNewIteratorResult(MNewIterator::Type type,
uint32_t templateObjectOffset);
MInstruction* addBoundsCheck(MDefinition* index, MDefinition* length);
[[nodiscard]] MInstruction* convertToBoolean(MDefinition* input);
bool emitAddAndStoreSlotShared(MAddAndStoreSlot::Kind kind,
ObjOperandId objId, uint32_t offsetOffset,
ValOperandId rhsId, uint32_t newShapeOffset);
void addDataViewData(MDefinition* obj, Scalar::Type type,
MDefinition** offset, MInstruction** elements);
[[nodiscard]] bool emitAtomicsBinaryOp(ObjOperandId objId,
IntPtrOperandId indexId,
uint32_t valueId,
Scalar::Type elementType,
bool forEffect, AtomicOp op);
[[nodiscard]] bool emitLoadArgumentSlot(ValOperandId resultId,
uint32_t slotIndex);
// Calls are either Native (native function without a JitEntry),
// a DOM Native (native function with a JitInfo OpType::Method),
// or Scripted (scripted function or native function with a JitEntry).
enum class CallKind { Native, DOM, Scripted };
[[nodiscard]] bool updateCallInfo(MDefinition* callee, CallFlags flags);
[[nodiscard]] bool emitCallFunction(ObjOperandId calleeId,
Int32OperandId argcId,
mozilla::Maybe<ObjOperandId> thisObjId,
CallFlags flags, CallKind kind);
[[nodiscard]] bool emitFunApplyArgsObj(WrappedFunction* wrappedTarget,
CallFlags flags);
MDefinition* convertWasmArg(MDefinition* arg, wasm::ValType::Kind kind);
WrappedFunction* maybeWrappedFunction(MDefinition* callee, CallKind kind,
uint16_t nargs, FunctionFlags flags);
WrappedFunction* maybeCallTarget(MDefinition* callee, CallKind kind);
bool maybeCreateThis(MDefinition* callee, CallFlags flags, CallKind kind);
[[nodiscard]] bool emitCallGetterResult(CallKind kind,
ValOperandId receiverId,
uint32_t getterOffset, bool sameRealm,
uint32_t nargsAndFlagsOffset);
[[nodiscard]] bool emitCallSetter(CallKind kind, ObjOperandId receiverId,
uint32_t setterOffset, ValOperandId rhsId,
bool sameRealm,
uint32_t nargsAndFlagsOffset);
#ifndef JS_CODEGEN_X86
[[nodiscard]] bool emitCallScriptedProxyGetShared(
MDefinition* target, MDefinition* receiver, MDefinition* handler,
MDefinition* id, MDefinition* trapDef, WrappedFunction* trap);
#endif
CACHE_IR_TRANSPILER_GENERATED
public:
WarpCacheIRTranspiler(WarpBuilder* builder, BytecodeLocation loc,
CallInfo* callInfo, const WarpCacheIR* cacheIRSnapshot)
: WarpBuilderShared(builder->snapshot(), builder->mirGen(),
builder->currentBlock()),
builder_(builder),
loc_(loc),
stubInfo_(cacheIRSnapshot->stubInfo()),
stubData_(cacheIRSnapshot->stubData()),
callInfo_(callInfo) {}
[[nodiscard]] bool transpile(std::initializer_list<MDefinition*> inputs);
};
bool WarpCacheIRTranspiler::transpile(
std::initializer_list<MDefinition*> inputs) {
if (!operands_.append(inputs.begin(), inputs.end())) {
return false;
}
CacheIRReader reader(stubInfo_);
do {
CacheOp op = reader.readOp();
switch (op) {
#define DEFINE_OP(op, ...) \
case CacheOp::op: \
if (!emit##op(reader)) { \
return false; \
} \
break;
CACHE_IR_TRANSPILER_OPS(DEFINE_OP)
#undef DEFINE_OP
default:
fprintf(stderr, "Unsupported op: %s\n", CacheIROpNames[size_t(op)]);
MOZ_CRASH("Unsupported op");
}
} while (reader.more());
// Effectful instructions should have a resume point. MIonToWasmCall is an
// exception: we can attach the resume point to the MInt64ToBigInt instruction
// instead.
MOZ_ASSERT_IF(effectful_,
effectful_->resumePoint() || effectful_->isIonToWasmCall());
return true;
}
MInstruction* WarpCacheIRTranspiler::objectStubField(uint32_t offset) {
WarpObjectField field = WarpObjectField::fromData(readStubWord(offset));
if (field.isNurseryIndex()) {
auto* ins = MNurseryObject::New(alloc(), field.toNurseryIndex());
add(ins);
return ins;
}
auto* ins = MConstant::NewObject(alloc(), field.toObject());
add(ins);
return ins;
}
bool WarpCacheIRTranspiler::emitGuardClass(ObjOperandId objId,
GuardClassKind kind) {
MDefinition* def = getOperand(objId);
MInstruction* ins;
if (kind == GuardClassKind::JSFunction) {
ins = MGuardToFunction::New(alloc(), def);
} else {
const JSClass* classp = classForGuardClassKind(kind);
ins = MGuardToClass::New(alloc(), def, classp);
}
add(ins);
setOperand(objId, ins);
return true;
}
const JSClass* WarpCacheIRTranspiler::classForGuardClassKind(
GuardClassKind kind) {
switch (kind) {
case GuardClassKind::Array:
return &ArrayObject::class_;
case GuardClassKind::PlainObject:
return &PlainObject::class_;
case GuardClassKind::ArrayBuffer:
return &ArrayBufferObject::class_;
case GuardClassKind::SharedArrayBuffer:
return &SharedArrayBufferObject::class_;
case GuardClassKind::DataView:
return &DataViewObject::class_;
case GuardClassKind::MappedArguments:
return &MappedArgumentsObject::class_;
case GuardClassKind::UnmappedArguments:
return &UnmappedArgumentsObject::class_;
case GuardClassKind::WindowProxy:
return mirGen().runtime->maybeWindowProxyClass();
case GuardClassKind::Set:
return &SetObject::class_;
case GuardClassKind::Map:
return &MapObject::class_;
case GuardClassKind::BoundFunction:
return &BoundFunctionObject::class_;
case GuardClassKind::JSFunction:
break;
}
MOZ_CRASH("unexpected kind");
}
bool WarpCacheIRTranspiler::emitGuardAnyClass(ObjOperandId objId,
uint32_t claspOffset) {
MDefinition* def = getOperand(objId);
const JSClass* classp = classStubField(claspOffset);
auto* ins = MGuardToClass::New(alloc(), def, classp);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardShape(ObjOperandId objId,
uint32_t shapeOffset) {
MDefinition* def = getOperand(objId);
// No read barrier is required because snapshot data is not weak and is traced
// as part of IonCompileTask.
Shape* shape = shapeStubField(shapeOffset);
auto* ins = MGuardShape::New(alloc(), def, shape);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardMultipleShapes(ObjOperandId objId,
uint32_t shapesOffset) {
MDefinition* def = getOperand(objId);
MInstruction* shapeList = objectStubField(shapesOffset);
auto* ins = MGuardMultipleShapes::New(alloc(), def, shapeList);
if (builder_->info().inlineScriptTree()->hasSharedICScript()) {
ins->setBailoutKind(BailoutKind::MonomorphicInlinedStubFolding);
}
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardNullProto(ObjOperandId objId) {
MDefinition* def = getOperand(objId);
auto* ins = MGuardNullProto::New(alloc(), def);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardIsNativeObject(ObjOperandId objId) {
MDefinition* obj = getOperand(objId);
auto* ins = MGuardIsNativeObject::New(alloc(), obj);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardIsProxy(ObjOperandId objId) {
MDefinition* obj = getOperand(objId);
auto* ins = MGuardIsProxy::New(alloc(), obj);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardIsNotProxy(ObjOperandId objId) {
MDefinition* obj = getOperand(objId);
auto* ins = MGuardIsNotProxy::New(alloc(), obj);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardIsNotDOMProxy(ObjOperandId objId) {
MDefinition* obj = getOperand(objId);
auto* ins = MGuardIsNotDOMProxy::New(alloc(), obj);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardHasGetterSetter(
ObjOperandId objId, uint32_t idOffset, uint32_t getterSetterOffset) {
MDefinition* obj = getOperand(objId);
jsid id = idStubField(idOffset);
GetterSetter* gs = getterSetterStubField(getterSetterOffset);
auto* ins = MGuardHasGetterSetter::New(alloc(), obj, id, gs);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitProxyGetResult(ObjOperandId objId,
uint32_t idOffset) {
MDefinition* obj = getOperand(objId);
jsid id = idStubField(idOffset);
auto* ins = MProxyGet::New(alloc(), obj, id);
addEffectful(ins);
pushResult(ins);
return resumeAfter(ins);
}
bool WarpCacheIRTranspiler::emitProxyGetByValueResult(ObjOperandId objId,
ValOperandId idId) {
MDefinition* obj = getOperand(objId);
MDefinition* id = getOperand(idId);
auto* ins = MProxyGetByValue::New(alloc(), obj, id);
addEffectful(ins);
pushResult(ins);
return resumeAfter(ins);
}
bool WarpCacheIRTranspiler::emitProxyHasPropResult(ObjOperandId objId,
ValOperandId idId,
bool hasOwn) {
MDefinition* obj = getOperand(objId);
MDefinition* id = getOperand(idId);
auto* ins = MProxyHasProp::New(alloc(), obj, id, hasOwn);
addEffectful(ins);
pushResult(ins);
return resumeAfter(ins);
}
bool WarpCacheIRTranspiler::emitProxySet(ObjOperandId objId, uint32_t idOffset,
ValOperandId rhsId, bool strict) {
MDefinition* obj = getOperand(objId);
jsid id = idStubField(idOffset);
MDefinition* rhs = getOperand(rhsId);
auto* ins = MProxySet::New(alloc(), obj, rhs, id, strict);
addEffectful(ins);
return resumeAfter(ins);
}
bool WarpCacheIRTranspiler::emitProxySetByValue(ObjOperandId objId,
ValOperandId idId,
ValOperandId rhsId,
bool strict) {
MDefinition* obj = getOperand(objId);
MDefinition* id = getOperand(idId);
MDefinition* rhs = getOperand(rhsId);
auto* ins = MProxySetByValue::New(alloc(), obj, id, rhs, strict);
addEffectful(ins);
return resumeAfter(ins);
}
bool WarpCacheIRTranspiler::emitCallSetArrayLength(ObjOperandId objId,
bool strict,
ValOperandId rhsId) {
MDefinition* obj = getOperand(objId);
MDefinition* rhs = getOperand(rhsId);
auto* ins = MCallSetArrayLength::New(alloc(), obj, rhs, strict);
addEffectful(ins);
return resumeAfter(ins);
}
bool WarpCacheIRTranspiler::emitCallDOMGetterResult(ObjOperandId objId,
uint32_t jitInfoOffset) {
MDefinition* obj = getOperand(objId);
const JSJitInfo* jitInfo = jitInfoStubField(jitInfoOffset);
MInstruction* ins;
if (jitInfo->isAlwaysInSlot) {
ins = MGetDOMMember::New(alloc(), jitInfo, obj, nullptr, nullptr);
} else {
// TODO(post-Warp): realms, guard operands (movable?).
ins = MGetDOMProperty::New(alloc(), jitInfo, DOMObjectKind::Native,
(JS::Realm*)mirGen().realm->realmPtr(), obj,
nullptr, nullptr);
}
if (!ins) {
return false;
}
if (ins->isEffectful()) {
addEffectful(ins);
pushResult(ins);
return resumeAfter(ins);
}
add(ins);
pushResult(ins);
return true;
}
bool WarpCacheIRTranspiler::emitCallDOMSetter(ObjOperandId objId,
uint32_t jitInfoOffset,
ValOperandId rhsId) {
MDefinition* obj = getOperand(objId);
const JSJitInfo* jitInfo = jitInfoStubField(jitInfoOffset);
MDefinition* value = getOperand(rhsId);
MOZ_ASSERT(jitInfo->type() == JSJitInfo::Setter);
auto* set =
MSetDOMProperty::New(alloc(), jitInfo->setter, DOMObjectKind::Native,
(JS::Realm*)mirGen().realm->realmPtr(), obj, value);
addEffectful(set);
return resumeAfter(set);
}
bool WarpCacheIRTranspiler::emitLoadDOMExpandoValue(ObjOperandId objId,
ValOperandId resultId) {
MDefinition* proxy = getOperand(objId);
auto* ins = MLoadDOMExpandoValue::New(alloc(), proxy);
add(ins);
return defineOperand(resultId, ins);
}
bool WarpCacheIRTranspiler::emitLoadDOMExpandoValueGuardGeneration(
ObjOperandId objId, uint32_t expandoAndGenerationOffset,
uint32_t generationOffset, ValOperandId resultId) {
MDefinition* proxy = getOperand(objId);
JS::ExpandoAndGeneration* expandoAndGeneration =
expandoAndGenerationField(expandoAndGenerationOffset);
uint64_t generation = uint64StubField(generationOffset);
auto* ins = MLoadDOMExpandoValueGuardGeneration::New(
alloc(), proxy, expandoAndGeneration, generation);
add(ins);
return defineOperand(resultId, ins);
}
bool WarpCacheIRTranspiler::emitLoadDOMExpandoValueIgnoreGeneration(
ObjOperandId objId, ValOperandId resultId) {
MDefinition* proxy = getOperand(objId);
auto* ins = MLoadDOMExpandoValueIgnoreGeneration::New(alloc(), proxy);
add(ins);
return defineOperand(resultId, ins);
}
bool WarpCacheIRTranspiler::emitGuardDOMExpandoMissingOrGuardShape(
ValOperandId expandoId, uint32_t shapeOffset) {
MDefinition* expando = getOperand(expandoId);
Shape* shape = shapeStubField(shapeOffset);
auto* ins = MGuardDOMExpandoMissingOrGuardShape::New(alloc(), expando, shape);
add(ins);
setOperand(expandoId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitMegamorphicLoadSlotResult(ObjOperandId objId,
uint32_t nameOffset) {
MDefinition* obj = getOperand(objId);
PropertyName* name = stringStubField(nameOffset)->asAtom().asPropertyName();
auto* ins = MMegamorphicLoadSlot::New(alloc(), obj, NameToId(name));
add(ins);
pushResult(ins);
return true;
}
bool WarpCacheIRTranspiler::emitMegamorphicLoadSlotByValueResult(
ObjOperandId objId, ValOperandId idId) {
MDefinition* obj = getOperand(objId);
MDefinition* id = getOperand(idId);
auto* ins = MMegamorphicLoadSlotByValue::New(alloc(), obj, id);
add(ins);
pushResult(ins);
return true;
}
bool WarpCacheIRTranspiler::emitMegamorphicStoreSlot(ObjOperandId objId,
uint32_t idOffset,
ValOperandId rhsId,
bool strict) {
MDefinition* obj = getOperand(objId);
jsid id = idStubField(idOffset);
MDefinition* rhs = getOperand(rhsId);
auto* ins = MMegamorphicStoreSlot::New(alloc(), obj, rhs, id, strict);
addEffectful(ins);
return resumeAfter(ins);
}
bool WarpCacheIRTranspiler::emitMegamorphicHasPropResult(ObjOperandId objId,
ValOperandId idId,
bool hasOwn) {
MDefinition* obj = getOperand(objId);
MDefinition* id = getOperand(idId);
auto* ins = MMegamorphicHasProp::New(alloc(), obj, id, hasOwn);
add(ins);
pushResult(ins);
return true;
}
bool WarpCacheIRTranspiler::emitMegamorphicSetElement(ObjOperandId objId,
ValOperandId idId,
ValOperandId rhsId,
bool strict) {
MDefinition* obj = getOperand(objId);
MDefinition* id = getOperand(idId);
MDefinition* rhs = getOperand(rhsId);
auto* ins = MMegamorphicSetElement::New(alloc(), obj, id, rhs, strict);
addEffectful(ins);
return resumeAfter(ins);
}
bool WarpCacheIRTranspiler::emitObjectToIteratorResult(
ObjOperandId objId, uint32_t enumeratorsAddrOffset) {
MDefinition* obj = getOperand(objId);
NativeIteratorListHead* enumeratorsAddr =
nativeIteratorListHeadStubField(enumeratorsAddrOffset);
auto* ins = MObjectToIterator::New(alloc(), obj, enumeratorsAddr);
addEffectful(ins);
pushResult(ins);
if (!resumeAfter(ins)) {
return false;
}
return true;
}
bool WarpCacheIRTranspiler::emitValueToIteratorResult(ValOperandId valId) {
MDefinition* val = getOperand(valId);
auto* ins = MValueToIterator::New(alloc(), val);
addEffectful(ins);
pushResult(ins);
return resumeAfter(ins);
}
bool WarpCacheIRTranspiler::emitGuardIsNotArrayBufferMaybeShared(
ObjOperandId objId) {
MDefinition* obj = getOperand(objId);
auto* ins = MGuardIsNotArrayBufferMaybeShared::New(alloc(), obj);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardIsTypedArray(ObjOperandId objId) {
MDefinition* obj = getOperand(objId);
auto* ins = MGuardIsTypedArray::New(alloc(), obj);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardHasProxyHandler(ObjOperandId objId,
uint32_t handlerOffset) {
MDefinition* obj = getOperand(objId);
const void* handler = rawPointerField(handlerOffset);
auto* ins = MGuardHasProxyHandler::New(alloc(), obj, handler);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardProto(ObjOperandId objId,
uint32_t protoOffset) {
MDefinition* def = getOperand(objId);
MDefinition* proto = objectStubField(protoOffset);
auto* ins = MGuardProto::New(alloc(), def, proto);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardDynamicSlotIsSpecificObject(
ObjOperandId objId, ObjOperandId expectedId, uint32_t slotOffset) {
size_t slotIndex = int32StubField(slotOffset);
MDefinition* obj = getOperand(objId);
MDefinition* expected = getOperand(expectedId);
auto* slots = MSlots::New(alloc(), obj);
add(slots);
auto* load = MLoadDynamicSlot::New(alloc(), slots, slotIndex);
add(load);
auto* unbox = MUnbox::New(alloc(), load, MIRType::Object, MUnbox::Fallible);
add(unbox);
auto* guard = MGuardObjectIdentity::New(alloc(), unbox, expected,
/* bailOnEquality = */ false);
add(guard);
return true;
}
bool WarpCacheIRTranspiler::emitLoadDynamicSlot(ValOperandId resultId,
ObjOperandId objId,
uint32_t slotOffset) {
size_t slotIndex = int32StubField(slotOffset);
MDefinition* obj = getOperand(objId);
auto* slots = MSlots::New(alloc(), obj);
add(slots);
auto* load = MLoadDynamicSlot::New(alloc(), slots, slotIndex);
add(load);
return defineOperand(resultId, load);
}
bool WarpCacheIRTranspiler::emitGuardDynamicSlotIsNotObject(
ObjOperandId objId, uint32_t slotOffset) {
size_t slotIndex = int32StubField(slotOffset);
MDefinition* obj = getOperand(objId);
auto* slots = MSlots::New(alloc(), obj);
add(slots);
auto* load = MLoadDynamicSlot::New(alloc(), slots, slotIndex);
add(load);
auto* guard = MGuardIsNotObject::New(alloc(), load);
add(guard);
return true;
}
bool WarpCacheIRTranspiler::emitGuardFixedSlotValue(ObjOperandId objId,
uint32_t offsetOffset,
uint32_t valOffset) {
MDefinition* obj = getOperand(objId);
size_t offset = int32StubField(offsetOffset);
Value val = valueStubField(valOffset);
uint32_t slotIndex = NativeObject::getFixedSlotIndexFromOffset(offset);
auto* load = MLoadFixedSlot::New(alloc(), obj, slotIndex);
add(load);
auto* guard = MGuardValue::New(alloc(), load, val);
add(guard);
return true;
}
bool WarpCacheIRTranspiler::emitGuardDynamicSlotValue(ObjOperandId objId,
uint32_t offsetOffset,
uint32_t valOffset) {
MDefinition* obj = getOperand(objId);
size_t offset = int32StubField(offsetOffset);
Value val = valueStubField(valOffset);
size_t slotIndex = NativeObject::getDynamicSlotIndexFromOffset(offset);
auto* slots = MSlots::New(alloc(), obj);
add(slots);
auto* load = MLoadDynamicSlot::New(alloc(), slots, slotIndex);
add(load);
auto* guard = MGuardValue::New(alloc(), load, val);
add(guard);
return true;
}
bool WarpCacheIRTranspiler::emitLoadScriptedProxyHandler(ValOperandId resultId,
ObjOperandId objId) {
MDefinition* obj = getOperand(objId);
auto* load = MLoadScriptedProxyHandler::New(alloc(), obj);
add(load);
return defineOperand(resultId, load);
}
bool WarpCacheIRTranspiler::emitIdToStringOrSymbol(ValOperandId resultId,
ValOperandId idId) {
MDefinition* id = getOperand(idId);
auto* ins = MIdToStringOrSymbol::New(alloc(), id);
add(ins);
return defineOperand(resultId, ins);
}
bool WarpCacheIRTranspiler::emitGuardSpecificAtom(StringOperandId strId,
uint32_t expectedOffset) {
MDefinition* str = getOperand(strId);
JSString* expected = stringStubField(expectedOffset);
auto* ins = MGuardSpecificAtom::New(alloc(), str, &expected->asAtom());
add(ins);
setOperand(strId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardSpecificSymbol(SymbolOperandId symId,
uint32_t expectedOffset) {
MDefinition* symbol = getOperand(symId);
JS::Symbol* expected = symbolStubField(expectedOffset);
auto* ins = MGuardSpecificSymbol::New(alloc(), symbol, expected);
add(ins);
setOperand(symId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardSpecificInt32(Int32OperandId numId,
int32_t expected) {
MDefinition* num = getOperand(numId);
auto* ins = MGuardSpecificInt32::New(alloc(), num, expected);
add(ins);
setOperand(numId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardSpecificObject(ObjOperandId objId,
uint32_t expectedOffset) {
MDefinition* obj = getOperand(objId);
MDefinition* expected = objectStubField(expectedOffset);
auto* ins = MGuardObjectIdentity::New(alloc(), obj, expected,
/* bailOnEquality = */ false);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardSpecificFunction(
ObjOperandId objId, uint32_t expectedOffset, uint32_t nargsAndFlagsOffset) {
MDefinition* obj = getOperand(objId);
MDefinition* expected = objectStubField(expectedOffset);
uint32_t nargsAndFlags = uint32StubField(nargsAndFlagsOffset);
uint16_t nargs = nargsAndFlags >> 16;
FunctionFlags flags = FunctionFlags(uint16_t(nargsAndFlags));
auto* ins = MGuardSpecificFunction::New(alloc(), obj, expected, nargs, flags);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardFunctionScript(
ObjOperandId funId, uint32_t expectedOffset, uint32_t nargsAndFlagsOffset) {
MDefinition* fun = getOperand(funId);
BaseScript* expected = baseScriptStubField(expectedOffset);
uint32_t nargsAndFlags = uint32StubField(nargsAndFlagsOffset);
uint16_t nargs = nargsAndFlags >> 16;
FunctionFlags flags = FunctionFlags(uint16_t(nargsAndFlags));
auto* ins = MGuardFunctionScript::New(alloc(), fun, expected, nargs, flags);
add(ins);
setOperand(funId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardStringToIndex(StringOperandId strId,
Int32OperandId resultId) {
MDefinition* str = getOperand(strId);
auto* ins = MGuardStringToIndex::New(alloc(), str);
add(ins);
return defineOperand(resultId, ins);
}
bool WarpCacheIRTranspiler::emitGuardStringToInt32(StringOperandId strId,
Int32OperandId resultId) {
MDefinition* str = getOperand(strId);
auto* ins = MGuardStringToInt32::New(alloc(), str);
add(ins);
return defineOperand(resultId, ins);
}
bool WarpCacheIRTranspiler::emitGuardStringToNumber(StringOperandId strId,
NumberOperandId resultId) {
MDefinition* str = getOperand(strId);
auto* ins = MGuardStringToDouble::New(alloc(), str);
add(ins);
return defineOperand(resultId, ins);
}
bool WarpCacheIRTranspiler::emitGuardNoDenseElements(ObjOperandId objId) {
MDefinition* obj = getOperand(objId);
auto* ins = MGuardNoDenseElements::New(alloc(), obj);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardFunctionHasJitEntry(ObjOperandId funId,
bool constructing) {
MDefinition* fun = getOperand(funId);
uint16_t expectedFlags = FunctionFlags::HasJitEntryFlags(constructing);
uint16_t unexpectedFlags = 0;
auto* ins =
MGuardFunctionFlags::New(alloc(), fun, expectedFlags, unexpectedFlags);
add(ins);
setOperand(funId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardFunctionHasNoJitEntry(ObjOperandId funId) {
MDefinition* fun = getOperand(funId);
uint16_t expectedFlags = 0;
uint16_t unexpectedFlags =
FunctionFlags::HasJitEntryFlags(/*isConstructing=*/false);
auto* ins =
MGuardFunctionFlags::New(alloc(), fun, expectedFlags, unexpectedFlags);
add(ins);
setOperand(funId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardFunctionIsNonBuiltinCtor(
ObjOperandId funId) {
MDefinition* fun = getOperand(funId);
auto* ins = MGuardFunctionIsNonBuiltinCtor::New(alloc(), fun);
add(ins);
setOperand(funId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardFunctionIsConstructor(ObjOperandId funId) {
MDefinition* fun = getOperand(funId);
uint16_t expectedFlags = FunctionFlags::CONSTRUCTOR;
uint16_t unexpectedFlags = 0;
auto* ins =
MGuardFunctionFlags::New(alloc(), fun, expectedFlags, unexpectedFlags);
add(ins);
setOperand(funId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardNotClassConstructor(ObjOperandId funId) {
MDefinition* fun = getOperand(funId);
auto* ins =
MGuardFunctionKind::New(alloc(), fun, FunctionFlags::ClassConstructor,
/*bailOnEquality=*/true);
add(ins);
setOperand(funId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardArrayIsPacked(ObjOperandId arrayId) {
MDefinition* array = getOperand(arrayId);
auto* ins = MGuardArrayIsPacked::New(alloc(), array);
add(ins);
setOperand(arrayId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardArgumentsObjectFlags(ObjOperandId objId,
uint8_t flags) {
MDefinition* obj = getOperand(objId);
auto* ins = MGuardArgumentsObjectFlags::New(alloc(), obj, flags);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardNonDoubleType(ValOperandId inputId,
ValueType type) {
switch (type) {
case ValueType::String:
case ValueType::Symbol:
case ValueType::BigInt:
case ValueType::Int32:
case ValueType::Boolean:
return emitGuardTo(inputId, MIRTypeFromValueType(JSValueType(type)));
case ValueType::Undefined:
return emitGuardIsUndefined(inputId);
case ValueType::Null:
return emitGuardIsNull(inputId);
case ValueType::Double:
case ValueType::Magic:
case ValueType::PrivateGCThing:
case ValueType::Object:
#ifdef ENABLE_RECORD_TUPLE
case ValueType::ExtendedPrimitive:
#endif
break;
}
MOZ_CRASH("unexpected type");
}
bool WarpCacheIRTranspiler::emitGuardTo(ValOperandId inputId, MIRType type) {
MDefinition* def = getOperand(inputId);
if (def->type() == type) {
return true;
}
auto* ins = MUnbox::New(alloc(), def, type, MUnbox::Fallible);
add(ins);
setOperand(inputId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardToObject(ValOperandId inputId) {
return emitGuardTo(inputId, MIRType::Object);
}
bool WarpCacheIRTranspiler::emitGuardToString(ValOperandId inputId) {
return emitGuardTo(inputId, MIRType::String);
}
bool WarpCacheIRTranspiler::emitGuardToSymbol(ValOperandId inputId) {
return emitGuardTo(inputId, MIRType::Symbol);
}
bool WarpCacheIRTranspiler::emitGuardToBigInt(ValOperandId inputId) {
return emitGuardTo(inputId, MIRType::BigInt);
}
bool WarpCacheIRTranspiler::emitGuardToBoolean(ValOperandId inputId) {
return emitGuardTo(inputId, MIRType::Boolean);
}
bool WarpCacheIRTranspiler::emitGuardToInt32(ValOperandId inputId) {
return emitGuardTo(inputId, MIRType::Int32);
}
bool WarpCacheIRTranspiler::emitGuardBooleanToInt32(ValOperandId inputId,
Int32OperandId resultId) {
if (!emitGuardTo(inputId, MIRType::Boolean)) {
return false;
}
MDefinition* input = getOperand(inputId);
MOZ_ASSERT(input->type() == MIRType::Boolean);
auto* ins = MBooleanToInt32::New(alloc(), input);
add(ins);
return defineOperand(resultId, ins);
}
bool WarpCacheIRTranspiler::emitGuardIsNumber(ValOperandId inputId) {
// Prefer MToDouble because it gets further optimizations downstream.
MDefinition* def = getOperand(inputId);
if (def->type() == MIRType::Int32) {
auto* ins = MToDouble::New(alloc(), def);
add(ins);
setOperand(inputId, ins);
return true;
}
// MIRType::Double also implies int32 in Ion.
return emitGuardTo(inputId, MIRType::Double);
}
bool WarpCacheIRTranspiler::emitGuardIsNullOrUndefined(ValOperandId inputId) {
MDefinition* input = getOperand(inputId);
if (input->type() == MIRType::Null || input->type() == MIRType::Undefined) {
return true;
}
auto* ins = MGuardNullOrUndefined::New(alloc(), input);
add(ins);
setOperand(inputId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardIsNull(ValOperandId inputId) {
MDefinition* input = getOperand(inputId);
if (input->type() == MIRType::Null) {
return true;
}
auto* ins = MGuardValue::New(alloc(), input, NullValue());
add(ins);
setOperand(inputId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardIsUndefined(ValOperandId inputId) {
MDefinition* input = getOperand(inputId);
if (input->type() == MIRType::Undefined) {
return true;
}
auto* ins = MGuardValue::New(alloc(), input, UndefinedValue());
add(ins);
setOperand(inputId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardIsExtensible(ObjOperandId objId) {
MDefinition* obj = getOperand(objId);
auto* ins = MGuardIsExtensible::New(alloc(), obj);
add(ins);
setOperand(objId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardInt32IsNonNegative(
Int32OperandId indexId) {
MDefinition* index = getOperand(indexId);
auto* ins = MGuardInt32IsNonNegative::New(alloc(), index);
add(ins);
setOperand(indexId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardIndexIsNotDenseElement(
ObjOperandId objId, Int32OperandId indexId) {
MDefinition* obj = getOperand(objId);
MDefinition* index = getOperand(indexId);
auto* ins = MGuardIndexIsNotDenseElement::New(alloc(), obj, index);
add(ins);
setOperand(indexId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardIndexIsValidUpdateOrAdd(
ObjOperandId objId, Int32OperandId indexId) {
MDefinition* obj = getOperand(objId);
MDefinition* index = getOperand(indexId);
auto* ins = MGuardIndexIsValidUpdateOrAdd::New(alloc(), obj, index);
add(ins);
setOperand(indexId, ins);
return true;
}
bool WarpCacheIRTranspiler::emitCallAddOrUpdateSparseElementHelper(
ObjOperandId objId, Int32OperandId idId, ValOperandId rhsId, bool strict) {
MDefinition* obj = getOperand(objId);
MDefinition* id = getOperand(idId);
MDefinition* rhs = getOperand(rhsId);
auto* ins = MCallAddOrUpdateSparseElement::New(alloc(), obj, id, rhs, strict);
addEffectful(ins);
return resumeAfter(ins);
}
bool WarpCacheIRTranspiler::emitGuardTagNotEqual(ValueTagOperandId lhsId,
ValueTagOperandId rhsId) {
MDefinition* lhs = getOperand(lhsId);
MDefinition* rhs = getOperand(rhsId);
auto* ins = MGuardTagNotEqual::New(alloc(), lhs, rhs);
add(ins);
return true;
}
bool WarpCacheIRTranspiler::emitGuardToInt32Index(ValOperandId inputId,
Int32OperandId resultId) {
MDefinition* input = getOperand(inputId);
auto* ins =
MToNumberInt32::New(alloc(), input, IntConversionInputKind::NumbersOnly);
// ToPropertyKey(-0) is "0", so we can silently convert -0 to 0 here.
ins->setNeedsNegativeZeroCheck(false);
add(ins);
return defineOperand(resultId, ins);
}
bool WarpCacheIRTranspiler::emitTruncateDoubleToUInt32(
NumberOperandId inputId, Int32OperandId resultId) {
MDefinition* input = getOperand(inputId);
auto* ins = MTruncateToInt32::New(alloc(), input);
add(ins);
return defineOperand(resultId, ins);
}
bool WarpCacheIRTranspiler::emitGuardToInt32ModUint32(ValOperandId valId,
Int32OperandId resultId) {
MDefinition* input = getOperand(valId);
auto* ins = MTruncateToInt32::New(alloc(), input);
add(ins);
return defineOperand(resultId, ins);
}
bool WarpCacheIRTranspiler::emitGuardToUint8Clamped(ValOperandId valId,
Int32OperandId resultId) {
MDefinition* input = getOperand(valId);
auto* ins = MClampToUint8::New(alloc(), input);
add(ins);
return defineOperand(resultId, ins);
}
bool WarpCacheIRTranspiler::emitToString(OperandId inputId,
StringOperandId resultId) {
MDefinition* input = getOperand(inputId);
auto* ins =
MToString::New(alloc(), input, MToString::SideEffectHandling::Bailout);
add(ins);
return defineOperand(resultId, ins);