diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/jit/WarpCacheIRTranspiler.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit/WarpCacheIRTranspiler.cpp')
-rw-r--r-- | js/src/jit/WarpCacheIRTranspiler.cpp | 5809 |
1 files changed, 5809 insertions, 0 deletions
diff --git a/js/src/jit/WarpCacheIRTranspiler.cpp b/js/src/jit/WarpCacheIRTranspiler.cpp new file mode 100644 index 0000000000..7be5f78526 --- /dev/null +++ b/js/src/jit/WarpCacheIRTranspiler.cpp @@ -0,0 +1,5809 @@ +/* -*- 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); + + 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); + 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_->isMonomorphicInlined()) { + 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::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::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) { + MDefinition* input = getOperand(inputId); + + 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); +} + +bool WarpCacheIRTranspiler::emitInt32ToIntPtr(Int32OperandId inputId, + IntPtrOperandId resultId) { + MDefinition* input = getOperand(inputId); + auto* ins = MInt32ToIntPtr::New(alloc(), input); + add(ins); + return defineOperand(resultId, ins); +} + +bool WarpCacheIRTranspiler::emitGuardNumberToIntPtrIndex( + NumberOperandId inputId, bool supportOOB, IntPtrOperandId resultId) { + MDefinition* input = getOperand(inputId); + auto* ins = MGuardNumberToIntPtrIndex::New(alloc(), input, supportOOB); + add(ins); + return defineOperand(resultId, ins); +} + +bool WarpCacheIRTranspiler::emitCallInt32ToString(Int32OperandId inputId, + StringOperandId resultId) { + return emitToString(inputId, resultId); +} + +bool WarpCacheIRTranspiler::emitCallNumberToString(NumberOperandId inputId, + StringOperandId resultId) { + return emitToString(inputId, resultId); +} + +bool WarpCacheIRTranspiler::emitInt32ToStringWithBaseResult( + Int32OperandId inputId, Int32OperandId baseId) { + MDefinition* input = getOperand(inputId); + MDefinition* base = getOperand(baseId); + + auto* guardedBase = MGuardInt32Range::New(alloc(), base, 2, 36); + add(guardedBase); + + auto* ins = MInt32ToStringWithBase::New(alloc(), input, guardedBase); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitBooleanToString(BooleanOperandId inputId, + StringOperandId resultId) { + return emitToString(inputId, resultId); +} + +bool WarpCacheIRTranspiler::emitBooleanToNumber(BooleanOperandId inputId, + NumberOperandId resultId) { + MDefinition* input = getOperand(inputId); + + auto* ins = MToDouble::New(alloc(), input); + add(ins); + + return defineOperand(resultId, ins); +} + +bool WarpCacheIRTranspiler::emitLoadInt32Result(Int32OperandId valId) { + MDefinition* val = getOperand(valId); + MOZ_ASSERT(val->type() == MIRType::Int32); + pushResult(val); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadDoubleResult(NumberOperandId valId) { + MDefinition* val = getOperand(valId); + MOZ_ASSERT(val->type() == MIRType::Double); + pushResult(val); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadBigIntResult(BigIntOperandId valId) { + MDefinition* val = getOperand(valId); + MOZ_ASSERT(val->type() == MIRType::BigInt); + pushResult(val); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadObjectResult(ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + MOZ_ASSERT(obj->type() == MIRType::Object); + pushResult(obj); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadStringResult(StringOperandId strId) { + MDefinition* str = getOperand(strId); + MOZ_ASSERT(str->type() == MIRType::String); + pushResult(str); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadSymbolResult(SymbolOperandId symId) { + MDefinition* sym = getOperand(symId); + MOZ_ASSERT(sym->type() == MIRType::Symbol); + pushResult(sym); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadUndefinedResult() { + pushResult(constant(UndefinedValue())); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadBooleanResult(bool val) { + pushResult(constant(BooleanValue(val))); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadInt32Constant(uint32_t valOffset, + Int32OperandId resultId) { + int32_t val = int32StubField(valOffset); + auto* valConst = constant(Int32Value(val)); + return defineOperand(resultId, valConst); +} + +bool WarpCacheIRTranspiler::emitLoadDoubleConstant(uint32_t valOffset, + NumberOperandId resultId) { + double val = doubleStubField(valOffset); + auto* valConst = constant(DoubleValue(val)); + return defineOperand(resultId, valConst); +} + +bool WarpCacheIRTranspiler::emitLoadBooleanConstant(bool val, + BooleanOperandId resultId) { + auto* valConst = constant(BooleanValue(val)); + return defineOperand(resultId, valConst); +} + +bool WarpCacheIRTranspiler::emitLoadUndefined(ValOperandId resultId) { + auto* valConst = constant(UndefinedValue()); + return defineOperand(resultId, valConst); +} + +bool WarpCacheIRTranspiler::emitLoadConstantString(uint32_t strOffset, + StringOperandId resultId) { + JSString* val = stringStubField(strOffset); + auto* valConst = constant(StringValue(val)); + return defineOperand(resultId, valConst); +} + +bool WarpCacheIRTranspiler::emitLoadConstantStringResult(uint32_t strOffset) { + JSString* val = stringStubField(strOffset); + auto* valConst = constant(StringValue(val)); + pushResult(valConst); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadTypeOfObjectResult(ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + auto* typeOf = MTypeOf::New(alloc(), obj); + add(typeOf); + + auto* ins = MTypeOfName::New(alloc(), typeOf); + add(ins); + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadEnclosingEnvironment( + ObjOperandId objId, ObjOperandId resultId) { + MDefinition* env = getOperand(objId); + auto* ins = MEnclosingEnvironment::New(alloc(), env); + add(ins); + + return defineOperand(resultId, ins); +} + +bool WarpCacheIRTranspiler::emitLoadObject(ObjOperandId resultId, + uint32_t objOffset) { + MInstruction* ins = objectStubField(objOffset); + + return defineOperand(resultId, ins); +} + +bool WarpCacheIRTranspiler::emitLoadProtoObject(ObjOperandId resultId, + uint32_t objOffset, + ObjOperandId receiverObjId) { + MInstruction* ins = objectStubField(objOffset); + if (ins->isConstant()) { + MDefinition* receiverObj = getOperand(receiverObjId); + + ins = MConstantProto::New(alloc(), ins, receiverObj->skipObjectGuards()); + add(ins); + } + return defineOperand(resultId, ins); +} + +bool WarpCacheIRTranspiler::emitLoadProto(ObjOperandId objId, + ObjOperandId resultId) { + MDefinition* obj = getOperand(objId); + + auto* ins = MObjectStaticProto::New(alloc(), obj); + add(ins); + + return defineOperand(resultId, ins); +} + +bool WarpCacheIRTranspiler::emitLoadInstanceOfObjectResult( + ValOperandId lhsId, ObjOperandId protoId) { + MDefinition* lhs = getOperand(lhsId); + MDefinition* proto = getOperand(protoId); + + auto* instanceOf = MInstanceOf::New(alloc(), lhs, proto); + addEffectful(instanceOf); + + pushResult(instanceOf); + return resumeAfter(instanceOf); +} + +bool WarpCacheIRTranspiler::emitLoadValueTag(ValOperandId valId, + ValueTagOperandId resultId) { + MDefinition* val = getOperand(valId); + + auto* ins = MLoadValueTag::New(alloc(), val); + add(ins); + + return defineOperand(resultId, ins); +} + +bool WarpCacheIRTranspiler::emitLoadDynamicSlotResult(ObjOperandId objId, + uint32_t offsetOffset) { + int32_t offset = int32StubField(offsetOffset); + + MDefinition* obj = getOperand(objId); + size_t slotIndex = NativeObject::getDynamicSlotIndexFromOffset(offset); + + auto* slots = MSlots::New(alloc(), obj); + add(slots); + + auto* load = MLoadDynamicSlot::New(alloc(), slots, slotIndex); + add(load); + + pushResult(load); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadFixedSlot(ValOperandId resultId, + ObjOperandId objId, + uint32_t offsetOffset) { + MDefinition* obj = getOperand(objId); + + size_t offset = int32StubField(offsetOffset); + uint32_t slotIndex = NativeObject::getFixedSlotIndexFromOffset(offset); + + auto* load = MLoadFixedSlot::New(alloc(), obj, slotIndex); + add(load); + + return defineOperand(resultId, load); +} + +bool WarpCacheIRTranspiler::emitLoadFixedSlotResult(ObjOperandId objId, + uint32_t offsetOffset) { + int32_t offset = int32StubField(offsetOffset); + + MDefinition* obj = getOperand(objId); + uint32_t slotIndex = NativeObject::getFixedSlotIndexFromOffset(offset); + + auto* load = MLoadFixedSlot::New(alloc(), obj, slotIndex); + add(load); + + pushResult(load); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadFixedSlotTypedResult(ObjOperandId objId, + uint32_t offsetOffset, + ValueType type) { + int32_t offset = int32StubField(offsetOffset); + + MDefinition* obj = getOperand(objId); + uint32_t slotIndex = NativeObject::getFixedSlotIndexFromOffset(offset); + + auto* load = MLoadFixedSlot::New(alloc(), obj, slotIndex); + load->setResultType(MIRTypeFromValueType(JSValueType(type))); + add(load); + + pushResult(load); + return true; +} + +bool WarpCacheIRTranspiler::emitGuardIsNotUninitializedLexical( + ValOperandId valId) { + MDefinition* val = getOperand(valId); + + auto* lexicalCheck = MLexicalCheck::New(alloc(), val); + add(lexicalCheck); + + if (snapshot().bailoutInfo().failedLexicalCheck()) { + lexicalCheck->setNotMovable(); + } + + setOperand(valId, lexicalCheck); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadInt32ArrayLengthResult(ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* elements = MElements::New(alloc(), obj); + add(elements); + + auto* length = MArrayLength::New(alloc(), elements); + add(length); + + pushResult(length); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadInt32ArrayLength(ObjOperandId objId, + Int32OperandId resultId) { + MDefinition* obj = getOperand(objId); + + auto* elements = MElements::New(alloc(), obj); + add(elements); + + auto* length = MArrayLength::New(alloc(), elements); + add(length); + + return defineOperand(resultId, length); +} + +bool WarpCacheIRTranspiler::emitLoadArgumentsObjectArgResult( + ObjOperandId objId, Int32OperandId indexId) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + + auto* load = MLoadArgumentsObjectArg::New(alloc(), obj, index); + add(load); + + pushResult(load); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadArgumentsObjectArgHoleResult( + ObjOperandId objId, Int32OperandId indexId) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + + auto* load = MLoadArgumentsObjectArgHole::New(alloc(), obj, index); + add(load); + + pushResult(load); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadArgumentsObjectArgExistsResult( + ObjOperandId objId, Int32OperandId indexId) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + + auto* ins = MInArgumentsObjectArg::New(alloc(), obj, index); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadArgumentsObjectLengthResult( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* length = MArgumentsObjectLength::New(alloc(), obj); + add(length); + + pushResult(length); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadArgumentsObjectLength( + ObjOperandId objId, Int32OperandId resultId) { + MDefinition* obj = getOperand(objId); + + auto* length = MArgumentsObjectLength::New(alloc(), obj); + add(length); + + return defineOperand(resultId, length); +} + +bool WarpCacheIRTranspiler::emitLoadBoundFunctionNumArgs( + ObjOperandId objId, Int32OperandId resultId) { + MDefinition* obj = getOperand(objId); + + auto* numArgs = MBoundFunctionNumArgs::New(alloc(), obj); + add(numArgs); + + return defineOperand(resultId, numArgs); +} + +bool WarpCacheIRTranspiler::emitLoadBoundFunctionTarget(ObjOperandId objId, + ObjOperandId resultId) { + MDefinition* obj = getOperand(objId); + + auto* target = MLoadFixedSlotAndUnbox::New( + alloc(), obj, BoundFunctionObject::targetSlot(), MUnbox::Mode::Infallible, + MIRType::Object); + add(target); + + return defineOperand(resultId, target); +} + +bool WarpCacheIRTranspiler::emitGuardBoundFunctionIsConstructor( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* guard = MGuardBoundFunctionIsConstructor::New(alloc(), obj); + add(guard); + + setOperand(objId, guard); + return true; +} + +bool WarpCacheIRTranspiler::emitGuardObjectIdentity(ObjOperandId obj1Id, + ObjOperandId obj2Id) { + MDefinition* obj1 = getOperand(obj1Id); + MDefinition* obj2 = getOperand(obj2Id); + + auto* guard = MGuardObjectIdentity::New(alloc(), obj1, obj2, + /* bailOnEquality = */ false); + add(guard); + return true; +} + +bool WarpCacheIRTranspiler::emitArrayFromArgumentsObjectResult( + ObjOperandId objId, uint32_t shapeOffset) { + MDefinition* obj = getOperand(objId); + Shape* shape = shapeStubField(shapeOffset); + MOZ_ASSERT(shape); + + auto* array = MArrayFromArgumentsObject::New(alloc(), obj, shape); + addEffectful(array); + + pushResult(array); + return resumeAfter(array); +} + +bool WarpCacheIRTranspiler::emitLoadFunctionLengthResult(ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* length = MFunctionLength::New(alloc(), obj); + add(length); + + pushResult(length); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadFunctionNameResult(ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* name = MFunctionName::New(alloc(), obj); + add(name); + + pushResult(name); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadArrayBufferByteLengthInt32Result( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* length = MArrayBufferByteLength::New(alloc(), obj); + add(length); + + auto* lengthInt32 = MNonNegativeIntPtrToInt32::New(alloc(), length); + add(lengthInt32); + + pushResult(lengthInt32); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadArrayBufferByteLengthDoubleResult( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* length = MArrayBufferByteLength::New(alloc(), obj); + add(length); + + auto* lengthDouble = MIntPtrToDouble::New(alloc(), length); + add(lengthDouble); + + pushResult(lengthDouble); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadArrayBufferViewLengthInt32Result( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + // Use a separate instruction for converting the length to Int32, so that we + // can fold the MArrayBufferViewLength instruction with length instructions + // added for bounds checks. + + auto* length = MArrayBufferViewLength::New(alloc(), obj); + add(length); + + auto* lengthInt32 = MNonNegativeIntPtrToInt32::New(alloc(), length); + add(lengthInt32); + + pushResult(lengthInt32); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadArrayBufferViewLengthDoubleResult( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* length = MArrayBufferViewLength::New(alloc(), obj); + add(length); + + auto* lengthDouble = MIntPtrToDouble::New(alloc(), length); + add(lengthDouble); + + pushResult(lengthDouble); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadStringLengthResult(StringOperandId strId) { + MDefinition* str = getOperand(strId); + + auto* length = MStringLength::New(alloc(), str); + add(length); + + pushResult(length); + return true; +} + +MInstruction* WarpCacheIRTranspiler::addBoundsCheck(MDefinition* index, + MDefinition* length) { + MInstruction* check = MBoundsCheck::New(alloc(), index, length); + add(check); + + if (snapshot().bailoutInfo().failedBoundsCheck()) { + check->setNotMovable(); + } + + if (JitOptions.spectreIndexMasking) { + // Use a separate MIR instruction for the index masking. Doing this as + // part of MBoundsCheck would be unsound because bounds checks can be + // optimized or eliminated completely. Consider this: + // + // for (var i = 0; i < x; i++) + // res = arr[i]; + // + // If we can prove |x < arr.length|, we are able to eliminate the bounds + // check, but we should not get rid of the index masking because the + // |i < x| branch could still be mispredicted. + // + // Using a separate instruction lets us eliminate the bounds check + // without affecting the index masking. + check = MSpectreMaskIndex::New(alloc(), check, length); + add(check); + } + + return check; +} + +bool WarpCacheIRTranspiler::emitLoadDenseElementResult(ObjOperandId objId, + Int32OperandId indexId) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + + auto* elements = MElements::New(alloc(), obj); + add(elements); + + auto* length = MInitializedLength::New(alloc(), elements); + add(length); + + index = addBoundsCheck(index, length); + + auto* load = MLoadElement::New(alloc(), elements, index); + add(load); + + pushResult(load); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadDenseElementHoleResult( + ObjOperandId objId, Int32OperandId indexId) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + + auto* elements = MElements::New(alloc(), obj); + add(elements); + + auto* length = MInitializedLength::New(alloc(), elements); + add(length); + + auto* load = MLoadElementHole::New(alloc(), elements, index, length); + add(load); + + pushResult(load); + return true; +} + +bool WarpCacheIRTranspiler::emitCallGetSparseElementResult( + ObjOperandId objId, Int32OperandId indexId) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + + auto* call = MCallGetSparseElement::New(alloc(), obj, index); + addEffectful(call); + + pushResult(call); + return resumeAfter(call); +} + +bool WarpCacheIRTranspiler::emitCallNativeGetElementResult( + ObjOperandId objId, Int32OperandId indexId) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + + auto* call = MCallNativeGetElement::New(alloc(), obj, index); + addEffectful(call); + + pushResult(call); + return resumeAfter(call); +} + +bool WarpCacheIRTranspiler::emitCallNativeGetElementSuperResult( + ObjOperandId objId, Int32OperandId indexId, ValOperandId receiverId) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + MDefinition* receiver = getOperand(receiverId); + + auto* call = MCallNativeGetElementSuper::New(alloc(), obj, index, receiver); + addEffectful(call); + + pushResult(call); + return resumeAfter(call); +} + +bool WarpCacheIRTranspiler::emitLoadDenseElementExistsResult( + ObjOperandId objId, Int32OperandId indexId) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + + // Get the elements vector. + auto* elements = MElements::New(alloc(), obj); + add(elements); + + auto* length = MInitializedLength::New(alloc(), elements); + add(length); + + // Check if id < initLength. + index = addBoundsCheck(index, length); + + // And check elem[id] is not a hole. + auto* guard = MGuardElementNotHole::New(alloc(), elements, index); + add(guard); + + pushResult(constant(BooleanValue(true))); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadDenseElementHoleExistsResult( + ObjOperandId objId, Int32OperandId indexId) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + + // Get the elements vector. + auto* elements = MElements::New(alloc(), obj); + add(elements); + + auto* length = MInitializedLength::New(alloc(), elements); + add(length); + + // Check if id < initLength and elem[id] not a hole. + auto* ins = MInArray::New(alloc(), elements, index, length, obj); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitCallObjectHasSparseElementResult( + ObjOperandId objId, Int32OperandId indexId) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + + auto* ins = MCallObjectHasSparseElement::New(alloc(), obj, index); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadTypedArrayElementExistsResult( + ObjOperandId objId, IntPtrOperandId indexId) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + + auto* length = MArrayBufferViewLength::New(alloc(), obj); + add(length); + + // Unsigned comparison to catch negative indices. + auto* ins = MCompare::New(alloc(), index, length, JSOp::Lt, + MCompare::Compare_UIntPtr); + add(ins); + + pushResult(ins); + return true; +} + +static MIRType MIRTypeForArrayBufferViewRead(Scalar::Type arrayType, + bool forceDoubleForUint32) { + switch (arrayType) { + case Scalar::Int8: + case Scalar::Uint8: + case Scalar::Uint8Clamped: + case Scalar::Int16: + case Scalar::Uint16: + case Scalar::Int32: + return MIRType::Int32; + case Scalar::Uint32: + return forceDoubleForUint32 ? MIRType::Double : MIRType::Int32; + case Scalar::Float32: + return MIRType::Float32; + case Scalar::Float64: + return MIRType::Double; + case Scalar::BigInt64: + case Scalar::BigUint64: + return MIRType::BigInt; + default: + break; + } + MOZ_CRASH("Unknown typed array type"); +} + +bool WarpCacheIRTranspiler::emitLoadTypedArrayElementResult( + ObjOperandId objId, IntPtrOperandId indexId, Scalar::Type elementType, + bool handleOOB, bool forceDoubleForUint32) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + + if (handleOOB) { + auto* load = MLoadTypedArrayElementHole::New( + alloc(), obj, index, elementType, forceDoubleForUint32); + add(load); + + pushResult(load); + return true; + } + + auto* length = MArrayBufferViewLength::New(alloc(), obj); + add(length); + + index = addBoundsCheck(index, length); + + auto* elements = MArrayBufferViewElements::New(alloc(), obj); + add(elements); + + auto* load = MLoadUnboxedScalar::New(alloc(), elements, index, elementType); + load->setResultType( + MIRTypeForArrayBufferViewRead(elementType, forceDoubleForUint32)); + add(load); + + pushResult(load); + return true; +} + +bool WarpCacheIRTranspiler::emitLinearizeForCharAccess( + StringOperandId strId, Int32OperandId indexId, StringOperandId resultId) { + MDefinition* str = getOperand(strId); + MDefinition* index = getOperand(indexId); + + auto* ins = MLinearizeForCharAccess::New(alloc(), str, index); + add(ins); + + return defineOperand(resultId, ins); +} + +bool WarpCacheIRTranspiler::emitLoadStringCharResult(StringOperandId strId, + Int32OperandId indexId, + bool handleOOB) { + MDefinition* str = getOperand(strId); + MDefinition* index = getOperand(indexId); + + if (handleOOB) { + auto* ins = MCharAtMaybeOutOfBounds::New(alloc(), str, index); + add(ins); + + pushResult(ins); + return true; + } + + auto* length = MStringLength::New(alloc(), str); + add(length); + + index = addBoundsCheck(index, length); + + auto* charCode = MCharCodeAt::New(alloc(), str, index); + add(charCode); + + auto* fromCharCode = MFromCharCode::New(alloc(), charCode); + add(fromCharCode); + + pushResult(fromCharCode); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadStringCharCodeResult(StringOperandId strId, + Int32OperandId indexId, + bool handleOOB) { + MDefinition* str = getOperand(strId); + MDefinition* index = getOperand(indexId); + + if (handleOOB) { + auto* ins = MCharCodeAtMaybeOutOfBounds::New(alloc(), str, index); + add(ins); + + pushResult(ins); + return true; + } + + auto* length = MStringLength::New(alloc(), str); + add(length); + + index = addBoundsCheck(index, length); + + auto* charCode = MCharCodeAt::New(alloc(), str, index); + add(charCode); + + pushResult(charCode); + return true; +} + +bool WarpCacheIRTranspiler::emitNewStringObjectResult( + uint32_t templateObjectOffset, StringOperandId strId) { + JSObject* templateObj = tenuredObjectStubField(templateObjectOffset); + MDefinition* string = getOperand(strId); + + auto* obj = MNewStringObject::New(alloc(), string, templateObj); + addEffectful(obj); + + pushResult(obj); + return resumeAfter(obj); +} + +bool WarpCacheIRTranspiler::emitStringFromCharCodeResult( + Int32OperandId codeId) { + MDefinition* code = getOperand(codeId); + + auto* fromCharCode = MFromCharCode::New(alloc(), code); + add(fromCharCode); + + pushResult(fromCharCode); + return true; +} + +bool WarpCacheIRTranspiler::emitStringFromCodePointResult( + Int32OperandId codeId) { + MDefinition* code = getOperand(codeId); + + auto* fromCodePoint = MFromCodePoint::New(alloc(), code); + add(fromCodePoint); + + pushResult(fromCodePoint); + return true; +} + +bool WarpCacheIRTranspiler::emitStringIndexOfResult( + StringOperandId strId, StringOperandId searchStrId) { + MDefinition* str = getOperand(strId); + MDefinition* searchStr = getOperand(searchStrId); + + auto* indexOf = MStringIndexOf::New(alloc(), str, searchStr); + add(indexOf); + + pushResult(indexOf); + return true; +} + +bool WarpCacheIRTranspiler::emitStringStartsWithResult( + StringOperandId strId, StringOperandId searchStrId) { + MDefinition* str = getOperand(strId); + MDefinition* searchStr = getOperand(searchStrId); + + auto* startsWith = MStringStartsWith::New(alloc(), str, searchStr); + add(startsWith); + + pushResult(startsWith); + return true; +} + +bool WarpCacheIRTranspiler::emitStringEndsWithResult( + StringOperandId strId, StringOperandId searchStrId) { + MDefinition* str = getOperand(strId); + MDefinition* searchStr = getOperand(searchStrId); + + auto* endsWith = MStringEndsWith::New(alloc(), str, searchStr); + add(endsWith); + + pushResult(endsWith); + return true; +} + +bool WarpCacheIRTranspiler::emitStringToLowerCaseResult(StringOperandId strId) { + MDefinition* str = getOperand(strId); + + auto* convert = + MStringConvertCase::New(alloc(), str, MStringConvertCase::LowerCase); + add(convert); + + pushResult(convert); + return true; +} + +bool WarpCacheIRTranspiler::emitStringToUpperCaseResult(StringOperandId strId) { + MDefinition* str = getOperand(strId); + + auto* convert = + MStringConvertCase::New(alloc(), str, MStringConvertCase::UpperCase); + add(convert); + + pushResult(convert); + return true; +} + +bool WarpCacheIRTranspiler::emitStoreDynamicSlot(ObjOperandId objId, + uint32_t offsetOffset, + ValOperandId rhsId) { + int32_t offset = int32StubField(offsetOffset); + + MDefinition* obj = getOperand(objId); + size_t slotIndex = NativeObject::getDynamicSlotIndexFromOffset(offset); + MDefinition* rhs = getOperand(rhsId); + + auto* barrier = MPostWriteBarrier::New(alloc(), obj, rhs); + add(barrier); + + auto* slots = MSlots::New(alloc(), obj); + add(slots); + + auto* store = MStoreDynamicSlot::NewBarriered(alloc(), slots, slotIndex, rhs); + addEffectful(store); + return resumeAfter(store); +} + +bool WarpCacheIRTranspiler::emitStoreFixedSlot(ObjOperandId objId, + uint32_t offsetOffset, + ValOperandId rhsId) { + int32_t offset = int32StubField(offsetOffset); + + MDefinition* obj = getOperand(objId); + size_t slotIndex = NativeObject::getFixedSlotIndexFromOffset(offset); + MDefinition* rhs = getOperand(rhsId); + + auto* barrier = MPostWriteBarrier::New(alloc(), obj, rhs); + add(barrier); + + auto* store = MStoreFixedSlot::NewBarriered(alloc(), obj, slotIndex, rhs); + addEffectful(store); + return resumeAfter(store); +} + +bool WarpCacheIRTranspiler::emitStoreFixedSlotUndefinedResult( + ObjOperandId objId, uint32_t offsetOffset, ValOperandId rhsId) { + int32_t offset = int32StubField(offsetOffset); + + MDefinition* obj = getOperand(objId); + size_t slotIndex = NativeObject::getFixedSlotIndexFromOffset(offset); + MDefinition* rhs = getOperand(rhsId); + + auto* barrier = MPostWriteBarrier::New(alloc(), obj, rhs); + add(barrier); + + auto* store = MStoreFixedSlot::NewBarriered(alloc(), obj, slotIndex, rhs); + addEffectful(store); + + auto* undef = constant(UndefinedValue()); + pushResult(undef); + + return resumeAfter(store); +} + +bool WarpCacheIRTranspiler::emitAddAndStoreSlotShared( + MAddAndStoreSlot::Kind kind, ObjOperandId objId, uint32_t offsetOffset, + ValOperandId rhsId, uint32_t newShapeOffset) { + int32_t offset = int32StubField(offsetOffset); + Shape* shape = shapeStubField(newShapeOffset); + + MDefinition* obj = getOperand(objId); + MDefinition* rhs = getOperand(rhsId); + + auto* barrier = MPostWriteBarrier::New(alloc(), obj, rhs); + add(barrier); + + auto* addAndStore = + MAddAndStoreSlot::New(alloc(), obj, rhs, kind, offset, shape); + addEffectful(addAndStore); + + return resumeAfter(addAndStore); +} + +bool WarpCacheIRTranspiler::emitAddAndStoreFixedSlot(ObjOperandId objId, + uint32_t offsetOffset, + ValOperandId rhsId, + uint32_t newShapeOffset) { + return emitAddAndStoreSlotShared(MAddAndStoreSlot::Kind::FixedSlot, objId, + offsetOffset, rhsId, newShapeOffset); +} + +bool WarpCacheIRTranspiler::emitAddAndStoreDynamicSlot( + ObjOperandId objId, uint32_t offsetOffset, ValOperandId rhsId, + uint32_t newShapeOffset) { + return emitAddAndStoreSlotShared(MAddAndStoreSlot::Kind::DynamicSlot, objId, + offsetOffset, rhsId, newShapeOffset); +} + +bool WarpCacheIRTranspiler::emitAllocateAndStoreDynamicSlot( + ObjOperandId objId, uint32_t offsetOffset, ValOperandId rhsId, + uint32_t newShapeOffset, uint32_t numNewSlotsOffset) { + int32_t offset = int32StubField(offsetOffset); + Shape* shape = shapeStubField(newShapeOffset); + uint32_t numNewSlots = uint32StubField(numNewSlotsOffset); + + MDefinition* obj = getOperand(objId); + MDefinition* rhs = getOperand(rhsId); + + auto* barrier = MPostWriteBarrier::New(alloc(), obj, rhs); + add(barrier); + + auto* allocateAndStore = + MAllocateAndStoreSlot::New(alloc(), obj, rhs, offset, shape, numNewSlots); + addEffectful(allocateAndStore); + + return resumeAfter(allocateAndStore); +} + +bool WarpCacheIRTranspiler::emitAddSlotAndCallAddPropHook( + ObjOperandId objId, ValOperandId rhsId, uint32_t newShapeOffset) { + Shape* shape = shapeStubField(newShapeOffset); + MDefinition* obj = getOperand(objId); + MDefinition* rhs = getOperand(rhsId); + + auto* addProp = MAddSlotAndCallAddPropHook::New(alloc(), obj, rhs, shape); + addEffectful(addProp); + + return resumeAfter(addProp); +} + +bool WarpCacheIRTranspiler::emitStoreDenseElement(ObjOperandId objId, + Int32OperandId indexId, + ValOperandId rhsId) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + MDefinition* rhs = getOperand(rhsId); + + auto* elements = MElements::New(alloc(), obj); + add(elements); + + auto* length = MInitializedLength::New(alloc(), elements); + add(length); + + index = addBoundsCheck(index, length); + + auto* barrier = MPostWriteElementBarrier::New(alloc(), obj, rhs, index); + add(barrier); + + bool needsHoleCheck = true; + auto* store = MStoreElement::NewBarriered(alloc(), elements, index, rhs, + needsHoleCheck); + addEffectful(store); + return resumeAfter(store); +} + +bool WarpCacheIRTranspiler::emitStoreDenseElementHole(ObjOperandId objId, + Int32OperandId indexId, + ValOperandId rhsId, + bool handleAdd) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + MDefinition* rhs = getOperand(rhsId); + + auto* elements = MElements::New(alloc(), obj); + add(elements); + + MInstruction* store; + if (handleAdd) { + // TODO(post-Warp): Consider changing MStoreElementHole to match IC code. + store = MStoreElementHole::New(alloc(), obj, elements, index, rhs); + } else { + auto* length = MInitializedLength::New(alloc(), elements); + add(length); + + index = addBoundsCheck(index, length); + + auto* barrier = MPostWriteElementBarrier::New(alloc(), obj, rhs, index); + add(barrier); + + bool needsHoleCheck = false; + store = MStoreElement::NewBarriered(alloc(), elements, index, rhs, + needsHoleCheck); + } + addEffectful(store); + + return resumeAfter(store); +} + +bool WarpCacheIRTranspiler::emitStoreTypedArrayElement(ObjOperandId objId, + Scalar::Type elementType, + IntPtrOperandId indexId, + uint32_t rhsId, + bool handleOOB) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + MDefinition* rhs = getOperand(ValOperandId(rhsId)); + + auto* length = MArrayBufferViewLength::New(alloc(), obj); + add(length); + + if (!handleOOB) { + // MStoreTypedArrayElementHole does the bounds checking. + index = addBoundsCheck(index, length); + } + + auto* elements = MArrayBufferViewElements::New(alloc(), obj); + add(elements); + + MInstruction* store; + if (handleOOB) { + store = MStoreTypedArrayElementHole::New(alloc(), elements, length, index, + rhs, elementType); + } else { + store = + MStoreUnboxedScalar::New(alloc(), elements, index, rhs, elementType); + } + addEffectful(store); + return resumeAfter(store); +} + +void WarpCacheIRTranspiler::addDataViewData(MDefinition* obj, Scalar::Type type, + MDefinition** offset, + MInstruction** elements) { + MInstruction* length = MArrayBufferViewLength::New(alloc(), obj); + add(length); + + // Adjust the length to account for accesses near the end of the dataview. + if (size_t byteSize = Scalar::byteSize(type); byteSize > 1) { + // To ensure |0 <= offset && offset + byteSize <= length|, first adjust the + // length by subtracting |byteSize - 1| (bailing out if that becomes + // negative). + length = MAdjustDataViewLength::New(alloc(), length, byteSize); + add(length); + } + + *offset = addBoundsCheck(*offset, length); + + *elements = MArrayBufferViewElements::New(alloc(), obj); + add(*elements); +} + +bool WarpCacheIRTranspiler::emitLoadDataViewValueResult( + ObjOperandId objId, IntPtrOperandId offsetId, + BooleanOperandId littleEndianId, Scalar::Type elementType, + bool forceDoubleForUint32) { + MDefinition* obj = getOperand(objId); + MDefinition* offset = getOperand(offsetId); + MDefinition* littleEndian = getOperand(littleEndianId); + + // Add bounds check and get the DataViewObject's elements. + MInstruction* elements; + addDataViewData(obj, elementType, &offset, &elements); + + // Load the element. + MInstruction* load; + if (Scalar::byteSize(elementType) == 1) { + load = MLoadUnboxedScalar::New(alloc(), elements, offset, elementType); + } else { + load = MLoadDataViewElement::New(alloc(), elements, offset, littleEndian, + elementType); + } + add(load); + + MIRType knownType = + MIRTypeForArrayBufferViewRead(elementType, forceDoubleForUint32); + load->setResultType(knownType); + + pushResult(load); + return true; +} + +bool WarpCacheIRTranspiler::emitStoreDataViewValueResult( + ObjOperandId objId, IntPtrOperandId offsetId, uint32_t valueId, + BooleanOperandId littleEndianId, Scalar::Type elementType) { + MDefinition* obj = getOperand(objId); + MDefinition* offset = getOperand(offsetId); + MDefinition* value = getOperand(ValOperandId(valueId)); + MDefinition* littleEndian = getOperand(littleEndianId); + + // Add bounds check and get the DataViewObject's elements. + MInstruction* elements; + addDataViewData(obj, elementType, &offset, &elements); + + // Store the element. + MInstruction* store; + if (Scalar::byteSize(elementType) == 1) { + store = + MStoreUnboxedScalar::New(alloc(), elements, offset, value, elementType); + } else { + store = MStoreDataViewElement::New(alloc(), elements, offset, value, + littleEndian, elementType); + } + addEffectful(store); + + pushResult(constant(UndefinedValue())); + + return resumeAfter(store); +} + +bool WarpCacheIRTranspiler::emitInt32IncResult(Int32OperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* constOne = MConstant::New(alloc(), Int32Value(1)); + add(constOne); + + auto* ins = MAdd::New(alloc(), input, constOne, MIRType::Int32); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitDoubleIncResult(NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* constOne = MConstant::New(alloc(), DoubleValue(1.0)); + add(constOne); + + auto* ins = MAdd::New(alloc(), input, constOne, MIRType::Double); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitInt32DecResult(Int32OperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* constOne = MConstant::New(alloc(), Int32Value(1)); + add(constOne); + + auto* ins = MSub::New(alloc(), input, constOne, MIRType::Int32); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitDoubleDecResult(NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* constOne = MConstant::New(alloc(), DoubleValue(1.0)); + add(constOne); + + auto* ins = MSub::New(alloc(), input, constOne, MIRType::Double); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitInt32NegationResult(Int32OperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* constNegOne = MConstant::New(alloc(), Int32Value(-1)); + add(constNegOne); + + auto* ins = MMul::New(alloc(), input, constNegOne, MIRType::Int32); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitDoubleNegationResult(NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* constNegOne = MConstant::New(alloc(), DoubleValue(-1.0)); + add(constNegOne); + + auto* ins = MMul::New(alloc(), input, constNegOne, MIRType::Double); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitInt32NotResult(Int32OperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* ins = MBitNot::New(alloc(), input); + add(ins); + + pushResult(ins); + return true; +} + +template <typename T> +bool WarpCacheIRTranspiler::emitDoubleBinaryArithResult(NumberOperandId lhsId, + NumberOperandId rhsId) { + MDefinition* lhs = getOperand(lhsId); + MDefinition* rhs = getOperand(rhsId); + + auto* ins = T::New(alloc(), lhs, rhs, MIRType::Double); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitDoubleAddResult(NumberOperandId lhsId, + NumberOperandId rhsId) { + return emitDoubleBinaryArithResult<MAdd>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitDoubleSubResult(NumberOperandId lhsId, + NumberOperandId rhsId) { + return emitDoubleBinaryArithResult<MSub>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitDoubleMulResult(NumberOperandId lhsId, + NumberOperandId rhsId) { + return emitDoubleBinaryArithResult<MMul>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitDoubleDivResult(NumberOperandId lhsId, + NumberOperandId rhsId) { + return emitDoubleBinaryArithResult<MDiv>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitDoubleModResult(NumberOperandId lhsId, + NumberOperandId rhsId) { + return emitDoubleBinaryArithResult<MMod>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitDoublePowResult(NumberOperandId lhsId, + NumberOperandId rhsId) { + return emitDoubleBinaryArithResult<MPow>(lhsId, rhsId); +} + +template <typename T> +bool WarpCacheIRTranspiler::emitInt32BinaryArithResult(Int32OperandId lhsId, + Int32OperandId rhsId) { + MDefinition* lhs = getOperand(lhsId); + MDefinition* rhs = getOperand(rhsId); + + auto* ins = T::New(alloc(), lhs, rhs, MIRType::Int32); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitInt32AddResult(Int32OperandId lhsId, + Int32OperandId rhsId) { + return emitInt32BinaryArithResult<MAdd>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitInt32SubResult(Int32OperandId lhsId, + Int32OperandId rhsId) { + return emitInt32BinaryArithResult<MSub>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitInt32MulResult(Int32OperandId lhsId, + Int32OperandId rhsId) { + return emitInt32BinaryArithResult<MMul>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitInt32DivResult(Int32OperandId lhsId, + Int32OperandId rhsId) { + return emitInt32BinaryArithResult<MDiv>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitInt32ModResult(Int32OperandId lhsId, + Int32OperandId rhsId) { + return emitInt32BinaryArithResult<MMod>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitInt32PowResult(Int32OperandId lhsId, + Int32OperandId rhsId) { + return emitInt32BinaryArithResult<MPow>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitInt32BitOrResult(Int32OperandId lhsId, + Int32OperandId rhsId) { + return emitInt32BinaryArithResult<MBitOr>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitInt32BitXorResult(Int32OperandId lhsId, + Int32OperandId rhsId) { + return emitInt32BinaryArithResult<MBitXor>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitInt32BitAndResult(Int32OperandId lhsId, + Int32OperandId rhsId) { + return emitInt32BinaryArithResult<MBitAnd>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitInt32LeftShiftResult(Int32OperandId lhsId, + Int32OperandId rhsId) { + return emitInt32BinaryArithResult<MLsh>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitInt32RightShiftResult(Int32OperandId lhsId, + Int32OperandId rhsId) { + return emitInt32BinaryArithResult<MRsh>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitInt32URightShiftResult(Int32OperandId lhsId, + Int32OperandId rhsId, + bool forceDouble) { + MDefinition* lhs = getOperand(lhsId); + MDefinition* rhs = getOperand(rhsId); + + MIRType specialization = forceDouble ? MIRType::Double : MIRType::Int32; + auto* ins = MUrsh::New(alloc(), lhs, rhs, specialization); + add(ins); + + pushResult(ins); + return true; +} + +template <typename T> +bool WarpCacheIRTranspiler::emitBigIntBinaryArithResult(BigIntOperandId lhsId, + BigIntOperandId rhsId) { + MDefinition* lhs = getOperand(lhsId); + MDefinition* rhs = getOperand(rhsId); + + auto* ins = T::New(alloc(), lhs, rhs); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitBigIntAddResult(BigIntOperandId lhsId, + BigIntOperandId rhsId) { + return emitBigIntBinaryArithResult<MBigIntAdd>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitBigIntSubResult(BigIntOperandId lhsId, + BigIntOperandId rhsId) { + return emitBigIntBinaryArithResult<MBigIntSub>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitBigIntMulResult(BigIntOperandId lhsId, + BigIntOperandId rhsId) { + return emitBigIntBinaryArithResult<MBigIntMul>(lhsId, rhsId); +} + +template <typename T> +bool WarpCacheIRTranspiler::emitBigIntBinaryArithEffectfulResult( + BigIntOperandId lhsId, BigIntOperandId rhsId) { + MDefinition* lhs = getOperand(lhsId); + MDefinition* rhs = getOperand(rhsId); + + auto* ins = T::New(alloc(), lhs, rhs); + + if (ins->isEffectful()) { + addEffectful(ins); + + pushResult(ins); + return resumeAfter(ins); + } + + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitBigIntDivResult(BigIntOperandId lhsId, + BigIntOperandId rhsId) { + return emitBigIntBinaryArithEffectfulResult<MBigIntDiv>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitBigIntModResult(BigIntOperandId lhsId, + BigIntOperandId rhsId) { + return emitBigIntBinaryArithEffectfulResult<MBigIntMod>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitBigIntPowResult(BigIntOperandId lhsId, + BigIntOperandId rhsId) { + return emitBigIntBinaryArithEffectfulResult<MBigIntPow>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitBigIntBitAndResult(BigIntOperandId lhsId, + BigIntOperandId rhsId) { + return emitBigIntBinaryArithResult<MBigIntBitAnd>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitBigIntBitOrResult(BigIntOperandId lhsId, + BigIntOperandId rhsId) { + return emitBigIntBinaryArithResult<MBigIntBitOr>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitBigIntBitXorResult(BigIntOperandId lhsId, + BigIntOperandId rhsId) { + return emitBigIntBinaryArithResult<MBigIntBitXor>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitBigIntLeftShiftResult(BigIntOperandId lhsId, + BigIntOperandId rhsId) { + return emitBigIntBinaryArithResult<MBigIntLsh>(lhsId, rhsId); +} + +bool WarpCacheIRTranspiler::emitBigIntRightShiftResult(BigIntOperandId lhsId, + BigIntOperandId rhsId) { + return emitBigIntBinaryArithResult<MBigIntRsh>(lhsId, rhsId); +} + +template <typename T> +bool WarpCacheIRTranspiler::emitBigIntUnaryArithResult( + BigIntOperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* ins = T::New(alloc(), input); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitBigIntIncResult(BigIntOperandId inputId) { + return emitBigIntUnaryArithResult<MBigIntIncrement>(inputId); +} + +bool WarpCacheIRTranspiler::emitBigIntDecResult(BigIntOperandId inputId) { + return emitBigIntUnaryArithResult<MBigIntDecrement>(inputId); +} + +bool WarpCacheIRTranspiler::emitBigIntNegationResult(BigIntOperandId inputId) { + return emitBigIntUnaryArithResult<MBigIntNegate>(inputId); +} + +bool WarpCacheIRTranspiler::emitBigIntNotResult(BigIntOperandId inputId) { + return emitBigIntUnaryArithResult<MBigIntBitNot>(inputId); +} + +bool WarpCacheIRTranspiler::emitCallStringConcatResult(StringOperandId lhsId, + StringOperandId rhsId) { + MDefinition* lhs = getOperand(lhsId); + MDefinition* rhs = getOperand(rhsId); + + auto* ins = MConcat::New(alloc(), lhs, rhs); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitCompareResult( + JSOp op, OperandId lhsId, OperandId rhsId, + MCompare::CompareType compareType) { + MDefinition* lhs = getOperand(lhsId); + MDefinition* rhs = getOperand(rhsId); + + auto* ins = MCompare::New(alloc(), lhs, rhs, op, compareType); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitCompareInt32Result(JSOp op, + Int32OperandId lhsId, + Int32OperandId rhsId) { + return emitCompareResult(op, lhsId, rhsId, MCompare::Compare_Int32); +} + +bool WarpCacheIRTranspiler::emitCompareDoubleResult(JSOp op, + NumberOperandId lhsId, + NumberOperandId rhsId) { + return emitCompareResult(op, lhsId, rhsId, MCompare::Compare_Double); +} + +bool WarpCacheIRTranspiler::emitCompareObjectResult(JSOp op, ObjOperandId lhsId, + ObjOperandId rhsId) { + MOZ_ASSERT(IsEqualityOp(op)); + return emitCompareResult(op, lhsId, rhsId, MCompare::Compare_Object); +} + +bool WarpCacheIRTranspiler::emitCompareStringResult(JSOp op, + StringOperandId lhsId, + StringOperandId rhsId) { + return emitCompareResult(op, lhsId, rhsId, MCompare::Compare_String); +} + +bool WarpCacheIRTranspiler::emitCompareSymbolResult(JSOp op, + SymbolOperandId lhsId, + SymbolOperandId rhsId) { + MOZ_ASSERT(IsEqualityOp(op)); + return emitCompareResult(op, lhsId, rhsId, MCompare::Compare_Symbol); +} + +bool WarpCacheIRTranspiler::emitCompareBigIntResult(JSOp op, + BigIntOperandId lhsId, + BigIntOperandId rhsId) { + return emitCompareResult(op, lhsId, rhsId, MCompare::Compare_BigInt); +} + +bool WarpCacheIRTranspiler::emitCompareBigIntInt32Result(JSOp op, + BigIntOperandId lhsId, + Int32OperandId rhsId) { + return emitCompareResult(op, lhsId, rhsId, MCompare::Compare_BigInt_Int32); +} + +bool WarpCacheIRTranspiler::emitCompareBigIntNumberResult( + JSOp op, BigIntOperandId lhsId, NumberOperandId rhsId) { + return emitCompareResult(op, lhsId, rhsId, MCompare::Compare_BigInt_Double); +} + +bool WarpCacheIRTranspiler::emitCompareBigIntStringResult( + JSOp op, BigIntOperandId lhsId, StringOperandId rhsId) { + return emitCompareResult(op, lhsId, rhsId, MCompare::Compare_BigInt_String); +} + +bool WarpCacheIRTranspiler::emitCompareNullUndefinedResult( + JSOp op, bool isUndefined, ValOperandId inputId) { + MDefinition* input = getOperand(inputId); + + MOZ_ASSERT(IsEqualityOp(op)); + + // A previously emitted guard ensures that one side of the comparison + // is null or undefined. + MDefinition* cst = + isUndefined ? constant(UndefinedValue()) : constant(NullValue()); + auto compareType = + isUndefined ? MCompare::Compare_Undefined : MCompare::Compare_Null; + auto* ins = MCompare::New(alloc(), input, cst, op, compareType); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitCompareDoubleSameValueResult( + NumberOperandId lhsId, NumberOperandId rhsId) { + MDefinition* lhs = getOperand(lhsId); + MDefinition* rhs = getOperand(rhsId); + + auto* sameValue = MSameValueDouble::New(alloc(), lhs, rhs); + add(sameValue); + + pushResult(sameValue); + return true; +} + +bool WarpCacheIRTranspiler::emitSameValueResult(ValOperandId lhsId, + ValOperandId rhsId) { + MDefinition* lhs = getOperand(lhsId); + MDefinition* rhs = getOperand(rhsId); + + auto* sameValue = MSameValue::New(alloc(), lhs, rhs); + add(sameValue); + + pushResult(sameValue); + return true; +} + +bool WarpCacheIRTranspiler::emitIndirectTruncateInt32Result( + Int32OperandId valId) { + MDefinition* val = getOperand(valId); + MOZ_ASSERT(val->type() == MIRType::Int32); + + auto* truncate = + MLimitedTruncate::New(alloc(), val, TruncateKind::IndirectTruncate); + add(truncate); + + pushResult(truncate); + return true; +} + +bool WarpCacheIRTranspiler::emitMathHypot2NumberResult( + NumberOperandId firstId, NumberOperandId secondId) { + MDefinitionVector vector(alloc()); + if (!vector.reserve(2)) { + return false; + } + + vector.infallibleAppend(getOperand(firstId)); + vector.infallibleAppend(getOperand(secondId)); + + auto* ins = MHypot::New(alloc(), vector); + if (!ins) { + return false; + } + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathHypot3NumberResult( + NumberOperandId firstId, NumberOperandId secondId, + NumberOperandId thirdId) { + MDefinitionVector vector(alloc()); + if (!vector.reserve(3)) { + return false; + } + + vector.infallibleAppend(getOperand(firstId)); + vector.infallibleAppend(getOperand(secondId)); + vector.infallibleAppend(getOperand(thirdId)); + + auto* ins = MHypot::New(alloc(), vector); + if (!ins) { + return false; + } + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathHypot4NumberResult( + NumberOperandId firstId, NumberOperandId secondId, NumberOperandId thirdId, + NumberOperandId fourthId) { + MDefinitionVector vector(alloc()); + if (!vector.reserve(4)) { + return false; + } + + vector.infallibleAppend(getOperand(firstId)); + vector.infallibleAppend(getOperand(secondId)); + vector.infallibleAppend(getOperand(thirdId)); + vector.infallibleAppend(getOperand(fourthId)); + + auto* ins = MHypot::New(alloc(), vector); + if (!ins) { + return false; + } + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathRandomResult(uint32_t rngOffset) { +#ifdef DEBUG + // CodeGenerator uses CompileRealm::addressOfRandomNumberGenerator. Assert it + // matches the RNG pointer stored in the stub field. + const void* rng = rawPointerField(rngOffset); + MOZ_ASSERT(rng == mirGen().realm->addressOfRandomNumberGenerator()); +#endif + + auto* ins = MRandom::New(alloc()); + addEffectful(ins); + + pushResult(ins); + return resumeAfter(ins); +} + +bool WarpCacheIRTranspiler::emitInt32MinMax(bool isMax, Int32OperandId firstId, + Int32OperandId secondId, + Int32OperandId resultId) { + MDefinition* first = getOperand(firstId); + MDefinition* second = getOperand(secondId); + + auto* ins = MMinMax::New(alloc(), first, second, MIRType::Int32, isMax); + add(ins); + + return defineOperand(resultId, ins); +} + +bool WarpCacheIRTranspiler::emitNumberMinMax(bool isMax, + NumberOperandId firstId, + NumberOperandId secondId, + NumberOperandId resultId) { + MDefinition* first = getOperand(firstId); + MDefinition* second = getOperand(secondId); + + auto* ins = MMinMax::New(alloc(), first, second, MIRType::Double, isMax); + add(ins); + + return defineOperand(resultId, ins); +} + +bool WarpCacheIRTranspiler::emitInt32MinMaxArrayResult(ObjOperandId arrayId, + bool isMax) { + MDefinition* array = getOperand(arrayId); + + auto* ins = MMinMaxArray::New(alloc(), array, MIRType::Int32, isMax); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitNumberMinMaxArrayResult(ObjOperandId arrayId, + bool isMax) { + MDefinition* array = getOperand(arrayId); + + auto* ins = MMinMaxArray::New(alloc(), array, MIRType::Double, isMax); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathAbsInt32Result(Int32OperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* ins = MAbs::New(alloc(), input, MIRType::Int32); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathAbsNumberResult(NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* ins = MAbs::New(alloc(), input, MIRType::Double); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathClz32Result(Int32OperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* ins = MClz::New(alloc(), input, MIRType::Int32); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathSignInt32Result(Int32OperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* ins = MSign::New(alloc(), input, MIRType::Int32); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathSignNumberResult(NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* ins = MSign::New(alloc(), input, MIRType::Double); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathSignNumberToInt32Result( + NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* ins = MSign::New(alloc(), input, MIRType::Int32); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathImulResult(Int32OperandId lhsId, + Int32OperandId rhsId) { + MDefinition* lhs = getOperand(lhsId); + MDefinition* rhs = getOperand(rhsId); + + auto* ins = MMul::New(alloc(), lhs, rhs, MIRType::Int32, MMul::Integer); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathFloorToInt32Result( + NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* ins = MFloor::New(alloc(), input); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathCeilToInt32Result(NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* ins = MCeil::New(alloc(), input); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathTruncToInt32Result( + NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* ins = MTrunc::New(alloc(), input); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathRoundToInt32Result( + NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* ins = MRound::New(alloc(), input); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathSqrtNumberResult(NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* ins = MSqrt::New(alloc(), input, MIRType::Double); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathFRoundNumberResult( + NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* ins = MToFloat32::New(alloc(), input); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathAtan2NumberResult(NumberOperandId yId, + NumberOperandId xId) { + MDefinition* y = getOperand(yId); + MDefinition* x = getOperand(xId); + + auto* ins = MAtan2::New(alloc(), y, x); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathFunctionNumberResult( + NumberOperandId inputId, UnaryMathFunction fun) { + MDefinition* input = getOperand(inputId); + + auto* ins = MMathFunction::New(alloc(), input, fun); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathFloorNumberResult(NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + MInstruction* ins; + if (MNearbyInt::HasAssemblerSupport(RoundingMode::Down)) { + ins = MNearbyInt::New(alloc(), input, MIRType::Double, RoundingMode::Down); + } else { + ins = MMathFunction::New(alloc(), input, UnaryMathFunction::Floor); + } + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathCeilNumberResult(NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + MInstruction* ins; + if (MNearbyInt::HasAssemblerSupport(RoundingMode::Up)) { + ins = MNearbyInt::New(alloc(), input, MIRType::Double, RoundingMode::Up); + } else { + ins = MMathFunction::New(alloc(), input, UnaryMathFunction::Ceil); + } + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMathTruncNumberResult(NumberOperandId inputId) { + MDefinition* input = getOperand(inputId); + + MInstruction* ins; + if (MNearbyInt::HasAssemblerSupport(RoundingMode::TowardsZero)) { + ins = MNearbyInt::New(alloc(), input, MIRType::Double, + RoundingMode::TowardsZero); + } else { + ins = MMathFunction::New(alloc(), input, UnaryMathFunction::Trunc); + } + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitNumberParseIntResult(StringOperandId strId, + Int32OperandId radixId) { + MDefinition* str = getOperand(strId); + MDefinition* radix = getOperand(radixId); + + auto* ins = MNumberParseInt::New(alloc(), str, radix); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitDoubleParseIntResult(NumberOperandId numId) { + MDefinition* num = getOperand(numId); + + auto* ins = MDoubleParseInt::New(alloc(), num); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitObjectToStringResult(ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* ins = MObjectClassToString::New(alloc(), obj); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitReflectGetPrototypeOfResult( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* ins = MGetPrototypeOf::New(alloc(), obj); + addEffectful(ins); + pushResult(ins); + + return resumeAfter(ins); +} + +bool WarpCacheIRTranspiler::emitArrayPush(ObjOperandId objId, + ValOperandId rhsId) { + MDefinition* obj = getOperand(objId); + MDefinition* value = getOperand(rhsId); + + auto* elements = MElements::New(alloc(), obj); + add(elements); + + auto* initLength = MInitializedLength::New(alloc(), elements); + add(initLength); + + auto* barrier = + MPostWriteElementBarrier::New(alloc(), obj, value, initLength); + add(barrier); + + auto* ins = MArrayPush::New(alloc(), obj, value); + addEffectful(ins); + pushResult(ins); + + return resumeAfter(ins); +} + +bool WarpCacheIRTranspiler::emitArrayJoinResult(ObjOperandId objId, + StringOperandId sepId) { + MDefinition* obj = getOperand(objId); + MDefinition* sep = getOperand(sepId); + + auto* join = MArrayJoin::New(alloc(), obj, sep); + addEffectful(join); + + pushResult(join); + return resumeAfter(join); +} + +bool WarpCacheIRTranspiler::emitPackedArrayPopResult(ObjOperandId arrayId) { + MDefinition* array = getOperand(arrayId); + + auto* ins = MArrayPopShift::New(alloc(), array, MArrayPopShift::Pop); + addEffectful(ins); + + pushResult(ins); + return resumeAfter(ins); +} + +bool WarpCacheIRTranspiler::emitPackedArrayShiftResult(ObjOperandId arrayId) { + MDefinition* array = getOperand(arrayId); + + auto* ins = MArrayPopShift::New(alloc(), array, MArrayPopShift::Shift); + addEffectful(ins); + + pushResult(ins); + return resumeAfter(ins); +} + +bool WarpCacheIRTranspiler::emitPackedArraySliceResult( + uint32_t templateObjectOffset, ObjOperandId arrayId, Int32OperandId beginId, + Int32OperandId endId) { + JSObject* templateObj = tenuredObjectStubField(templateObjectOffset); + + MDefinition* array = getOperand(arrayId); + MDefinition* begin = getOperand(beginId); + MDefinition* end = getOperand(endId); + + // TODO: support pre-tenuring. + gc::Heap heap = gc::Heap::Default; + + auto* ins = MArraySlice::New(alloc(), array, begin, end, templateObj, heap); + addEffectful(ins); + + pushResult(ins); + return resumeAfter(ins); +} + +bool WarpCacheIRTranspiler::emitArgumentsSliceResult( + uint32_t templateObjectOffset, ObjOperandId argsId, Int32OperandId beginId, + Int32OperandId endId) { + JSObject* templateObj = tenuredObjectStubField(templateObjectOffset); + + MDefinition* args = getOperand(argsId); + MDefinition* begin = getOperand(beginId); + MDefinition* end = getOperand(endId); + + // TODO: support pre-tenuring. + gc::Heap heap = gc::Heap::Default; + + auto* ins = + MArgumentsSlice::New(alloc(), args, begin, end, templateObj, heap); + addEffectful(ins); + + pushResult(ins); + return resumeAfter(ins); +} + +bool WarpCacheIRTranspiler::emitHasClassResult(ObjOperandId objId, + uint32_t claspOffset) { + MDefinition* obj = getOperand(objId); + const JSClass* clasp = classStubField(claspOffset); + + auto* hasClass = MHasClass::New(alloc(), obj, clasp); + add(hasClass); + + pushResult(hasClass); + return true; +} + +bool WarpCacheIRTranspiler::emitCallRegExpMatcherResult( + ObjOperandId regexpId, StringOperandId inputId, Int32OperandId lastIndexId, + uint32_t stubOffset) { + MDefinition* regexp = getOperand(regexpId); + MDefinition* input = getOperand(inputId); + MDefinition* lastIndex = getOperand(lastIndexId); + + auto* matcher = MRegExpMatcher::New(alloc(), regexp, input, lastIndex); + addEffectful(matcher); + pushResult(matcher); + + return resumeAfter(matcher); +} + +bool WarpCacheIRTranspiler::emitCallRegExpSearcherResult( + ObjOperandId regexpId, StringOperandId inputId, Int32OperandId lastIndexId, + uint32_t stubOffset) { + MDefinition* regexp = getOperand(regexpId); + MDefinition* input = getOperand(inputId); + MDefinition* lastIndex = getOperand(lastIndexId); + + auto* searcher = MRegExpSearcher::New(alloc(), regexp, input, lastIndex); + addEffectful(searcher); + pushResult(searcher); + + return resumeAfter(searcher); +} + +bool WarpCacheIRTranspiler::emitRegExpBuiltinExecMatchResult( + ObjOperandId regexpId, StringOperandId inputId, uint32_t stubOffset) { + MDefinition* regexp = getOperand(regexpId); + MDefinition* input = getOperand(inputId); + + auto* ins = MRegExpExecMatch::New(alloc(), regexp, input); + addEffectful(ins); + pushResult(ins); + + return resumeAfter(ins); +} + +bool WarpCacheIRTranspiler::emitRegExpBuiltinExecTestResult( + ObjOperandId regexpId, StringOperandId inputId, uint32_t stubOffset) { + MDefinition* regexp = getOperand(regexpId); + MDefinition* input = getOperand(inputId); + + auto* ins = MRegExpExecTest::New(alloc(), regexp, input); + addEffectful(ins); + pushResult(ins); + + return resumeAfter(ins); +} + +MInstruction* WarpCacheIRTranspiler::convertToBoolean(MDefinition* input) { + // Convert to bool with the '!!' idiom. + // + // The FoldTests and GVN passes both specifically handle this pattern. If you + // change this code, make sure to update FoldTests and GVN, too. + + auto* resultInverted = MNot::New(alloc(), input); + add(resultInverted); + auto* result = MNot::New(alloc(), resultInverted); + add(result); + + return result; +} + +bool WarpCacheIRTranspiler::emitRegExpFlagResult(ObjOperandId regexpId, + int32_t flagsMask) { + MDefinition* regexp = getOperand(regexpId); + + auto* flags = MLoadFixedSlot::New(alloc(), regexp, RegExpObject::flagsSlot()); + flags->setResultType(MIRType::Int32); + add(flags); + + auto* mask = MConstant::New(alloc(), Int32Value(flagsMask)); + add(mask); + + auto* maskedFlag = MBitAnd::New(alloc(), flags, mask, MIRType::Int32); + add(maskedFlag); + + auto* result = convertToBoolean(maskedFlag); + + pushResult(result); + return true; +} + +bool WarpCacheIRTranspiler::emitCallSubstringKernelResult( + StringOperandId strId, Int32OperandId beginId, Int32OperandId lengthId) { + MDefinition* str = getOperand(strId); + MDefinition* begin = getOperand(beginId); + MDefinition* length = getOperand(lengthId); + + auto* substr = MSubstr::New(alloc(), str, begin, length); + add(substr); + + pushResult(substr); + return true; +} + +bool WarpCacheIRTranspiler::emitStringReplaceStringResult( + StringOperandId strId, StringOperandId patternId, + StringOperandId replacementId) { + MDefinition* str = getOperand(strId); + MDefinition* pattern = getOperand(patternId); + MDefinition* replacement = getOperand(replacementId); + + auto* replace = MStringReplace::New(alloc(), str, pattern, replacement); + add(replace); + + pushResult(replace); + return true; +} + +bool WarpCacheIRTranspiler::emitStringSplitStringResult( + StringOperandId strId, StringOperandId separatorId) { + MDefinition* str = getOperand(strId); + MDefinition* separator = getOperand(separatorId); + + auto* split = MStringSplit::New(alloc(), str, separator); + add(split); + + pushResult(split); + return true; +} + +bool WarpCacheIRTranspiler::emitRegExpPrototypeOptimizableResult( + ObjOperandId protoId) { + MDefinition* proto = getOperand(protoId); + + auto* optimizable = MRegExpPrototypeOptimizable::New(alloc(), proto); + add(optimizable); + + pushResult(optimizable); + return true; +} + +bool WarpCacheIRTranspiler::emitRegExpInstanceOptimizableResult( + ObjOperandId regexpId, ObjOperandId protoId) { + MDefinition* regexp = getOperand(regexpId); + MDefinition* proto = getOperand(protoId); + + auto* optimizable = MRegExpInstanceOptimizable::New(alloc(), regexp, proto); + add(optimizable); + + pushResult(optimizable); + return true; +} + +bool WarpCacheIRTranspiler::emitGetFirstDollarIndexResult( + StringOperandId strId) { + MDefinition* str = getOperand(strId); + + auto* firstDollarIndex = MGetFirstDollarIndex::New(alloc(), str); + add(firstDollarIndex); + + pushResult(firstDollarIndex); + return true; +} + +bool WarpCacheIRTranspiler::emitIsArrayResult(ValOperandId inputId) { + MDefinition* value = getOperand(inputId); + + auto* isArray = MIsArray::New(alloc(), value); + addEffectful(isArray); + pushResult(isArray); + + return resumeAfter(isArray); +} + +bool WarpCacheIRTranspiler::emitIsObjectResult(ValOperandId inputId) { + MDefinition* value = getOperand(inputId); + + if (value->type() == MIRType::Object) { + pushResult(constant(BooleanValue(true))); + } else { + auto* isObject = MIsObject::New(alloc(), value); + add(isObject); + pushResult(isObject); + } + + return true; +} + +bool WarpCacheIRTranspiler::emitIsPackedArrayResult(ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* isPackedArray = MIsPackedArray::New(alloc(), obj); + add(isPackedArray); + + pushResult(isPackedArray); + return true; +} + +bool WarpCacheIRTranspiler::emitIsCallableResult(ValOperandId inputId) { + MDefinition* value = getOperand(inputId); + + auto* isCallable = MIsCallable::New(alloc(), value); + add(isCallable); + + pushResult(isCallable); + return true; +} + +bool WarpCacheIRTranspiler::emitIsConstructorResult(ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* isConstructor = MIsConstructor::New(alloc(), obj); + add(isConstructor); + + pushResult(isConstructor); + return true; +} + +bool WarpCacheIRTranspiler::emitIsCrossRealmArrayConstructorResult( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* ins = MIsCrossRealmArrayConstructor::New(alloc(), obj); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitIsTypedArrayResult(ObjOperandId objId, + bool isPossiblyWrapped) { + MDefinition* obj = getOperand(objId); + + auto* ins = MIsTypedArray::New(alloc(), obj, isPossiblyWrapped); + if (isPossiblyWrapped) { + addEffectful(ins); + } else { + add(ins); + } + + pushResult(ins); + + if (isPossiblyWrapped) { + if (!resumeAfter(ins)) { + return false; + } + } + + return true; +} + +bool WarpCacheIRTranspiler::emitArrayBufferViewByteOffsetInt32Result( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* byteOffset = MArrayBufferViewByteOffset::New(alloc(), obj); + add(byteOffset); + + auto* byteOffsetInt32 = MNonNegativeIntPtrToInt32::New(alloc(), byteOffset); + add(byteOffsetInt32); + + pushResult(byteOffsetInt32); + return true; +} + +bool WarpCacheIRTranspiler::emitArrayBufferViewByteOffsetDoubleResult( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* byteOffset = MArrayBufferViewByteOffset::New(alloc(), obj); + add(byteOffset); + + auto* byteOffsetDouble = MIntPtrToDouble::New(alloc(), byteOffset); + add(byteOffsetDouble); + + pushResult(byteOffsetDouble); + return true; +} + +bool WarpCacheIRTranspiler::emitTypedArrayByteLengthInt32Result( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* length = MArrayBufferViewLength::New(alloc(), obj); + add(length); + + auto* lengthInt32 = MNonNegativeIntPtrToInt32::New(alloc(), length); + add(lengthInt32); + + auto* size = MTypedArrayElementSize::New(alloc(), obj); + add(size); + + auto* mul = MMul::New(alloc(), lengthInt32, size, MIRType::Int32); + mul->setCanBeNegativeZero(false); + add(mul); + + pushResult(mul); + return true; +} + +bool WarpCacheIRTranspiler::emitTypedArrayByteLengthDoubleResult( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* length = MArrayBufferViewLength::New(alloc(), obj); + add(length); + + auto* lengthDouble = MIntPtrToDouble::New(alloc(), length); + add(lengthDouble); + + auto* size = MTypedArrayElementSize::New(alloc(), obj); + add(size); + + auto* mul = MMul::New(alloc(), lengthDouble, size, MIRType::Double); + mul->setCanBeNegativeZero(false); + add(mul); + + pushResult(mul); + return true; +} + +bool WarpCacheIRTranspiler::emitTypedArrayElementSizeResult( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* ins = MTypedArrayElementSize::New(alloc(), obj); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitGuardHasAttachedArrayBuffer( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* ins = MGuardHasAttachedArrayBuffer::New(alloc(), obj); + add(ins); + + setOperand(objId, ins); + return true; +} + +bool WarpCacheIRTranspiler::emitIsTypedArrayConstructorResult( + ObjOperandId objId) { + MDefinition* obj = getOperand(objId); + + auto* ins = MIsTypedArrayConstructor::New(alloc(), obj); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitGetNextMapSetEntryForIteratorResult( + ObjOperandId iterId, ObjOperandId resultArrId, bool isMap) { + MDefinition* iter = getOperand(iterId); + MDefinition* resultArr = getOperand(resultArrId); + + MGetNextEntryForIterator::Mode mode = + isMap ? MGetNextEntryForIterator::Map : MGetNextEntryForIterator::Set; + auto* ins = MGetNextEntryForIterator::New(alloc(), iter, resultArr, mode); + addEffectful(ins); + pushResult(ins); + + return resumeAfter(ins); +} + +bool WarpCacheIRTranspiler::emitFrameIsConstructingResult() { + if (const CallInfo* callInfo = builder_->inlineCallInfo()) { + auto* ins = constant(BooleanValue(callInfo->constructing())); + pushResult(ins); + return true; + } + + auto* ins = MIsConstructing::New(alloc()); + add(ins); + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitNewIteratorResult( + MNewIterator::Type type, uint32_t templateObjectOffset) { + JSObject* templateObj = tenuredObjectStubField(templateObjectOffset); + + auto* templateConst = constant(ObjectValue(*templateObj)); + auto* iter = MNewIterator::New(alloc(), templateConst, type); + add(iter); + + pushResult(iter); + return true; +} + +bool WarpCacheIRTranspiler::emitNewArrayIteratorResult( + uint32_t templateObjectOffset) { + return emitNewIteratorResult(MNewIterator::ArrayIterator, + templateObjectOffset); +} + +bool WarpCacheIRTranspiler::emitNewStringIteratorResult( + uint32_t templateObjectOffset) { + return emitNewIteratorResult(MNewIterator::StringIterator, + templateObjectOffset); +} + +bool WarpCacheIRTranspiler::emitNewRegExpStringIteratorResult( + uint32_t templateObjectOffset) { + return emitNewIteratorResult(MNewIterator::RegExpStringIterator, + templateObjectOffset); +} + +bool WarpCacheIRTranspiler::emitObjectCreateResult( + uint32_t templateObjectOffset) { + JSObject* templateObj = tenuredObjectStubField(templateObjectOffset); + + auto* templateConst = constant(ObjectValue(*templateObj)); + + // TODO: support pre-tenuring. + gc::Heap heap = gc::Heap::Default; + auto* obj = + MNewObject::New(alloc(), templateConst, heap, MNewObject::ObjectCreate); + addEffectful(obj); + + pushResult(obj); + return resumeAfter(obj); +} + +bool WarpCacheIRTranspiler::emitNewArrayFromLengthResult( + uint32_t templateObjectOffset, Int32OperandId lengthId) { + JSObject* templateObj = tenuredObjectStubField(templateObjectOffset); + MDefinition* length = getOperand(lengthId); + + // TODO: support pre-tenuring. + gc::Heap heap = gc::Heap::Default; + + if (length->isConstant()) { + int32_t lenInt32 = length->toConstant()->toInt32(); + if (lenInt32 >= 0 && + uint32_t(lenInt32) == templateObj->as<ArrayObject>().length()) { + uint32_t len = uint32_t(lenInt32); + auto* templateConst = constant(ObjectValue(*templateObj)); + + size_t inlineLength = + gc::GetGCKindSlots(templateObj->asTenured().getAllocKind()) - + ObjectElements::VALUES_PER_HEADER; + + MNewArray* obj; + if (len > inlineLength) { + obj = MNewArray::NewVM(alloc(), len, templateConst, heap); + } else { + obj = MNewArray::New(alloc(), len, templateConst, heap); + } + add(obj); + pushResult(obj); + return true; + } + } + + auto* obj = MNewArrayDynamicLength::New(alloc(), length, templateObj, heap); + addEffectful(obj); + pushResult(obj); + return resumeAfter(obj); +} + +bool WarpCacheIRTranspiler::emitNewTypedArrayFromLengthResult( + uint32_t templateObjectOffset, Int32OperandId lengthId) { + JSObject* templateObj = tenuredObjectStubField(templateObjectOffset); + MDefinition* length = getOperand(lengthId); + + // TODO: support pre-tenuring. + gc::Heap heap = gc::Heap::Default; + + if (length->isConstant()) { + int32_t len = length->toConstant()->toInt32(); + if (len > 0 && + uint32_t(len) == templateObj->as<TypedArrayObject>().length()) { + auto* templateConst = constant(ObjectValue(*templateObj)); + auto* obj = MNewTypedArray::New(alloc(), templateConst, heap); + add(obj); + pushResult(obj); + return true; + } + } + + auto* obj = + MNewTypedArrayDynamicLength::New(alloc(), length, templateObj, heap); + addEffectful(obj); + pushResult(obj); + return resumeAfter(obj); +} + +bool WarpCacheIRTranspiler::emitNewTypedArrayFromArrayBufferResult( + uint32_t templateObjectOffset, ObjOperandId bufferId, + ValOperandId byteOffsetId, ValOperandId lengthId) { + JSObject* templateObj = tenuredObjectStubField(templateObjectOffset); + MDefinition* buffer = getOperand(bufferId); + MDefinition* byteOffset = getOperand(byteOffsetId); + MDefinition* length = getOperand(lengthId); + + // TODO: support pre-tenuring. + gc::Heap heap = gc::Heap::Default; + + auto* obj = MNewTypedArrayFromArrayBuffer::New(alloc(), buffer, byteOffset, + length, templateObj, heap); + addEffectful(obj); + + pushResult(obj); + return resumeAfter(obj); +} + +bool WarpCacheIRTranspiler::emitNewTypedArrayFromArrayResult( + uint32_t templateObjectOffset, ObjOperandId arrayId) { + JSObject* templateObj = tenuredObjectStubField(templateObjectOffset); + MDefinition* array = getOperand(arrayId); + + // TODO: support pre-tenuring. + gc::Heap heap = gc::Heap::Default; + + auto* obj = MNewTypedArrayFromArray::New(alloc(), array, templateObj, heap); + addEffectful(obj); + + pushResult(obj); + return resumeAfter(obj); +} + +bool WarpCacheIRTranspiler::emitAtomicsCompareExchangeResult( + ObjOperandId objId, IntPtrOperandId indexId, uint32_t expectedId, + uint32_t replacementId, Scalar::Type elementType) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + MDefinition* expected = getOperand(ValOperandId(expectedId)); + MDefinition* replacement = getOperand(ValOperandId(replacementId)); + + auto* length = MArrayBufferViewLength::New(alloc(), obj); + add(length); + + index = addBoundsCheck(index, length); + + auto* elements = MArrayBufferViewElements::New(alloc(), obj); + add(elements); + + bool forceDoubleForUint32 = true; + MIRType knownType = + MIRTypeForArrayBufferViewRead(elementType, forceDoubleForUint32); + + auto* cas = MCompareExchangeTypedArrayElement::New( + alloc(), elements, index, elementType, expected, replacement); + cas->setResultType(knownType); + addEffectful(cas); + + pushResult(cas); + return resumeAfter(cas); +} + +bool WarpCacheIRTranspiler::emitAtomicsExchangeResult( + ObjOperandId objId, IntPtrOperandId indexId, uint32_t valueId, + Scalar::Type elementType) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + MDefinition* value = getOperand(ValOperandId(valueId)); + + auto* length = MArrayBufferViewLength::New(alloc(), obj); + add(length); + + index = addBoundsCheck(index, length); + + auto* elements = MArrayBufferViewElements::New(alloc(), obj); + add(elements); + + bool forceDoubleForUint32 = true; + MIRType knownType = + MIRTypeForArrayBufferViewRead(elementType, forceDoubleForUint32); + + auto* exchange = MAtomicExchangeTypedArrayElement::New( + alloc(), elements, index, value, elementType); + exchange->setResultType(knownType); + addEffectful(exchange); + + pushResult(exchange); + return resumeAfter(exchange); +} + +bool WarpCacheIRTranspiler::emitAtomicsBinaryOp(ObjOperandId objId, + IntPtrOperandId indexId, + uint32_t valueId, + Scalar::Type elementType, + bool forEffect, AtomicOp op) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + MDefinition* value = getOperand(ValOperandId(valueId)); + + auto* length = MArrayBufferViewLength::New(alloc(), obj); + add(length); + + index = addBoundsCheck(index, length); + + auto* elements = MArrayBufferViewElements::New(alloc(), obj); + add(elements); + + bool forceDoubleForUint32 = true; + MIRType knownType = + MIRTypeForArrayBufferViewRead(elementType, forceDoubleForUint32); + + auto* binop = MAtomicTypedArrayElementBinop::New( + alloc(), op, elements, index, elementType, value, forEffect); + if (!forEffect) { + binop->setResultType(knownType); + } + addEffectful(binop); + + if (!forEffect) { + pushResult(binop); + } else { + pushResult(constant(UndefinedValue())); + } + return resumeAfter(binop); +} + +bool WarpCacheIRTranspiler::emitAtomicsAddResult(ObjOperandId objId, + IntPtrOperandId indexId, + uint32_t valueId, + Scalar::Type elementType, + bool forEffect) { + return emitAtomicsBinaryOp(objId, indexId, valueId, elementType, forEffect, + AtomicFetchAddOp); +} + +bool WarpCacheIRTranspiler::emitAtomicsSubResult(ObjOperandId objId, + IntPtrOperandId indexId, + uint32_t valueId, + Scalar::Type elementType, + bool forEffect) { + return emitAtomicsBinaryOp(objId, indexId, valueId, elementType, forEffect, + AtomicFetchSubOp); +} + +bool WarpCacheIRTranspiler::emitAtomicsAndResult(ObjOperandId objId, + IntPtrOperandId indexId, + uint32_t valueId, + Scalar::Type elementType, + bool forEffect) { + return emitAtomicsBinaryOp(objId, indexId, valueId, elementType, forEffect, + AtomicFetchAndOp); +} + +bool WarpCacheIRTranspiler::emitAtomicsOrResult(ObjOperandId objId, + IntPtrOperandId indexId, + uint32_t valueId, + Scalar::Type elementType, + bool forEffect) { + return emitAtomicsBinaryOp(objId, indexId, valueId, elementType, forEffect, + AtomicFetchOrOp); +} + +bool WarpCacheIRTranspiler::emitAtomicsXorResult(ObjOperandId objId, + IntPtrOperandId indexId, + uint32_t valueId, + Scalar::Type elementType, + bool forEffect) { + return emitAtomicsBinaryOp(objId, indexId, valueId, elementType, forEffect, + AtomicFetchXorOp); +} + +bool WarpCacheIRTranspiler::emitAtomicsLoadResult(ObjOperandId objId, + IntPtrOperandId indexId, + Scalar::Type elementType) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + + auto* length = MArrayBufferViewLength::New(alloc(), obj); + add(length); + + index = addBoundsCheck(index, length); + + auto* elements = MArrayBufferViewElements::New(alloc(), obj); + add(elements); + + bool forceDoubleForUint32 = true; + MIRType knownType = + MIRTypeForArrayBufferViewRead(elementType, forceDoubleForUint32); + + auto* load = MLoadUnboxedScalar::New(alloc(), elements, index, elementType, + DoesRequireMemoryBarrier); + load->setResultType(knownType); + addEffectful(load); + + pushResult(load); + return resumeAfter(load); +} + +bool WarpCacheIRTranspiler::emitAtomicsStoreResult(ObjOperandId objId, + IntPtrOperandId indexId, + uint32_t valueId, + Scalar::Type elementType) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + MDefinition* value = getOperand(ValOperandId(valueId)); + + auto* length = MArrayBufferViewLength::New(alloc(), obj); + add(length); + + index = addBoundsCheck(index, length); + + auto* elements = MArrayBufferViewElements::New(alloc(), obj); + add(elements); + + auto* store = MStoreUnboxedScalar::New(alloc(), elements, index, value, + elementType, DoesRequireMemoryBarrier); + addEffectful(store); + + pushResult(value); + return resumeAfter(store); +} + +bool WarpCacheIRTranspiler::emitAtomicsIsLockFreeResult( + Int32OperandId valueId) { + MDefinition* value = getOperand(valueId); + + auto* ilf = MAtomicIsLockFree::New(alloc(), value); + add(ilf); + + pushResult(ilf); + return true; +} + +bool WarpCacheIRTranspiler::emitBigIntAsIntNResult(Int32OperandId bitsId, + BigIntOperandId bigIntId) { + MDefinition* bits = getOperand(bitsId); + MDefinition* bigInt = getOperand(bigIntId); + + auto* ins = MBigIntAsIntN::New(alloc(), bits, bigInt); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitBigIntAsUintNResult(Int32OperandId bitsId, + BigIntOperandId bigIntId) { + MDefinition* bits = getOperand(bitsId); + MDefinition* bigInt = getOperand(bigIntId); + + auto* ins = MBigIntAsUintN::New(alloc(), bits, bigInt); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitGuardToNonGCThing(ValOperandId inputId) { + MDefinition* def = getOperand(inputId); + if (IsNonGCThing(def->type())) { + return true; + } + + auto* ins = MGuardNonGCThing::New(alloc(), def); + add(ins); + + setOperand(inputId, ins); + return true; +} + +bool WarpCacheIRTranspiler::emitSetHasNonGCThingResult(ObjOperandId setId, + ValOperandId valId) { + MDefinition* set = getOperand(setId); + MDefinition* val = getOperand(valId); + + auto* hashValue = MToHashableNonGCThing::New(alloc(), val); + add(hashValue); + + auto* hash = MHashNonGCThing::New(alloc(), hashValue); + add(hash); + + auto* ins = MSetObjectHasNonBigInt::New(alloc(), set, hashValue, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitSetHasStringResult(ObjOperandId setId, + StringOperandId strId) { + MDefinition* set = getOperand(setId); + MDefinition* str = getOperand(strId); + + auto* hashValue = MToHashableString::New(alloc(), str); + add(hashValue); + + auto* hash = MHashString::New(alloc(), hashValue); + add(hash); + + auto* ins = MSetObjectHasNonBigInt::New(alloc(), set, hashValue, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitSetHasSymbolResult(ObjOperandId setId, + SymbolOperandId symId) { + MDefinition* set = getOperand(setId); + MDefinition* sym = getOperand(symId); + + auto* hash = MHashSymbol::New(alloc(), sym); + add(hash); + + auto* ins = MSetObjectHasNonBigInt::New(alloc(), set, sym, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitSetHasBigIntResult(ObjOperandId setId, + BigIntOperandId bigIntId) { + MDefinition* set = getOperand(setId); + MDefinition* bigInt = getOperand(bigIntId); + + auto* hash = MHashBigInt::New(alloc(), bigInt); + add(hash); + + auto* ins = MSetObjectHasBigInt::New(alloc(), set, bigInt, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitSetHasObjectResult(ObjOperandId setId, + ObjOperandId objId) { + MDefinition* set = getOperand(setId); + MDefinition* obj = getOperand(objId); + + auto* hash = MHashObject::New(alloc(), set, obj); + add(hash); + + auto* ins = MSetObjectHasNonBigInt::New(alloc(), set, obj, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitSetHasResult(ObjOperandId setId, + ValOperandId valId) { + MDefinition* set = getOperand(setId); + MDefinition* val = getOperand(valId); + +#ifdef JS_PUNBOX64 + auto* hashValue = MToHashableValue::New(alloc(), val); + add(hashValue); + + auto* hash = MHashValue::New(alloc(), set, hashValue); + add(hash); + + auto* ins = MSetObjectHasValue::New(alloc(), set, hashValue, hash); + add(ins); +#else + auto* ins = MSetObjectHasValueVMCall::New(alloc(), set, val); + add(ins); +#endif + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitSetSizeResult(ObjOperandId setId) { + MDefinition* set = getOperand(setId); + + auto* ins = MSetObjectSize::New(alloc(), set); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMapHasNonGCThingResult(ObjOperandId mapId, + ValOperandId valId) { + MDefinition* map = getOperand(mapId); + MDefinition* val = getOperand(valId); + + auto* hashValue = MToHashableNonGCThing::New(alloc(), val); + add(hashValue); + + auto* hash = MHashNonGCThing::New(alloc(), hashValue); + add(hash); + + auto* ins = MMapObjectHasNonBigInt::New(alloc(), map, hashValue, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMapHasStringResult(ObjOperandId mapId, + StringOperandId strId) { + MDefinition* map = getOperand(mapId); + MDefinition* str = getOperand(strId); + + auto* hashValue = MToHashableString::New(alloc(), str); + add(hashValue); + + auto* hash = MHashString::New(alloc(), hashValue); + add(hash); + + auto* ins = MMapObjectHasNonBigInt::New(alloc(), map, hashValue, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMapHasSymbolResult(ObjOperandId mapId, + SymbolOperandId symId) { + MDefinition* map = getOperand(mapId); + MDefinition* sym = getOperand(symId); + + auto* hash = MHashSymbol::New(alloc(), sym); + add(hash); + + auto* ins = MMapObjectHasNonBigInt::New(alloc(), map, sym, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMapHasBigIntResult(ObjOperandId mapId, + BigIntOperandId bigIntId) { + MDefinition* map = getOperand(mapId); + MDefinition* bigInt = getOperand(bigIntId); + + auto* hash = MHashBigInt::New(alloc(), bigInt); + add(hash); + + auto* ins = MMapObjectHasBigInt::New(alloc(), map, bigInt, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMapHasObjectResult(ObjOperandId mapId, + ObjOperandId objId) { + MDefinition* map = getOperand(mapId); + MDefinition* obj = getOperand(objId); + + auto* hash = MHashObject::New(alloc(), map, obj); + add(hash); + + auto* ins = MMapObjectHasNonBigInt::New(alloc(), map, obj, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMapHasResult(ObjOperandId mapId, + ValOperandId valId) { + MDefinition* map = getOperand(mapId); + MDefinition* val = getOperand(valId); + +#ifdef JS_PUNBOX64 + auto* hashValue = MToHashableValue::New(alloc(), val); + add(hashValue); + + auto* hash = MHashValue::New(alloc(), map, hashValue); + add(hash); + + auto* ins = MMapObjectHasValue::New(alloc(), map, hashValue, hash); + add(ins); +#else + auto* ins = MMapObjectHasValueVMCall::New(alloc(), map, val); + add(ins); +#endif + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMapGetNonGCThingResult(ObjOperandId mapId, + ValOperandId valId) { + MDefinition* map = getOperand(mapId); + MDefinition* val = getOperand(valId); + + auto* hashValue = MToHashableNonGCThing::New(alloc(), val); + add(hashValue); + + auto* hash = MHashNonGCThing::New(alloc(), hashValue); + add(hash); + + auto* ins = MMapObjectGetNonBigInt::New(alloc(), map, hashValue, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMapGetStringResult(ObjOperandId mapId, + StringOperandId strId) { + MDefinition* map = getOperand(mapId); + MDefinition* str = getOperand(strId); + + auto* hashValue = MToHashableString::New(alloc(), str); + add(hashValue); + + auto* hash = MHashString::New(alloc(), hashValue); + add(hash); + + auto* ins = MMapObjectGetNonBigInt::New(alloc(), map, hashValue, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMapGetSymbolResult(ObjOperandId mapId, + SymbolOperandId symId) { + MDefinition* map = getOperand(mapId); + MDefinition* sym = getOperand(symId); + + auto* hash = MHashSymbol::New(alloc(), sym); + add(hash); + + auto* ins = MMapObjectGetNonBigInt::New(alloc(), map, sym, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMapGetBigIntResult(ObjOperandId mapId, + BigIntOperandId bigIntId) { + MDefinition* map = getOperand(mapId); + MDefinition* bigInt = getOperand(bigIntId); + + auto* hash = MHashBigInt::New(alloc(), bigInt); + add(hash); + + auto* ins = MMapObjectGetBigInt::New(alloc(), map, bigInt, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMapGetObjectResult(ObjOperandId mapId, + ObjOperandId objId) { + MDefinition* map = getOperand(mapId); + MDefinition* obj = getOperand(objId); + + auto* hash = MHashObject::New(alloc(), map, obj); + add(hash); + + auto* ins = MMapObjectGetNonBigInt::New(alloc(), map, obj, hash); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMapGetResult(ObjOperandId mapId, + ValOperandId valId) { + MDefinition* map = getOperand(mapId); + MDefinition* val = getOperand(valId); + +#ifdef JS_PUNBOX64 + auto* hashValue = MToHashableValue::New(alloc(), val); + add(hashValue); + + auto* hash = MHashValue::New(alloc(), map, hashValue); + add(hash); + + auto* ins = MMapObjectGetValue::New(alloc(), map, hashValue, hash); + add(ins); +#else + auto* ins = MMapObjectGetValueVMCall::New(alloc(), map, val); + add(ins); +#endif + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitMapSizeResult(ObjOperandId mapId) { + MDefinition* map = getOperand(mapId); + + auto* ins = MMapObjectSize::New(alloc(), map); + add(ins); + + pushResult(ins); + return true; +} + +bool WarpCacheIRTranspiler::emitTruthyResult(OperandId inputId) { + MDefinition* input = getOperand(inputId); + + auto* result = convertToBoolean(input); + + pushResult(result); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadInt32TruthyResult(ValOperandId inputId) { + return emitTruthyResult(inputId); +} + +bool WarpCacheIRTranspiler::emitLoadDoubleTruthyResult( + NumberOperandId inputId) { + return emitTruthyResult(inputId); +} + +bool WarpCacheIRTranspiler::emitLoadStringTruthyResult( + StringOperandId inputId) { + return emitTruthyResult(inputId); +} + +bool WarpCacheIRTranspiler::emitLoadObjectTruthyResult(ObjOperandId inputId) { + return emitTruthyResult(inputId); +} + +bool WarpCacheIRTranspiler::emitLoadBigIntTruthyResult( + BigIntOperandId inputId) { + return emitTruthyResult(inputId); +} + +bool WarpCacheIRTranspiler::emitLoadValueTruthyResult(ValOperandId inputId) { + return emitTruthyResult(inputId); +} + +bool WarpCacheIRTranspiler::emitLoadOperandResult(ValOperandId inputId) { + MDefinition* input = getOperand(inputId); + pushResult(input); + return true; +} + +bool WarpCacheIRTranspiler::emitLoadWrapperTarget(ObjOperandId objId, + ObjOperandId resultId) { + MDefinition* obj = getOperand(objId); + + auto* ins = MLoadWrapperTarget::New(alloc(), obj); + add(ins); + + return defineOperand(resultId, ins); +} + +// When we transpile a call, we may generate guards for some +// arguments. To make sure the call instruction depends on those +// guards, when the transpiler creates an operand for an argument, we +// register the OperandId of that argument in argumentIds_. (See +// emitLoadArgumentSlot.) Before generating the call, we update the +// CallInfo to use the appropriate value from operands_. +// Note: The callee is an explicit argument to the call op, and is +// tracked separately. +void WarpCacheIRTranspiler::updateArgumentsFromOperands() { + for (uint32_t i = 0; i < uint32_t(ArgumentKind::NumKinds); i++) { + ArgumentKind kind = ArgumentKind(i); + OperandId id = argumentOperandIds_[kind]; + if (id.valid()) { + switch (kind) { + case ArgumentKind::This: + callInfo_->setThis(getOperand(id)); + break; + case ArgumentKind::NewTarget: + callInfo_->setNewTarget(getOperand(id)); + break; + case ArgumentKind::Arg0: + callInfo_->setArg(0, getOperand(id)); + break; + case ArgumentKind::Arg1: + callInfo_->setArg(1, getOperand(id)); + break; + case ArgumentKind::Arg2: + callInfo_->setArg(2, getOperand(id)); + break; + case ArgumentKind::Arg3: + callInfo_->setArg(3, getOperand(id)); + break; + case ArgumentKind::Arg4: + callInfo_->setArg(4, getOperand(id)); + break; + case ArgumentKind::Arg5: + callInfo_->setArg(5, getOperand(id)); + break; + case ArgumentKind::Arg6: + callInfo_->setArg(6, getOperand(id)); + break; + case ArgumentKind::Arg7: + callInfo_->setArg(7, getOperand(id)); + break; + case ArgumentKind::Callee: + case ArgumentKind::NumKinds: + MOZ_CRASH("Unexpected argument kind"); + } + } + } +} + +bool WarpCacheIRTranspiler::emitLoadArgumentSlot(ValOperandId resultId, + uint32_t slotIndex) { + // Reverse of GetIndexOfArgument. + + // Layout: + // NewTarget | Args.. (reversed) | ThisValue | Callee + // 0 | ArgC .. Arg1 Arg0 (+1) | argc (+1) | argc + 1 (+ 1) + // ^ (if constructing) + + // NewTarget (optional) + if (callInfo_->constructing()) { + if (slotIndex == 0) { + setArgumentId(ArgumentKind::NewTarget, resultId); + return defineOperand(resultId, callInfo_->getNewTarget()); + } + + slotIndex -= 1; // Adjust slot index to match non-constructing calls. + } + + // Args.. + if (slotIndex < callInfo_->argc()) { + uint32_t arg = callInfo_->argc() - 1 - slotIndex; + ArgumentKind kind = ArgumentKindForArgIndex(arg); + MOZ_ASSERT(kind < ArgumentKind::NumKinds); + setArgumentId(kind, resultId); + return defineOperand(resultId, callInfo_->getArg(arg)); + } + + // ThisValue + if (slotIndex == callInfo_->argc()) { + setArgumentId(ArgumentKind::This, resultId); + return defineOperand(resultId, callInfo_->thisArg()); + } + + // Callee + MOZ_ASSERT(slotIndex == callInfo_->argc() + 1); + return defineOperand(resultId, callInfo_->callee()); +} + +bool WarpCacheIRTranspiler::emitLoadArgumentFixedSlot(ValOperandId resultId, + uint8_t slotIndex) { + return emitLoadArgumentSlot(resultId, slotIndex); +} + +bool WarpCacheIRTranspiler::emitLoadArgumentDynamicSlot(ValOperandId resultId, + Int32OperandId argcId, + uint8_t slotIndex) { +#ifdef DEBUG + MDefinition* argc = getOperand(argcId); + MOZ_ASSERT(argc->toConstant()->toInt32() == + static_cast<int32_t>(callInfo_->argc())); +#endif + + return emitLoadArgumentSlot(resultId, callInfo_->argc() + slotIndex); +} + +WrappedFunction* WarpCacheIRTranspiler::maybeWrappedFunction( + MDefinition* callee, CallKind kind, uint16_t nargs, FunctionFlags flags) { + MOZ_ASSERT(callee->isConstant() || callee->isNurseryObject()); + + // If this is a native without a JitEntry, WrappedFunction needs to know the + // target JSFunction. + // TODO: support nursery-allocated natives with WrappedFunction, maybe by + // storing the JSNative in the Baseline stub like flags/nargs. + bool isNative = flags.isNativeWithoutJitEntry(); + if (isNative && !callee->isConstant()) { + return nullptr; + } + + JSFunction* nativeTarget = nullptr; + if (isNative) { + nativeTarget = &callee->toConstant()->toObject().as<JSFunction>(); + } + + WrappedFunction* wrappedTarget = + new (alloc()) WrappedFunction(nativeTarget, nargs, flags); + MOZ_ASSERT_IF(kind == CallKind::Native || kind == CallKind::DOM, + wrappedTarget->isNativeWithoutJitEntry()); + MOZ_ASSERT_IF(kind == CallKind::Scripted, wrappedTarget->hasJitEntry()); + return wrappedTarget; +} + +WrappedFunction* WarpCacheIRTranspiler::maybeCallTarget(MDefinition* callee, + CallKind kind) { + // CacheIR emits the following for specialized calls: + // GuardSpecificFunction <callee> <func> .. + // Call(Native|Scripted)Function <callee> .. + // or: + // GuardClass <callee> .. + // GuardFunctionScript <callee> <script> .. + // CallScriptedFunction <callee> .. + // + // We can use the <func> JSFunction or <script> BaseScript to specialize this + // call. + if (callee->isGuardSpecificFunction()) { + auto* guard = callee->toGuardSpecificFunction(); + return maybeWrappedFunction(guard->expected(), kind, guard->nargs(), + guard->flags()); + } + if (callee->isGuardFunctionScript()) { + MOZ_ASSERT(kind == CallKind::Scripted); + auto* guard = callee->toGuardFunctionScript(); + WrappedFunction* wrappedTarget = new (alloc()) WrappedFunction( + /* nativeFun = */ nullptr, guard->nargs(), guard->flags()); + MOZ_ASSERT(wrappedTarget->hasJitEntry()); + return wrappedTarget; + } + return nullptr; +} + +// If it is possible to use MCall for this call, update callInfo_ to use +// the correct arguments. Otherwise, update the ArgFormat of callInfo_. +bool WarpCacheIRTranspiler::updateCallInfo(MDefinition* callee, + CallFlags flags) { + // The transpilation will add various guards to the callee. + // We replace the callee referenced by the CallInfo, so that + // the resulting call instruction depends on these guards. + callInfo_->setCallee(callee); + + // The transpilation may also add guards to other arguments. + // We replace those arguments in the CallInfo here. + updateArgumentsFromOperands(); + + switch (flags.getArgFormat()) { + case CallFlags::Standard: + MOZ_ASSERT(callInfo_->argFormat() == CallInfo::ArgFormat::Standard); + break; + case CallFlags::Spread: + MOZ_ASSERT(callInfo_->argFormat() == CallInfo::ArgFormat::Array); + break; + case CallFlags::FunCall: + // Note: We already changed the callee to the target + // function instead of the |call| function. + MOZ_ASSERT(!callInfo_->constructing()); + MOZ_ASSERT(callInfo_->argFormat() == CallInfo::ArgFormat::Standard); + + if (callInfo_->argc() == 0) { + // Special case for fun.call() with no arguments. + auto* undef = constant(UndefinedValue()); + callInfo_->setThis(undef); + } else { + // The first argument for |call| is the new this value. + callInfo_->setThis(callInfo_->getArg(0)); + + // Shift down all other arguments by removing the first. + callInfo_->removeArg(0); + } + break; + case CallFlags::FunApplyArgsObj: + MOZ_ASSERT(!callInfo_->constructing()); + MOZ_ASSERT(callInfo_->argFormat() == CallInfo::ArgFormat::Standard); + + callInfo_->setArgFormat(CallInfo::ArgFormat::FunApplyArgsObj); + break; + case CallFlags::FunApplyArray: { + MDefinition* argFunc = callInfo_->thisArg(); + MDefinition* argThis = callInfo_->getArg(0); + callInfo_->setCallee(argFunc); + callInfo_->setThis(argThis); + callInfo_->setArgFormat(CallInfo::ArgFormat::Array); + break; + } + case CallFlags::FunApplyNullUndefined: + // Note: We already changed the callee to the target + // function instead of the |apply| function. + MOZ_ASSERT(callInfo_->argc() == 2); + MOZ_ASSERT(!callInfo_->constructing()); + MOZ_ASSERT(callInfo_->argFormat() == CallInfo::ArgFormat::Standard); + callInfo_->setThis(callInfo_->getArg(0)); + callInfo_->getArg(1)->setImplicitlyUsedUnchecked(); + callInfo_->removeArg(1); + callInfo_->removeArg(0); + break; + default: + MOZ_CRASH("Unsupported arg format"); + } + return true; +} + +// Returns true if we are generating a call to CreateThisFromIon and +// must check its return value. +bool WarpCacheIRTranspiler::maybeCreateThis(MDefinition* callee, + CallFlags flags, CallKind kind) { + MOZ_ASSERT(kind != CallKind::DOM, "DOM functions are not constructors"); + MDefinition* thisArg = callInfo_->thisArg(); + + if (kind == CallKind::Native) { + // Native functions keep the is-constructing MagicValue as |this|. + // If one of the arguments uses spread syntax this can be a loop phi with + // MIRType::Value. + MOZ_ASSERT(thisArg->type() == MIRType::MagicIsConstructing || + thisArg->isPhi()); + return false; + } + MOZ_ASSERT(kind == CallKind::Scripted); + + if (thisArg->isNewPlainObject()) { + // We have already updated |this| based on MetaScriptedThisShape. We do + // not need to generate a check. + return false; + } + if (flags.needsUninitializedThis()) { + MConstant* uninit = constant(MagicValue(JS_UNINITIALIZED_LEXICAL)); + thisArg->setImplicitlyUsedUnchecked(); + callInfo_->setThis(uninit); + return false; + } + // See the Native case above. + MOZ_ASSERT(thisArg->type() == MIRType::MagicIsConstructing || + thisArg->isPhi()); + + MDefinition* newTarget = callInfo_->getNewTarget(); + auto* createThis = MCreateThis::New(alloc(), callee, newTarget); + add(createThis); + + thisArg->setImplicitlyUsedUnchecked(); + callInfo_->setThis(createThis); + + return true; +} + +bool WarpCacheIRTranspiler::emitCallFunction( + ObjOperandId calleeId, Int32OperandId argcId, + mozilla::Maybe<ObjOperandId> thisObjId, CallFlags flags, CallKind kind) { + MDefinition* callee = getOperand(calleeId); + if (kind == CallKind::Scripted && callInfo_ && callInfo_->isInlined()) { + // We are transpiling to generate the correct guards. We also + // update the CallInfo to use the correct arguments. Code for the + // inlined function itself will be generated in + // WarpBuilder::buildInlinedCall. + if (!updateCallInfo(callee, flags)) { + return false; + } + if (callInfo_->constructing()) { + MOZ_ASSERT(flags.isConstructing()); + + // We call maybeCreateThis to update |this|, but inlined constructors + // never need a VM call. CallIRGenerator::getThisForScripted ensures that + // we don't attach a specialized stub unless we have a template object or + // know that the constructor needs uninitialized this. + MOZ_ALWAYS_FALSE(maybeCreateThis(callee, flags, CallKind::Scripted)); + mozilla::DebugOnly<MDefinition*> thisArg = callInfo_->thisArg(); + MOZ_ASSERT(thisArg->isNewPlainObject() || + thisArg->type() == MIRType::MagicUninitializedLexical); + } + + if (flags.getArgFormat() == CallFlags::FunCall) { + callInfo_->setInliningResumeMode(ResumeMode::InlinedFunCall); + } else { + MOZ_ASSERT(flags.getArgFormat() == CallFlags::Standard); + callInfo_->setInliningResumeMode(ResumeMode::InlinedStandardCall); + } + + switch (callInfo_->argFormat()) { + case CallInfo::ArgFormat::Standard: + break; + default: + MOZ_CRASH("Unsupported arg format"); + } + return true; + } + +#ifdef DEBUG + MDefinition* argc = getOperand(argcId); + MOZ_ASSERT(argc->toConstant()->toInt32() == + static_cast<int32_t>(callInfo_->argc())); +#endif + + if (!updateCallInfo(callee, flags)) { + return false; + } + + if (kind == CallKind::DOM) { + MOZ_ASSERT(flags.getArgFormat() == CallFlags::Standard); + // For DOM calls |this| has a class guard. + MDefinition* thisObj = getOperand(*thisObjId); + callInfo_->setThis(thisObj); + } + + WrappedFunction* wrappedTarget = maybeCallTarget(callee, kind); + + bool needsThisCheck = false; + if (callInfo_->constructing()) { + MOZ_ASSERT(flags.isConstructing()); + needsThisCheck = maybeCreateThis(callee, flags, kind); + if (needsThisCheck) { + wrappedTarget = nullptr; + } + } + + switch (callInfo_->argFormat()) { + case CallInfo::ArgFormat::Standard: { + MCall* call = makeCall(*callInfo_, needsThisCheck, wrappedTarget, + kind == CallKind::DOM); + if (!call) { + return false; + } + + if (flags.isSameRealm()) { + call->setNotCrossRealm(); + } + + if (call->isEffectful()) { + addEffectful(call); + pushResult(call); + return resumeAfter(call); + } + + MOZ_ASSERT(kind == CallKind::DOM); + add(call); + pushResult(call); + return true; + } + case CallInfo::ArgFormat::Array: { + MInstruction* call = makeSpreadCall(*callInfo_, needsThisCheck, + flags.isSameRealm(), wrappedTarget); + if (!call) { + return false; + } + addEffectful(call); + pushResult(call); + + return resumeAfter(call); + } + case CallInfo::ArgFormat::FunApplyArgsObj: { + return emitFunApplyArgsObj(wrappedTarget, flags); + } + } + MOZ_CRASH("unreachable"); +} + +bool WarpCacheIRTranspiler::emitFunApplyArgsObj(WrappedFunction* wrappedTarget, + CallFlags flags) { + MOZ_ASSERT(!callInfo_->constructing()); + + MDefinition* callee = callInfo_->thisArg(); + MDefinition* thisArg = callInfo_->getArg(0); + MDefinition* argsObj = callInfo_->getArg(1); + + MApplyArgsObj* apply = + MApplyArgsObj::New(alloc(), wrappedTarget, callee, argsObj, thisArg); + + if (flags.isSameRealm()) { + apply->setNotCrossRealm(); + } + if (callInfo_->ignoresReturnValue()) { + apply->setIgnoresReturnValue(); + } + + addEffectful(apply); + pushResult(apply); + + return resumeAfter(apply); +} + +#ifndef JS_SIMULATOR +bool WarpCacheIRTranspiler::emitCallNativeFunction(ObjOperandId calleeId, + Int32OperandId argcId, + CallFlags flags, + uint32_t argcFixed, + bool ignoresReturnValue) { + // Instead of ignoresReturnValue we use CallInfo::ignoresReturnValue. + return emitCallFunction(calleeId, argcId, mozilla::Nothing(), flags, + CallKind::Native); +} + +bool WarpCacheIRTranspiler::emitCallDOMFunction(ObjOperandId calleeId, + Int32OperandId argcId, + ObjOperandId thisObjId, + CallFlags flags, + uint32_t argcFixed) { + return emitCallFunction(calleeId, argcId, mozilla::Some(thisObjId), flags, + CallKind::DOM); +} +#else +bool WarpCacheIRTranspiler::emitCallNativeFunction(ObjOperandId calleeId, + Int32OperandId argcId, + CallFlags flags, + uint32_t argcFixed, + uint32_t targetOffset) { + return emitCallFunction(calleeId, argcId, mozilla::Nothing(), flags, + CallKind::Native); +} + +bool WarpCacheIRTranspiler::emitCallDOMFunction( + ObjOperandId calleeId, Int32OperandId argcId, ObjOperandId thisObjId, + CallFlags flags, uint32_t argcFixed, uint32_t targetOffset) { + return emitCallFunction(calleeId, argcId, mozilla::Some(thisObjId), flags, + CallKind::DOM); +} +#endif + +bool WarpCacheIRTranspiler::emitCallScriptedFunction(ObjOperandId calleeId, + Int32OperandId argcId, + CallFlags flags, + uint32_t argcFixed) { + return emitCallFunction(calleeId, argcId, mozilla::Nothing(), flags, + CallKind::Scripted); +} + +bool WarpCacheIRTranspiler::emitCallInlinedFunction(ObjOperandId calleeId, + Int32OperandId argcId, + uint32_t icScriptOffset, + CallFlags flags, + uint32_t argcFixed) { + return emitCallFunction(calleeId, argcId, mozilla::Nothing(), flags, + CallKind::Scripted); +} + +bool WarpCacheIRTranspiler::emitCallClassHook(ObjOperandId calleeId, + Int32OperandId argcId, + CallFlags flags, + uint32_t argcFixed, + uint32_t targetOffset) { + MDefinition* callee = getOperand(calleeId); + JSNative target = jsnativeStubField(targetOffset); + +#ifdef DEBUG + MDefinition* argc = getOperand(argcId); + MOZ_ASSERT(argc->toConstant()->toInt32() == + static_cast<int32_t>(callInfo_->argc())); +#endif + + if (!updateCallInfo(callee, flags)) { + return false; + } + + MOZ_ASSERT(callInfo_->argFormat() == CallInfo::ArgFormat::Standard); + MOZ_ASSERT(flags.getArgFormat() == CallFlags::ArgFormat::Standard); + + // Callees can be from any realm. If this changes, we should update + // MCallClassHook::maybeCrossRealm. + MOZ_ASSERT(!flags.isSameRealm()); + + auto* call = MCallClassHook::New(alloc(), target, callInfo_->argc(), + callInfo_->constructing()); + if (!call) { + return false; + } + + if (callInfo_->ignoresReturnValue()) { + call->setIgnoresReturnValue(); + } + + call->initCallee(callInfo_->callee()); + call->addArg(0, callInfo_->thisArg()); + + for (uint32_t i = 0; i < callInfo_->argc(); i++) { + call->addArg(i + 1, callInfo_->getArg(i)); + } + + if (callInfo_->constructing()) { + call->addArg(1 + callInfo_->argc(), callInfo_->getNewTarget()); + } + + addEffectful(call); + pushResult(call); + + return resumeAfter(call); +} + +bool WarpCacheIRTranspiler::emitCallBoundScriptedFunction( + ObjOperandId calleeId, ObjOperandId targetId, Int32OperandId argcId, + CallFlags flags, uint32_t numBoundArgs) { + MDefinition* callee = getOperand(calleeId); + MDefinition* target = getOperand(targetId); + + MOZ_ASSERT(callInfo_->argFormat() == CallInfo::ArgFormat::Standard); + MOZ_ASSERT(callInfo_->constructing() == flags.isConstructing()); + + callInfo_->setCallee(target); + updateArgumentsFromOperands(); + + WrappedFunction* wrappedTarget = maybeCallTarget(target, CallKind::Scripted); + + bool needsThisCheck = false; + if (callInfo_->constructing()) { + callInfo_->setNewTarget(target); + needsThisCheck = maybeCreateThis(target, flags, CallKind::Scripted); + if (needsThisCheck) { + wrappedTarget = nullptr; + } + } else { + auto* thisv = MLoadFixedSlot::New(alloc(), callee, + BoundFunctionObject::boundThisSlot()); + add(thisv); + callInfo_->thisArg()->setImplicitlyUsedUnchecked(); + callInfo_->setThis(thisv); + } + + bool usingInlineBoundArgs = + numBoundArgs <= BoundFunctionObject::MaxInlineBoundArgs; + + MElements* elements = nullptr; + if (!usingInlineBoundArgs) { + auto* boundArgs = MLoadFixedSlot::New( + alloc(), callee, BoundFunctionObject::firstInlineBoundArgSlot()); + add(boundArgs); + elements = MElements::New(alloc(), boundArgs); + add(elements); + } + + auto loadBoundArg = [&](size_t index) { + MInstruction* arg; + if (usingInlineBoundArgs) { + size_t slot = BoundFunctionObject::firstInlineBoundArgSlot() + index; + arg = MLoadFixedSlot::New(alloc(), callee, slot); + } else { + arg = MLoadElement::New(alloc(), elements, constant(Int32Value(index))); + } + add(arg); + return arg; + }; + if (!callInfo_->prependArgs(numBoundArgs, loadBoundArg)) { + return false; + } + + MCall* call = makeCall(*callInfo_, needsThisCheck, wrappedTarget); + if (!call) { + return false; + } + + if (flags.isSameRealm()) { + call->setNotCrossRealm(); + } + + addEffectful(call); + pushResult(call); + return resumeAfter(call); +} + +bool WarpCacheIRTranspiler::emitBindFunctionResult( + ObjOperandId targetId, uint32_t argc, uint32_t templateObjectOffset) { + MDefinition* target = getOperand(targetId); + JSObject* templateObj = tenuredObjectStubField(templateObjectOffset); + + MOZ_ASSERT(callInfo_->argc() == argc); + + auto* bound = MBindFunction::New(alloc(), target, argc, templateObj); + if (!bound) { + return false; + } + addEffectful(bound); + + for (uint32_t i = 0; i < argc; i++) { + bound->initArg(i, callInfo_->getArg(i)); + } + + pushResult(bound); + return resumeAfter(bound); +} + +bool WarpCacheIRTranspiler::emitSpecializedBindFunctionResult( + ObjOperandId targetId, uint32_t argc, uint32_t templateObjectOffset) { + MDefinition* target = getOperand(targetId); + JSObject* templateObj = tenuredObjectStubField(templateObjectOffset); + + MOZ_ASSERT(callInfo_->argc() == argc); + + auto* bound = MNewBoundFunction::New(alloc(), templateObj); + add(bound); + + size_t numBoundArgs = argc > 0 ? argc - 1 : 0; + MOZ_ASSERT(numBoundArgs <= BoundFunctionObject::MaxInlineBoundArgs); + + auto initSlot = [&](size_t slot, MDefinition* value) { +#ifdef DEBUG + // Assert we can elide the post write barrier. See also the comment in + // WarpBuilder::buildNamedLambdaEnv. + add(MAssertCanElidePostWriteBarrier::New(alloc(), bound, value)); +#endif + addUnchecked(MStoreFixedSlot::NewUnbarriered(alloc(), bound, slot, value)); + }; + + initSlot(BoundFunctionObject::targetSlot(), target); + if (argc > 0) { + initSlot(BoundFunctionObject::boundThisSlot(), callInfo_->getArg(0)); + } + for (size_t i = 0; i < numBoundArgs; i++) { + size_t slot = BoundFunctionObject::firstInlineBoundArgSlot() + i; + initSlot(slot, callInfo_->getArg(1 + i)); + } + + pushResult(bound); + return true; +} + +bool WarpCacheIRTranspiler::emitCallWasmFunction( + ObjOperandId calleeId, Int32OperandId argcId, CallFlags flags, + uint32_t argcFixed, uint32_t funcExportOffset, uint32_t instanceOffset) { + MDefinition* callee = getOperand(calleeId); +#ifdef DEBUG + MDefinition* argc = getOperand(argcId); + MOZ_ASSERT(argc->toConstant()->toInt32() == + static_cast<int32_t>(callInfo_->argc())); +#endif + JSObject* instanceObject = tenuredObjectStubField(instanceOffset); + auto* wasmInstanceObj = &instanceObject->as<WasmInstanceObject>(); + const wasm::FuncExport* funcExport = wasmFuncExportField(funcExportOffset); + const wasm::FuncType& sig = + wasmInstanceObj->instance().metadata().getFuncExportType(*funcExport); + + if (!updateCallInfo(callee, flags)) { + return false; + } + + static_assert(wasm::MaxArgsForJitInlineCall <= MaxNumLInstructionOperands, + "arguments must fit in LIR operands"); + MOZ_ASSERT(sig.args().length() <= wasm::MaxArgsForJitInlineCall); + + MOZ_ASSERT(callInfo_->argFormat() == CallInfo::ArgFormat::Standard); + + auto* call = MIonToWasmCall::New(alloc(), wasmInstanceObj, *funcExport); + if (!call) { + return false; + } + + mozilla::Maybe<MDefinition*> undefined; + for (size_t i = 0; i < sig.args().length(); i++) { + if (!alloc().ensureBallast()) { + return false; + } + + MDefinition* arg; + if (i < callInfo_->argc()) { + arg = callInfo_->getArg(i); + } else { + if (!undefined) { + undefined.emplace(constant(UndefinedValue())); + } + arg = convertWasmArg(*undefined, sig.args()[i].kind()); + } + call->initArg(i, arg); + } + + addEffectful(call); + + // Add any post-function call conversions that are necessary. + MInstruction* postConversion = call; + const wasm::ValTypeVector& results = sig.results(); + MOZ_ASSERT(results.length() <= 1, "Multi-value returns not supported."); + if (results.length() == 0) { + // No results to convert. + } else { + switch (results[0].kind()) { + case wasm::ValType::I64: + // JS expects a BigInt from I64 types. + postConversion = MInt64ToBigInt::New(alloc(), call); + + // Make non-movable so we can attach a resume point. + postConversion->setNotMovable(); + + add(postConversion); + break; + default: + // No spectre.index_masking of i32 results required, as the generated + // stub takes care of that. + break; + } + } + + // The resume point has to be attached to the post-conversion instruction + // (if present) instead of to the call. This way, if the call triggers an + // invalidation bailout, we will have the BigInt value on the Baseline stack. + // Potential alternative solution: attach the resume point to the call and + // have bailouts turn the Int64 value into a BigInt, maybe with a recover + // instruction. + pushResult(postConversion); + return resumeAfterUnchecked(postConversion); +} + +MDefinition* WarpCacheIRTranspiler::convertWasmArg(MDefinition* arg, + wasm::ValType::Kind kind) { + // An invariant in this code is that any type conversion operation that has + // externally visible effects, such as invoking valueOf on an object argument, + // must bailout so that we don't have to worry about replaying effects during + // argument conversion. + MInstruction* conversion = nullptr; + switch (kind) { + case wasm::ValType::I32: + conversion = MTruncateToInt32::New(alloc(), arg); + break; + case wasm::ValType::I64: + conversion = MToInt64::New(alloc(), arg); + break; + case wasm::ValType::F32: + conversion = MToFloat32::New(alloc(), arg); + break; + case wasm::ValType::F64: + conversion = MToDouble::New(alloc(), arg); + break; + case wasm::ValType::V128: + MOZ_CRASH("Unexpected type for Wasm JitEntry"); + case wasm::ValType::Ref: + // Transform the JS representation into an AnyRef representation. + // The resulting type is MIRType::RefOrNull. These cases are all + // effect-free. + switch (arg->type()) { + case MIRType::Object: + conversion = MWasmAnyRefFromJSObject::New(alloc(), arg); + break; + case MIRType::Null: + arg->setImplicitlyUsedUnchecked(); + conversion = MWasmNullConstant::New(alloc()); + break; + default: + conversion = MWasmBoxValue::New(alloc(), arg); + break; + } + break; + } + + add(conversion); + return conversion; +} + +bool WarpCacheIRTranspiler::emitGuardWasmArg(ValOperandId argId, + wasm::ValType::Kind kind) { + MDefinition* arg = getOperand(argId); + MDefinition* conversion = convertWasmArg(arg, kind); + + setOperand(argId, conversion); + return true; +} + +bool WarpCacheIRTranspiler::emitCallGetterResult(CallKind kind, + ValOperandId receiverId, + uint32_t getterOffset, + bool sameRealm, + uint32_t nargsAndFlagsOffset) { + MDefinition* receiver = getOperand(receiverId); + MDefinition* getter = objectStubField(getterOffset); + if (kind == CallKind::Scripted && callInfo_ && callInfo_->isInlined()) { + // We are transpiling to generate the correct guards. We also update the + // CallInfo to use the correct arguments. Code for the inlined getter + // itself will be generated in WarpBuilder::buildInlinedCall. + callInfo_->initForGetterCall(getter, receiver); + callInfo_->setInliningResumeMode(ResumeMode::InlinedAccessor); + + // Make sure there's enough room to push the arguments on the stack. + if (!current->ensureHasSlots(2)) { + return false; + } + + return true; + } + + uint32_t nargsAndFlags = uint32StubField(nargsAndFlagsOffset); + uint16_t nargs = nargsAndFlags >> 16; + FunctionFlags flags = FunctionFlags(uint16_t(nargsAndFlags)); + WrappedFunction* wrappedTarget = + maybeWrappedFunction(getter, kind, nargs, flags); + + bool ignoresRval = loc_.resultIsPopped(); + CallInfo callInfo(alloc(), /* constructing = */ false, ignoresRval); + callInfo.initForGetterCall(getter, receiver); + + MCall* call = makeCall(callInfo, /* needsThisCheck = */ false, wrappedTarget); + if (!call) { + return false; + } + + if (sameRealm) { + call->setNotCrossRealm(); + } + + addEffectful(call); + pushResult(call); + + return resumeAfter(call); +} + +bool WarpCacheIRTranspiler::emitCallScriptedGetterResult( + ValOperandId receiverId, uint32_t getterOffset, bool sameRealm, + uint32_t nargsAndFlagsOffset) { + return emitCallGetterResult(CallKind::Scripted, receiverId, getterOffset, + sameRealm, nargsAndFlagsOffset); +} + +bool WarpCacheIRTranspiler::emitCallInlinedGetterResult( + ValOperandId receiverId, uint32_t getterOffset, uint32_t icScriptOffset, + bool sameRealm, uint32_t nargsAndFlagsOffset) { + return emitCallGetterResult(CallKind::Scripted, receiverId, getterOffset, + sameRealm, nargsAndFlagsOffset); +} + +bool WarpCacheIRTranspiler::emitCallNativeGetterResult( + ValOperandId receiverId, uint32_t getterOffset, bool sameRealm, + uint32_t nargsAndFlagsOffset) { + return emitCallGetterResult(CallKind::Native, receiverId, getterOffset, + sameRealm, nargsAndFlagsOffset); +} + +bool WarpCacheIRTranspiler::emitCallSetter(CallKind kind, + ObjOperandId receiverId, + uint32_t setterOffset, + ValOperandId rhsId, bool sameRealm, + uint32_t nargsAndFlagsOffset) { + MDefinition* receiver = getOperand(receiverId); + MDefinition* setter = objectStubField(setterOffset); + MDefinition* rhs = getOperand(rhsId); + if (kind == CallKind::Scripted && callInfo_ && callInfo_->isInlined()) { + // We are transpiling to generate the correct guards. We also update the + // CallInfo to use the correct arguments. Code for the inlined setter + // itself will be generated in WarpBuilder::buildInlinedCall. + callInfo_->initForSetterCall(setter, receiver, rhs); + callInfo_->setInliningResumeMode(ResumeMode::InlinedAccessor); + + // Make sure there's enough room to push the arguments on the stack. + if (!current->ensureHasSlots(3)) { + return false; + } + + return true; + } + + uint32_t nargsAndFlags = uint32StubField(nargsAndFlagsOffset); + uint16_t nargs = nargsAndFlags >> 16; + FunctionFlags flags = FunctionFlags(uint16_t(nargsAndFlags)); + WrappedFunction* wrappedTarget = + maybeWrappedFunction(setter, kind, nargs, flags); + + CallInfo callInfo(alloc(), /* constructing = */ false, + /* ignoresReturnValue = */ true); + callInfo.initForSetterCall(setter, receiver, rhs); + + MCall* call = makeCall(callInfo, /* needsThisCheck = */ false, wrappedTarget); + if (!call) { + return false; + } + + if (sameRealm) { + call->setNotCrossRealm(); + } + + addEffectful(call); + return resumeAfter(call); +} + +bool WarpCacheIRTranspiler::emitCallScriptedSetter( + ObjOperandId receiverId, uint32_t setterOffset, ValOperandId rhsId, + bool sameRealm, uint32_t nargsAndFlagsOffset) { + return emitCallSetter(CallKind::Scripted, receiverId, setterOffset, rhsId, + sameRealm, nargsAndFlagsOffset); +} + +bool WarpCacheIRTranspiler::emitCallInlinedSetter( + ObjOperandId receiverId, uint32_t setterOffset, ValOperandId rhsId, + uint32_t icScriptOffset, bool sameRealm, uint32_t nargsAndFlagsOffset) { + return emitCallSetter(CallKind::Scripted, receiverId, setterOffset, rhsId, + sameRealm, nargsAndFlagsOffset); +} + +bool WarpCacheIRTranspiler::emitCallNativeSetter(ObjOperandId receiverId, + uint32_t setterOffset, + ValOperandId rhsId, + bool sameRealm, + uint32_t nargsAndFlagsOffset) { + return emitCallSetter(CallKind::Native, receiverId, setterOffset, rhsId, + sameRealm, nargsAndFlagsOffset); +} + +bool WarpCacheIRTranspiler::emitMetaScriptedThisShape( + uint32_t thisShapeOffset) { + SharedShape* shape = &shapeStubField(thisShapeOffset)->asShared(); + MOZ_ASSERT(shape->getObjectClass() == &PlainObject::class_); + + MConstant* shapeConst = MConstant::NewShape(alloc(), shape); + add(shapeConst); + + // TODO: support pre-tenuring. + gc::Heap heap = gc::Heap::Default; + + uint32_t numFixedSlots = shape->numFixedSlots(); + uint32_t numDynamicSlots = NativeObject::calculateDynamicSlots(shape); + gc::AllocKind kind = gc::GetGCObjectKind(numFixedSlots); + MOZ_ASSERT(gc::CanChangeToBackgroundAllocKind(kind, &PlainObject::class_)); + kind = gc::ForegroundToBackgroundAllocKind(kind); + + auto* createThis = MNewPlainObject::New(alloc(), shapeConst, numFixedSlots, + numDynamicSlots, kind, heap); + add(createThis); + + callInfo_->thisArg()->setImplicitlyUsedUnchecked(); + callInfo_->setThis(createThis); + return true; +} + +bool WarpCacheIRTranspiler::emitReturnFromIC() { return true; } + +bool WarpCacheIRTranspiler::emitBailout() { + auto* bail = MBail::New(alloc()); + add(bail); + + return true; +} + +bool WarpCacheIRTranspiler::emitAssertPropertyLookup(ObjOperandId objId, + uint32_t idOffset, + uint32_t slotOffset) { + // We currently only emit checks in baseline. + return true; +} + +bool WarpCacheIRTranspiler::emitAssertRecoveredOnBailoutResult( + ValOperandId valId, bool mustBeRecovered) { + MDefinition* val = getOperand(valId); + + // Don't assert for recovered instructions when recovering is disabled. + if (JitOptions.disableRecoverIns) { + pushResult(constant(UndefinedValue())); + return true; + } + + if (JitOptions.checkRangeAnalysis) { + // If we are checking the range of all instructions, then the guards + // inserted by Range Analysis prevent the use of recover instruction. Thus, + // we just disable these checks. + pushResult(constant(UndefinedValue())); + return true; + } + + auto* assert = MAssertRecoveredOnBailout::New(alloc(), val, mustBeRecovered); + addEffectfulUnsafe(assert); + current->push(assert); + + // Create an instruction sequence which implies that the argument of the + // assertRecoveredOnBailout function would be encoded at least in one + // Snapshot. + auto* nop = MNop::New(alloc()); + add(nop); + + auto* resumePoint = MResumePoint::New( + alloc(), nop->block(), loc_.toRawBytecode(), ResumeMode::ResumeAfter); + if (!resumePoint) { + return false; + } + nop->setResumePoint(resumePoint); + + auto* encode = MEncodeSnapshot::New(alloc()); + addEffectfulUnsafe(encode); + + current->pop(); + + pushResult(constant(UndefinedValue())); + return true; +} + +bool WarpCacheIRTranspiler::emitGuardNoAllocationMetadataBuilder( + uint32_t builderAddrOffset) { + // This is a no-op because we discard all JIT code when set an allocation + // metadata callback. + return true; +} + +bool WarpCacheIRTranspiler::emitNewPlainObjectResult(uint32_t numFixedSlots, + uint32_t numDynamicSlots, + gc::AllocKind allocKind, + uint32_t shapeOffset, + uint32_t siteOffset) { + Shape* shape = shapeStubField(shapeOffset); + gc::Heap heap = allocSiteInitialHeapField(siteOffset); + + auto* shapeConstant = MConstant::NewShape(alloc(), shape); + add(shapeConstant); + + auto* obj = MNewPlainObject::New(alloc(), shapeConstant, numFixedSlots, + numDynamicSlots, allocKind, heap); + add(obj); + + pushResult(obj); + return true; +} + +bool WarpCacheIRTranspiler::emitNewArrayObjectResult(uint32_t length, + uint32_t shapeOffset, + uint32_t siteOffset) { + Shape* shape = shapeStubField(shapeOffset); + gc::Heap heap = allocSiteInitialHeapField(siteOffset); + + auto* shapeConstant = MConstant::NewShape(alloc(), shape); + add(shapeConstant); + + auto* obj = MNewArrayObject::New(alloc(), shapeConstant, length, heap); + add(obj); + + pushResult(obj); + return true; +} + +bool WarpCacheIRTranspiler::emitCloseIterScriptedResult(ObjOperandId iterId, + ObjOperandId calleeId, + CompletionKind kind, + uint32_t calleeNargs) { + MDefinition* iter = getOperand(iterId); + MDefinition* callee = getOperand(calleeId); + + WrappedFunction* wrappedTarget = maybeCallTarget(callee, CallKind::Scripted); + MOZ_ASSERT(wrappedTarget); + MOZ_ASSERT(wrappedTarget->nargs() == calleeNargs); + MOZ_ASSERT(wrappedTarget->hasJitEntry()); + + bool constructing = false; + bool ignoresRval = false; + bool needsThisCheck = false; + bool isDOMCall = false; + CallInfo callInfo(alloc(), constructing, ignoresRval); + callInfo.initForCloseIter(iter, callee); + MCall* call = makeCall(callInfo, needsThisCheck, wrappedTarget, isDOMCall); + if (!call) { + return false; + } + addEffectful(call); + if (kind == CompletionKind::Throw) { + return resumeAfter(call); + } + + // If we bail out here, after the call but before the CheckIsObj, we + // can't simply resume in the baseline interpreter. If we resume + // after the CloseIter, we won't check the return value. If we + // resume at the CloseIter, we will call the |return| method twice. + // Instead, we use a special resume mode that captures the + // intermediate value, and then checks that it's an object while + // bailing out. + current->push(call); + MResumePoint* resumePoint = + MResumePoint::New(alloc(), current, loc_.toRawBytecode(), + ResumeMode::ResumeAfterCheckIsObject); + if (!resumePoint) { + return false; + } + call->setResumePoint(resumePoint); + current->pop(); + + MCheckIsObj* check = MCheckIsObj::New( + alloc(), call, uint8_t(CheckIsObjectKind::IteratorReturn)); + add(check); + + return true; +} + +bool WarpCacheIRTranspiler::emitGuardGlobalGeneration( + uint32_t expectedOffset, uint32_t generationAddrOffset) { + uint32_t expected = uint32StubField(expectedOffset); + const void* generationAddr = rawPointerField(generationAddrOffset); + + auto guard = MGuardGlobalGeneration::New(alloc(), expected, generationAddr); + add(guard); + + return true; +} + +#ifdef FUZZING_JS_FUZZILLI +bool WarpCacheIRTranspiler::emitFuzzilliHashResult(ValOperandId valId) { + MDefinition* input = getOperand(valId); + + auto* hash = MFuzzilliHash::New(alloc(), input); + add(hash); + + auto* store = MFuzzilliHashStore::New(alloc(), hash); + addEffectful(store); + pushResult(constant(UndefinedValue())); + + return resumeAfter(store); +} +#endif + +static void MaybeSetImplicitlyUsed(uint32_t numInstructionIdsBefore, + MDefinition* input) { + // When building MIR from bytecode, for each MDefinition that's an operand to + // a bytecode instruction, we must either add an SSA use or set the + // ImplicitlyUsed flag on that definition. The ImplicitlyUsed flag prevents + // the backend from optimizing-out values that will be used by Baseline after + // a bailout. + // + // WarpBuilder uses WarpPoppedValueUseChecker to assert this invariant in + // debug builds. + // + // This function is responsible for setting the ImplicitlyUsed flag for an + // input when using the transpiler. It looks at the input's most recent use + // and if that's an instruction that was added while transpiling this JSOp + // (based on the MIR instruction id) we don't set the ImplicitlyUsed flag. + + if (input->isImplicitlyUsed()) { + // Nothing to do. + return; + } + + // If the most recent use of 'input' is an instruction we just added, there is + // nothing to do. + MDefinition* inputUse = input->maybeMostRecentlyAddedDefUse(); + if (inputUse && inputUse->id() >= numInstructionIdsBefore) { + return; + } + + // The transpiler didn't add a use for 'input'. + input->setImplicitlyUsed(); +} + +bool jit::TranspileCacheIRToMIR(WarpBuilder* builder, BytecodeLocation loc, + const WarpCacheIR* cacheIRSnapshot, + std::initializer_list<MDefinition*> inputs, + CallInfo* maybeCallInfo) { + uint32_t numInstructionIdsBefore = + builder->mirGen().graph().getNumInstructionIds(); + + WarpCacheIRTranspiler transpiler(builder, loc, maybeCallInfo, + cacheIRSnapshot); + if (!transpiler.transpile(inputs)) { + return false; + } + + for (MDefinition* input : inputs) { + MaybeSetImplicitlyUsed(numInstructionIdsBefore, input); + } + + if (maybeCallInfo) { + auto maybeSetFlag = [numInstructionIdsBefore](MDefinition* def) { + MaybeSetImplicitlyUsed(numInstructionIdsBefore, def); + }; + maybeCallInfo->forEachCallOperand(maybeSetFlag); + } + + return true; +} |