diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /js/src/jit/MIR.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/jit/MIR.cpp')
-rw-r--r-- | js/src/jit/MIR.cpp | 6642 |
1 files changed, 6642 insertions, 0 deletions
diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp new file mode 100644 index 000000000..287b87582 --- /dev/null +++ b/js/src/jit/MIR.cpp @@ -0,0 +1,6642 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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/MIR.h" + +#include "mozilla/CheckedInt.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/SizePrintfMacros.h" + +#include <ctype.h> + +#include "jslibmath.h" +#include "jsstr.h" + +#include "jit/AtomicOperations.h" +#include "jit/BaselineInspector.h" +#include "jit/IonBuilder.h" +#include "jit/JitSpewer.h" +#include "jit/MIRGraph.h" +#include "jit/RangeAnalysis.h" +#include "js/Conversions.h" + +#include "jsatominlines.h" +#include "jsboolinlines.h" +#include "jsobjinlines.h" +#include "jsscriptinlines.h" + +using namespace js; +using namespace js::jit; + +using JS::ToInt32; + +using mozilla::CheckedInt; +using mozilla::NumbersAreIdentical; +using mozilla::IsFloat32Representable; +using mozilla::IsNaN; +using mozilla::IsPowerOfTwo; +using mozilla::Maybe; +using mozilla::DebugOnly; + +#ifdef DEBUG +size_t MUse::index() const +{ + return consumer()->indexOf(this); +} +#endif + +template<size_t Op> static void +ConvertDefinitionToDouble(TempAllocator& alloc, MDefinition* def, MInstruction* consumer) +{ + MInstruction* replace = MToDouble::New(alloc, def); + consumer->replaceOperand(Op, replace); + consumer->block()->insertBefore(consumer, replace); +} + +static bool +CheckUsesAreFloat32Consumers(const MInstruction* ins) +{ + bool allConsumerUses = true; + for (MUseDefIterator use(ins); allConsumerUses && use; use++) + allConsumerUses &= use.def()->canConsumeFloat32(use.use()); + return allConsumerUses; +} + +void +MDefinition::PrintOpcodeName(GenericPrinter& out, MDefinition::Opcode op) +{ + static const char * const names[] = + { +#define NAME(x) #x, + MIR_OPCODE_LIST(NAME) +#undef NAME + }; + const char* name = names[op]; + size_t len = strlen(name); + for (size_t i = 0; i < len; i++) + out.printf("%c", tolower(name[i])); +} + +static MConstant* +EvaluateConstantOperands(TempAllocator& alloc, MBinaryInstruction* ins, bool* ptypeChange = nullptr) +{ + MDefinition* left = ins->getOperand(0); + MDefinition* right = ins->getOperand(1); + + MOZ_ASSERT(IsTypeRepresentableAsDouble(left->type())); + MOZ_ASSERT(IsTypeRepresentableAsDouble(right->type())); + + if (!left->isConstant() || !right->isConstant()) + return nullptr; + + MConstant* lhs = left->toConstant(); + MConstant* rhs = right->toConstant(); + double ret = JS::GenericNaN(); + + switch (ins->op()) { + case MDefinition::Op_BitAnd: + ret = double(lhs->toInt32() & rhs->toInt32()); + break; + case MDefinition::Op_BitOr: + ret = double(lhs->toInt32() | rhs->toInt32()); + break; + case MDefinition::Op_BitXor: + ret = double(lhs->toInt32() ^ rhs->toInt32()); + break; + case MDefinition::Op_Lsh: + ret = double(uint32_t(lhs->toInt32()) << (rhs->toInt32() & 0x1F)); + break; + case MDefinition::Op_Rsh: + ret = double(lhs->toInt32() >> (rhs->toInt32() & 0x1F)); + break; + case MDefinition::Op_Ursh: + ret = double(uint32_t(lhs->toInt32()) >> (rhs->toInt32() & 0x1F)); + break; + case MDefinition::Op_Add: + ret = lhs->numberToDouble() + rhs->numberToDouble(); + break; + case MDefinition::Op_Sub: + ret = lhs->numberToDouble() - rhs->numberToDouble(); + break; + case MDefinition::Op_Mul: + ret = lhs->numberToDouble() * rhs->numberToDouble(); + break; + case MDefinition::Op_Div: + if (ins->toDiv()->isUnsigned()) { + if (rhs->isInt32(0)) { + if (ins->toDiv()->trapOnError()) + return nullptr; + ret = 0.0; + } else { + ret = double(uint32_t(lhs->toInt32()) / uint32_t(rhs->toInt32())); + } + } else { + ret = NumberDiv(lhs->numberToDouble(), rhs->numberToDouble()); + } + break; + case MDefinition::Op_Mod: + if (ins->toMod()->isUnsigned()) { + if (rhs->isInt32(0)) { + if (ins->toMod()->trapOnError()) + return nullptr; + ret = 0.0; + } else { + ret = double(uint32_t(lhs->toInt32()) % uint32_t(rhs->toInt32())); + } + } else { + ret = NumberMod(lhs->numberToDouble(), rhs->numberToDouble()); + } + break; + default: + MOZ_CRASH("NYI"); + } + + // For a float32 or double value, use the Raw* New so that we preserve NaN + // bits. This isn't strictly required for either ES or wasm, but it does + // avoid making constant-folding observable. + if (ins->type() == MIRType::Double) + return MConstant::New(alloc, wasm::RawF64(ret)); + if (ins->type() == MIRType::Float32) + return MConstant::New(alloc, wasm::RawF32(float(ret))); + + Value retVal; + retVal.setNumber(JS::CanonicalizeNaN(ret)); + + // If this was an int32 operation but the result isn't an int32 (for + // example, a division where the numerator isn't evenly divisible by the + // denominator), decline folding. + MOZ_ASSERT(ins->type() == MIRType::Int32); + if (!retVal.isInt32()) { + if (ptypeChange) + *ptypeChange = true; + return nullptr; + } + + return MConstant::New(alloc, retVal); +} + +static MMul* +EvaluateExactReciprocal(TempAllocator& alloc, MDiv* ins) +{ + // we should fold only when it is a floating point operation + if (!IsFloatingPointType(ins->type())) + return nullptr; + + MDefinition* left = ins->getOperand(0); + MDefinition* right = ins->getOperand(1); + + if (!right->isConstant()) + return nullptr; + + int32_t num; + if (!mozilla::NumberIsInt32(right->toConstant()->numberToDouble(), &num)) + return nullptr; + + // check if rhs is a power of two + if (mozilla::Abs(num) & (mozilla::Abs(num) - 1)) + return nullptr; + + Value ret; + ret.setDouble(1.0 / double(num)); + + MConstant* foldedRhs; + if (ins->type() == MIRType::Float32) + foldedRhs = MConstant::NewFloat32(alloc, ret.toDouble()); + else + foldedRhs = MConstant::New(alloc, ret); + + MOZ_ASSERT(foldedRhs->type() == ins->type()); + ins->block()->insertBefore(ins, foldedRhs); + + MMul* mul = MMul::New(alloc, left, foldedRhs, ins->type()); + mul->setCommutative(); + mul->setMustPreserveNaN(ins->mustPreserveNaN()); + return mul; +} + +void +MDefinition::printName(GenericPrinter& out) const +{ + PrintOpcodeName(out, op()); + out.printf("%u", id()); +} + +HashNumber +MDefinition::addU32ToHash(HashNumber hash, uint32_t data) +{ + return data + (hash << 6) + (hash << 16) - hash; +} + +HashNumber +MDefinition::valueHash() const +{ + HashNumber out = op(); + for (size_t i = 0, e = numOperands(); i < e; i++) + out = addU32ToHash(out, getOperand(i)->id()); + if (MDefinition* dep = dependency()) + out = addU32ToHash(out, dep->id()); + return out; +} + +bool +MDefinition::congruentIfOperandsEqual(const MDefinition* ins) const +{ + if (op() != ins->op()) + return false; + + if (type() != ins->type()) + return false; + + if (isEffectful() || ins->isEffectful()) + return false; + + if (numOperands() != ins->numOperands()) + return false; + + for (size_t i = 0, e = numOperands(); i < e; i++) { + if (getOperand(i) != ins->getOperand(i)) + return false; + } + + return true; +} + +MDefinition* +MDefinition::foldsTo(TempAllocator& alloc) +{ + // In the default case, there are no constants to fold. + return this; +} + +bool +MDefinition::mightBeMagicType() const +{ + if (IsMagicType(type())) + return true; + + if (MIRType::Value != type()) + return false; + + return !resultTypeSet() || resultTypeSet()->hasType(TypeSet::MagicArgType()); +} + +MDefinition* +MInstruction::foldsToStore(TempAllocator& alloc) +{ + if (!dependency()) + return nullptr; + + MDefinition* store = dependency(); + if (mightAlias(store) != AliasType::MustAlias) + return nullptr; + + if (!store->block()->dominates(block())) + return nullptr; + + MDefinition* value; + switch (store->op()) { + case Op_StoreFixedSlot: + value = store->toStoreFixedSlot()->value(); + break; + case Op_StoreSlot: + value = store->toStoreSlot()->value(); + break; + case Op_StoreElement: + value = store->toStoreElement()->value(); + break; + case Op_StoreUnboxedObjectOrNull: + value = store->toStoreUnboxedObjectOrNull()->value(); + break; + default: + MOZ_CRASH("unknown store"); + } + + // If the type are matching then we return the value which is used as + // argument of the store. + if (value->type() != type()) { + // If we expect to read a type which is more generic than the type seen + // by the store, then we box the value used by the store. + if (type() != MIRType::Value) + return nullptr; + // We cannot unbox ObjectOrNull yet. + if (value->type() == MIRType::ObjectOrNull) + return nullptr; + + MOZ_ASSERT(value->type() < MIRType::Value); + MBox* box = MBox::New(alloc, value); + value = box; + } + + return value; +} + +void +MDefinition::analyzeEdgeCasesForward() +{ +} + +void +MDefinition::analyzeEdgeCasesBackward() +{ +} + +void +MInstruction::setResumePoint(MResumePoint* resumePoint) +{ + MOZ_ASSERT(!resumePoint_); + resumePoint_ = resumePoint; + resumePoint_->setInstruction(this); +} + +void +MInstruction::stealResumePoint(MInstruction* ins) +{ + MOZ_ASSERT(ins->resumePoint_->instruction() == ins); + resumePoint_ = ins->resumePoint_; + ins->resumePoint_ = nullptr; + resumePoint_->replaceInstruction(this); +} + +void +MInstruction::moveResumePointAsEntry() +{ + MOZ_ASSERT(isNop()); + block()->clearEntryResumePoint(); + block()->setEntryResumePoint(resumePoint_); + resumePoint_->resetInstruction(); + resumePoint_ = nullptr; +} + +void +MInstruction::clearResumePoint() +{ + resumePoint_->resetInstruction(); + block()->discardPreAllocatedResumePoint(resumePoint_); + resumePoint_ = nullptr; +} + +bool +MDefinition::maybeEmulatesUndefined(CompilerConstraintList* constraints) +{ + if (!mightBeType(MIRType::Object)) + return false; + + TemporaryTypeSet* types = resultTypeSet(); + if (!types) + return true; + + return types->maybeEmulatesUndefined(constraints); +} + +static bool +MaybeCallable(CompilerConstraintList* constraints, MDefinition* op) +{ + if (!op->mightBeType(MIRType::Object)) + return false; + + TemporaryTypeSet* types = op->resultTypeSet(); + if (!types) + return true; + + return types->maybeCallable(constraints); +} + +/* static */ const char* +AliasSet::Name(size_t flag) +{ + switch(flag) { + case 0: return "ObjectFields"; + case 1: return "Element"; + case 2: return "UnboxedElement"; + case 3: return "DynamicSlot"; + case 4: return "FixedSlot"; + case 5: return "DOMProperty"; + case 6: return "FrameArgument"; + case 7: return "WasmGlobalVar"; + case 8: return "WasmHeap"; + case 9: return "TypedArrayLength"; + default: + MOZ_CRASH("Unknown flag"); + } +} + +void +MTest::cacheOperandMightEmulateUndefined(CompilerConstraintList* constraints) +{ + MOZ_ASSERT(operandMightEmulateUndefined()); + + if (!getOperand(0)->maybeEmulatesUndefined(constraints)) + markNoOperandEmulatesUndefined(); +} + +MDefinition* +MTest::foldsDoubleNegation(TempAllocator& alloc) +{ + MDefinition* op = getOperand(0); + + if (op->isNot()) { + // If the operand of the Not is itself a Not, they cancel out. + MDefinition* opop = op->getOperand(0); + if (opop->isNot()) + return MTest::New(alloc, opop->toNot()->input(), ifTrue(), ifFalse()); + return MTest::New(alloc, op->toNot()->input(), ifFalse(), ifTrue()); + } + return nullptr; +} + +MDefinition* +MTest::foldsConstant(TempAllocator& alloc) +{ + MDefinition* op = getOperand(0); + if (MConstant* opConst = op->maybeConstantValue()) { + bool b; + if (opConst->valueToBoolean(&b)) + return MGoto::New(alloc, b ? ifTrue() : ifFalse()); + } + return nullptr; +} + +MDefinition* +MTest::foldsTypes(TempAllocator& alloc) +{ + MDefinition* op = getOperand(0); + + switch (op->type()) { + case MIRType::Undefined: + case MIRType::Null: + return MGoto::New(alloc, ifFalse()); + case MIRType::Symbol: + return MGoto::New(alloc, ifTrue()); + case MIRType::Object: + if (!operandMightEmulateUndefined()) + return MGoto::New(alloc, ifTrue()); + break; + default: + break; + } + return nullptr; +} + +MDefinition* +MTest::foldsNeedlessControlFlow(TempAllocator& alloc) +{ + for (MInstructionIterator iter(ifTrue()->begin()), end(ifTrue()->end()); iter != end; ) { + MInstruction* ins = *iter++; + if (ins->isNop() || ins->isGoto()) + continue; + if (ins->hasUses()) + return nullptr; + if (!DeadIfUnused(ins)) + return nullptr; + } + + for (MInstructionIterator iter(ifFalse()->begin()), end(ifFalse()->end()); iter != end; ) { + MInstruction* ins = *iter++; + if (ins->isNop() || ins->isGoto()) + continue; + if (ins->hasUses()) + return nullptr; + if (!DeadIfUnused(ins)) + return nullptr; + } + + if (ifTrue()->numSuccessors() != 1 || ifFalse()->numSuccessors() != 1) + return nullptr; + if (ifTrue()->getSuccessor(0) != ifFalse()->getSuccessor(0)) + return nullptr; + + if (ifTrue()->successorWithPhis()) + return nullptr; + + return MGoto::New(alloc, ifTrue()); +} + +MDefinition* +MTest::foldsTo(TempAllocator& alloc) +{ + + if (MDefinition* def = foldsDoubleNegation(alloc)) + return def; + + if (MDefinition* def = foldsConstant(alloc)) + return def; + + if (MDefinition* def = foldsTypes(alloc)) + return def; + + if (MDefinition* def = foldsNeedlessControlFlow(alloc)) + return def; + + return this; +} + +void +MTest::filtersUndefinedOrNull(bool trueBranch, MDefinition** subject, bool* filtersUndefined, + bool* filtersNull) +{ + MDefinition* ins = getOperand(0); + if (ins->isCompare()) { + ins->toCompare()->filtersUndefinedOrNull(trueBranch, subject, filtersUndefined, filtersNull); + return; + } + + if (!trueBranch && ins->isNot()) { + *subject = ins->getOperand(0); + *filtersUndefined = *filtersNull = true; + return; + } + + if (trueBranch) { + *subject = ins; + *filtersUndefined = *filtersNull = true; + return; + } + + *filtersUndefined = *filtersNull = false; + *subject = nullptr; +} + +void +MDefinition::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeName(out, op()); + for (size_t j = 0, e = numOperands(); j < e; j++) { + out.printf(" "); + if (getUseFor(j)->hasProducer()) + getOperand(j)->printName(out); + else + out.printf("(null)"); + } +} + +void +MDefinition::dump(GenericPrinter& out) const +{ + printName(out); + out.printf(" = "); + printOpcode(out); + out.printf("\n"); + + if (isInstruction()) { + if (MResumePoint* resume = toInstruction()->resumePoint()) + resume->dump(out); + } +} + +void +MDefinition::dump() const +{ + Fprinter out(stderr); + dump(out); + out.finish(); +} + +void +MDefinition::dumpLocation(GenericPrinter& out) const +{ + MResumePoint* rp = nullptr; + const char* linkWord = nullptr; + if (isInstruction() && toInstruction()->resumePoint()) { + rp = toInstruction()->resumePoint(); + linkWord = "at"; + } else { + rp = block()->entryResumePoint(); + linkWord = "after"; + } + + while (rp) { + JSScript* script = rp->block()->info().script(); + uint32_t lineno = PCToLineNumber(rp->block()->info().script(), rp->pc()); + out.printf(" %s %s:%d\n", linkWord, script->filename(), lineno); + rp = rp->caller(); + linkWord = "in"; + } +} + +void +MDefinition::dumpLocation() const +{ + Fprinter out(stderr); + dumpLocation(out); + out.finish(); +} + +#if defined(DEBUG) || defined(JS_JITSPEW) +size_t +MDefinition::useCount() const +{ + size_t count = 0; + for (MUseIterator i(uses_.begin()); i != uses_.end(); i++) + count++; + return count; +} + +size_t +MDefinition::defUseCount() const +{ + size_t count = 0; + for (MUseIterator i(uses_.begin()); i != uses_.end(); i++) + if ((*i)->consumer()->isDefinition()) + count++; + return count; +} +#endif + +bool +MDefinition::hasOneUse() const +{ + MUseIterator i(uses_.begin()); + if (i == uses_.end()) + return false; + i++; + return i == uses_.end(); +} + +bool +MDefinition::hasOneDefUse() const +{ + bool hasOneDefUse = false; + for (MUseIterator i(uses_.begin()); i != uses_.end(); i++) { + if (!(*i)->consumer()->isDefinition()) + continue; + + // We already have a definition use. So 1+ + if (hasOneDefUse) + return false; + + // We saw one definition. Loop to test if there is another. + hasOneDefUse = true; + } + + return hasOneDefUse; +} + +bool +MDefinition::hasDefUses() const +{ + for (MUseIterator i(uses_.begin()); i != uses_.end(); i++) { + if ((*i)->consumer()->isDefinition()) + return true; + } + + return false; +} + +bool +MDefinition::hasLiveDefUses() const +{ + for (MUseIterator i(uses_.begin()); i != uses_.end(); i++) { + MNode* ins = (*i)->consumer(); + if (ins->isDefinition()) { + if (!ins->toDefinition()->isRecoveredOnBailout()) + return true; + } else { + MOZ_ASSERT(ins->isResumePoint()); + if (!ins->toResumePoint()->isRecoverableOperand(*i)) + return true; + } + } + + return false; +} + +void +MDefinition::replaceAllUsesWith(MDefinition* dom) +{ + for (size_t i = 0, e = numOperands(); i < e; ++i) + getOperand(i)->setUseRemovedUnchecked(); + + justReplaceAllUsesWith(dom); +} + +void +MDefinition::justReplaceAllUsesWith(MDefinition* dom) +{ + MOZ_ASSERT(dom != nullptr); + MOZ_ASSERT(dom != this); + + // Carry over the fact the value has uses which are no longer inspectable + // with the graph. + if (isUseRemoved()) + dom->setUseRemovedUnchecked(); + + for (MUseIterator i(usesBegin()), e(usesEnd()); i != e; ++i) + i->setProducerUnchecked(dom); + dom->uses_.takeElements(uses_); +} + +void +MDefinition::justReplaceAllUsesWithExcept(MDefinition* dom) +{ + MOZ_ASSERT(dom != nullptr); + MOZ_ASSERT(dom != this); + + // Carry over the fact the value has uses which are no longer inspectable + // with the graph. + if (isUseRemoved()) + dom->setUseRemovedUnchecked(); + + // Move all uses to new dom. Save the use of the dominating instruction. + MUse *exceptUse = nullptr; + for (MUseIterator i(usesBegin()), e(usesEnd()); i != e; ++i) { + if (i->consumer() != dom) { + i->setProducerUnchecked(dom); + } else { + MOZ_ASSERT(!exceptUse); + exceptUse = *i; + } + } + dom->uses_.takeElements(uses_); + + // Restore the use to the original definition. + dom->uses_.remove(exceptUse); + exceptUse->setProducerUnchecked(this); + uses_.pushFront(exceptUse); +} + +bool +MDefinition::optimizeOutAllUses(TempAllocator& alloc) +{ + for (MUseIterator i(usesBegin()), e(usesEnd()); i != e;) { + MUse* use = *i++; + MConstant* constant = use->consumer()->block()->optimizedOutConstant(alloc); + if (!alloc.ensureBallast()) + return false; + + // Update the resume point operand to use the optimized-out constant. + use->setProducerUnchecked(constant); + constant->addUseUnchecked(use); + } + + // Remove dangling pointers. + this->uses_.clear(); + return true; +} + +void +MDefinition::replaceAllLiveUsesWith(MDefinition* dom) +{ + for (MUseIterator i(usesBegin()), e(usesEnd()); i != e; ) { + MUse* use = *i++; + MNode* consumer = use->consumer(); + if (consumer->isResumePoint()) + continue; + if (consumer->isDefinition() && consumer->toDefinition()->isRecoveredOnBailout()) + continue; + + // Update the operand to use the dominating definition. + use->replaceProducer(dom); + } +} + +bool +MDefinition::emptyResultTypeSet() const +{ + return resultTypeSet() && resultTypeSet()->empty(); +} + +MConstant* +MConstant::New(TempAllocator& alloc, const Value& v, CompilerConstraintList* constraints) +{ + return new(alloc) MConstant(v, constraints); +} + +MConstant* +MConstant::New(TempAllocator::Fallible alloc, const Value& v, CompilerConstraintList* constraints) +{ + return new(alloc) MConstant(v, constraints); +} + +MConstant* +MConstant::NewFloat32(TempAllocator& alloc, double d) +{ + MOZ_ASSERT(IsNaN(d) || d == double(float(d))); + return new(alloc) MConstant(float(d)); +} + +MConstant* +MConstant::New(TempAllocator& alloc, wasm::RawF32 f) +{ + auto* c = new(alloc) MConstant(Int32Value(f.bits()), nullptr); + c->setResultType(MIRType::Float32); + return c; +} + +MConstant* +MConstant::New(TempAllocator& alloc, wasm::RawF64 d) +{ + auto* c = new(alloc) MConstant(int64_t(d.bits())); + c->setResultType(MIRType::Double); + return c; +} + +MConstant* +MConstant::NewInt64(TempAllocator& alloc, int64_t i) +{ + return new(alloc) MConstant(i); +} + +MConstant* +MConstant::New(TempAllocator& alloc, const Value& v, MIRType type) +{ + if (type == MIRType::Float32) + return NewFloat32(alloc, v.toNumber()); + MConstant* res = New(alloc, v); + MOZ_ASSERT(res->type() == type); + return res; +} + +MConstant* +MConstant::NewConstraintlessObject(TempAllocator& alloc, JSObject* v) +{ + return new(alloc) MConstant(v); +} + +static TemporaryTypeSet* +MakeSingletonTypeSetFromKey(CompilerConstraintList* constraints, TypeSet::ObjectKey* key) +{ + // Invalidate when this object's ObjectGroup gets unknown properties. This + // happens for instance when we mutate an object's __proto__, in this case + // we want to invalidate and mark this TypeSet as containing AnyObject + // (because mutating __proto__ will change an object's ObjectGroup). + MOZ_ASSERT(constraints); + (void)key->hasStableClassAndProto(constraints); + + LifoAlloc* alloc = GetJitContext()->temp->lifoAlloc(); + return alloc->new_<TemporaryTypeSet>(alloc, TypeSet::ObjectType(key)); +} + +TemporaryTypeSet* +jit::MakeSingletonTypeSet(CompilerConstraintList* constraints, JSObject* obj) +{ + return MakeSingletonTypeSetFromKey(constraints, TypeSet::ObjectKey::get(obj)); +} + +TemporaryTypeSet* +jit::MakeSingletonTypeSet(CompilerConstraintList* constraints, ObjectGroup* obj) +{ + return MakeSingletonTypeSetFromKey(constraints, TypeSet::ObjectKey::get(obj)); +} + +static TemporaryTypeSet* +MakeUnknownTypeSet() +{ + LifoAlloc* alloc = GetJitContext()->temp->lifoAlloc(); + return alloc->new_<TemporaryTypeSet>(alloc, TypeSet::UnknownType()); +} + +#ifdef DEBUG + +bool +jit::IonCompilationCanUseNurseryPointers() +{ + // If we are doing backend compilation, which could occur on a helper + // thread but might actually be on the main thread, check the flag set on + // the PerThreadData by AutoEnterIonCompilation. + if (CurrentThreadIsIonCompiling()) + return !CurrentThreadIsIonCompilingSafeForMinorGC(); + + // Otherwise, we must be on the main thread during MIR construction. The + // store buffer must have been notified that minor GCs must cancel pending + // or in progress Ion compilations. + JSRuntime* rt = TlsPerThreadData.get()->runtimeFromMainThread(); + return rt->gc.storeBuffer.cancelIonCompilations(); +} + +#endif // DEBUG + +MConstant::MConstant(const js::Value& vp, CompilerConstraintList* constraints) +{ + setResultType(MIRTypeFromValue(vp)); + + MOZ_ASSERT(payload_.asBits == 0); + + switch (type()) { + case MIRType::Undefined: + case MIRType::Null: + break; + case MIRType::Boolean: + payload_.b = vp.toBoolean(); + break; + case MIRType::Int32: + payload_.i32 = vp.toInt32(); + break; + case MIRType::Double: + payload_.d = vp.toDouble(); + break; + case MIRType::String: + MOZ_ASSERT(vp.toString()->isAtom()); + payload_.str = vp.toString(); + break; + case MIRType::Symbol: + payload_.sym = vp.toSymbol(); + break; + case MIRType::Object: + payload_.obj = &vp.toObject(); + // Create a singleton type set for the object. This isn't necessary for + // other types as the result type encodes all needed information. + MOZ_ASSERT_IF(IsInsideNursery(&vp.toObject()), IonCompilationCanUseNurseryPointers()); + setResultTypeSet(MakeSingletonTypeSet(constraints, &vp.toObject())); + break; + case MIRType::MagicOptimizedArguments: + case MIRType::MagicOptimizedOut: + case MIRType::MagicHole: + case MIRType::MagicIsConstructing: + break; + case MIRType::MagicUninitializedLexical: + // JS_UNINITIALIZED_LEXICAL does not escape to script and is not + // observed in type sets. However, it may flow around freely during + // Ion compilation. Give it an unknown typeset to poison any type sets + // it merges with. + // + // TODO We could track uninitialized lexicals more precisely by tracking + // them in type sets. + setResultTypeSet(MakeUnknownTypeSet()); + break; + default: + MOZ_CRASH("Unexpected type"); + } + + setMovable(); +} + +MConstant::MConstant(JSObject* obj) +{ + MOZ_ASSERT_IF(IsInsideNursery(obj), IonCompilationCanUseNurseryPointers()); + setResultType(MIRType::Object); + payload_.obj = obj; + setMovable(); +} + +MConstant::MConstant(float f) +{ + setResultType(MIRType::Float32); + payload_.f = f; + setMovable(); +} + +MConstant::MConstant(double d) +{ + setResultType(MIRType::Double); + payload_.d = d; + setMovable(); +} + +MConstant::MConstant(int64_t i) +{ + setResultType(MIRType::Int64); + payload_.i64 = i; + setMovable(); +} + +#ifdef DEBUG +void +MConstant::assertInitializedPayload() const +{ + // valueHash() and equals() expect the unused payload bits to be + // initialized to zero. Assert this in debug builds. + + switch (type()) { + case MIRType::Int32: + case MIRType::Float32: + MOZ_ASSERT((payload_.asBits >> 32) == 0); + break; + case MIRType::Boolean: + MOZ_ASSERT((payload_.asBits >> 1) == 0); + break; + case MIRType::Double: + case MIRType::Int64: + break; + case MIRType::String: + case MIRType::Object: + case MIRType::Symbol: + MOZ_ASSERT_IF(JS_BITS_PER_WORD == 32, (payload_.asBits >> 32) == 0); + break; + default: + MOZ_ASSERT(IsNullOrUndefined(type()) || IsMagicType(type())); + MOZ_ASSERT(payload_.asBits == 0); + break; + } +} +#endif + +HashNumber +MConstant::valueHash() const +{ + static_assert(sizeof(Payload) == sizeof(uint64_t), + "Code below assumes payload fits in 64 bits"); + + assertInitializedPayload(); + + // Build a 64-bit value holding both the payload and the type. + static const size_t TypeBits = 8; + static const size_t TypeShift = 64 - TypeBits; + MOZ_ASSERT(uintptr_t(type()) <= (1 << TypeBits) - 1); + uint64_t bits = (uint64_t(type()) << TypeShift) ^ payload_.asBits; + + // Fold all 64 bits into the 32-bit result. It's tempting to just discard + // half of the bits, as this is just a hash, however there are many common + // patterns of values where only the low or the high bits vary, so + // discarding either side would lead to excessive hash collisions. + return (HashNumber)bits ^ (HashNumber)(bits >> 32); +} + +bool +MConstant::congruentTo(const MDefinition* ins) const +{ + return ins->isConstant() && equals(ins->toConstant()); +} + +void +MConstant::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeName(out, op()); + out.printf(" "); + switch (type()) { + case MIRType::Undefined: + out.printf("undefined"); + break; + case MIRType::Null: + out.printf("null"); + break; + case MIRType::Boolean: + out.printf(toBoolean() ? "true" : "false"); + break; + case MIRType::Int32: + out.printf("0x%x", toInt32()); + break; + case MIRType::Int64: + out.printf("0x%" PRIx64, toInt64()); + break; + case MIRType::Double: + out.printf("%.16g", toDouble()); + break; + case MIRType::Float32: + { + float val = toFloat32(); + out.printf("%.16g", val); + break; + } + case MIRType::Object: + if (toObject().is<JSFunction>()) { + JSFunction* fun = &toObject().as<JSFunction>(); + if (fun->displayAtom()) { + out.put("function "); + EscapedStringPrinter(out, fun->displayAtom(), 0); + } else { + out.put("unnamed function"); + } + if (fun->hasScript()) { + JSScript* script = fun->nonLazyScript(); + out.printf(" (%s:%" PRIuSIZE ")", + script->filename() ? script->filename() : "", script->lineno()); + } + out.printf(" at %p", (void*) fun); + break; + } + out.printf("object %p (%s)", (void*)&toObject(), toObject().getClass()->name); + break; + case MIRType::Symbol: + out.printf("symbol at %p", (void*)toSymbol()); + break; + case MIRType::String: + out.printf("string %p", (void*)toString()); + break; + case MIRType::MagicOptimizedArguments: + out.printf("magic lazyargs"); + break; + case MIRType::MagicHole: + out.printf("magic hole"); + break; + case MIRType::MagicIsConstructing: + out.printf("magic is-constructing"); + break; + case MIRType::MagicOptimizedOut: + out.printf("magic optimized-out"); + break; + case MIRType::MagicUninitializedLexical: + out.printf("magic uninitialized-lexical"); + break; + default: + MOZ_CRASH("unexpected type"); + } +} + +bool +MConstant::canProduceFloat32() const +{ + if (!isTypeRepresentableAsDouble()) + return false; + + if (type() == MIRType::Int32) + return IsFloat32Representable(static_cast<double>(toInt32())); + if (type() == MIRType::Double) + return IsFloat32Representable(toDouble()); + MOZ_ASSERT(type() == MIRType::Float32); + return true; +} + +Value +MConstant::toJSValue() const +{ + // Wasm has types like int64 that cannot be stored as js::Value. It also + // doesn't want the NaN canonicalization enforced by js::Value. + MOZ_ASSERT(!IsCompilingWasm()); + + switch (type()) { + case MIRType::Undefined: + return UndefinedValue(); + case MIRType::Null: + return NullValue(); + case MIRType::Boolean: + return BooleanValue(toBoolean()); + case MIRType::Int32: + return Int32Value(toInt32()); + case MIRType::Double: + return DoubleValue(toDouble()); + case MIRType::Float32: + return Float32Value(toFloat32()); + case MIRType::String: + return StringValue(toString()); + case MIRType::Symbol: + return SymbolValue(toSymbol()); + case MIRType::Object: + return ObjectValue(toObject()); + case MIRType::MagicOptimizedArguments: + return MagicValue(JS_OPTIMIZED_ARGUMENTS); + case MIRType::MagicOptimizedOut: + return MagicValue(JS_OPTIMIZED_OUT); + case MIRType::MagicHole: + return MagicValue(JS_ELEMENTS_HOLE); + case MIRType::MagicIsConstructing: + return MagicValue(JS_IS_CONSTRUCTING); + case MIRType::MagicUninitializedLexical: + return MagicValue(JS_UNINITIALIZED_LEXICAL); + default: + MOZ_CRASH("Unexpected type"); + } +} + +bool +MConstant::valueToBoolean(bool* res) const +{ + switch (type()) { + case MIRType::Boolean: + *res = toBoolean(); + return true; + case MIRType::Int32: + *res = toInt32() != 0; + return true; + case MIRType::Int64: + *res = toInt64() != 0; + return true; + case MIRType::Double: + *res = !mozilla::IsNaN(toDouble()) && toDouble() != 0.0; + return true; + case MIRType::Float32: + *res = !mozilla::IsNaN(toFloat32()) && toFloat32() != 0.0f; + return true; + case MIRType::Null: + case MIRType::Undefined: + *res = false; + return true; + case MIRType::Symbol: + *res = true; + return true; + case MIRType::String: + *res = toString()->length() != 0; + return true; + case MIRType::Object: + *res = !EmulatesUndefined(&toObject()); + return true; + default: + MOZ_ASSERT(IsMagicType(type())); + return false; + } +} + +MDefinition* +MSimdValueX4::foldsTo(TempAllocator& alloc) +{ +#ifdef DEBUG + MIRType laneType = SimdTypeToLaneArgumentType(type()); +#endif + bool allConstants = true; + bool allSame = true; + + for (size_t i = 0; i < 4; ++i) { + MDefinition* op = getOperand(i); + MOZ_ASSERT(op->type() == laneType); + if (!op->isConstant()) + allConstants = false; + if (i > 0 && op != getOperand(i - 1)) + allSame = false; + } + + if (!allConstants && !allSame) + return this; + + if (allConstants) { + SimdConstant cst; + switch (type()) { + case MIRType::Bool32x4: { + int32_t a[4]; + for (size_t i = 0; i < 4; ++i) + a[i] = getOperand(i)->toConstant()->valueToBooleanInfallible() ? -1 : 0; + cst = SimdConstant::CreateX4(a); + break; + } + case MIRType::Int32x4: { + int32_t a[4]; + for (size_t i = 0; i < 4; ++i) + a[i] = getOperand(i)->toConstant()->toInt32(); + cst = SimdConstant::CreateX4(a); + break; + } + case MIRType::Float32x4: { + float a[4]; + for (size_t i = 0; i < 4; ++i) + a[i] = getOperand(i)->toConstant()->numberToDouble(); + cst = SimdConstant::CreateX4(a); + break; + } + default: MOZ_CRASH("unexpected type in MSimdValueX4::foldsTo"); + } + + return MSimdConstant::New(alloc, cst, type()); + } + + MOZ_ASSERT(allSame); + return MSimdSplat::New(alloc, getOperand(0), type()); +} + +MDefinition* +MSimdSplat::foldsTo(TempAllocator& alloc) +{ +#ifdef DEBUG + MIRType laneType = SimdTypeToLaneArgumentType(type()); +#endif + MDefinition* op = getOperand(0); + if (!op->isConstant()) + return this; + MOZ_ASSERT(op->type() == laneType); + + SimdConstant cst; + switch (type()) { + case MIRType::Bool8x16: { + int8_t v = op->toConstant()->valueToBooleanInfallible() ? -1 : 0; + cst = SimdConstant::SplatX16(v); + break; + } + case MIRType::Bool16x8: { + int16_t v = op->toConstant()->valueToBooleanInfallible() ? -1 : 0; + cst = SimdConstant::SplatX8(v); + break; + } + case MIRType::Bool32x4: { + int32_t v = op->toConstant()->valueToBooleanInfallible() ? -1 : 0; + cst = SimdConstant::SplatX4(v); + break; + } + case MIRType::Int8x16: { + int32_t v = op->toConstant()->toInt32(); + cst = SimdConstant::SplatX16(v); + break; + } + case MIRType::Int16x8: { + int32_t v = op->toConstant()->toInt32(); + cst = SimdConstant::SplatX8(v); + break; + } + case MIRType::Int32x4: { + int32_t v = op->toConstant()->toInt32(); + cst = SimdConstant::SplatX4(v); + break; + } + case MIRType::Float32x4: { + float v = op->toConstant()->numberToDouble(); + cst = SimdConstant::SplatX4(v); + break; + } + default: MOZ_CRASH("unexpected type in MSimdSplat::foldsTo"); + } + + return MSimdConstant::New(alloc, cst, type()); +} + +MDefinition* +MSimdUnbox::foldsTo(TempAllocator& alloc) +{ + MDefinition* in = input(); + + if (in->isSimdBox()) { + MSimdBox* box = in->toSimdBox(); + // If the operand is a MSimdBox, then we just reuse the operand of the + // MSimdBox as long as the type corresponds to what we are supposed to + // unbox. + in = box->input(); + if (box->simdType() != simdType()) + return this; + MOZ_ASSERT(in->type() == type()); + return in; + } + + return this; +} + +MDefinition* +MSimdSwizzle::foldsTo(TempAllocator& alloc) +{ + if (lanesMatch(0, 1, 2, 3)) + return input(); + return this; +} + +MDefinition* +MSimdGeneralShuffle::foldsTo(TempAllocator& alloc) +{ + FixedList<uint8_t> lanes; + if (!lanes.init(alloc, numLanes())) + return this; + + for (size_t i = 0; i < numLanes(); i++) { + if (!lane(i)->isConstant() || lane(i)->type() != MIRType::Int32) + return this; + int32_t temp = lane(i)->toConstant()->toInt32(); + if (temp < 0 || unsigned(temp) >= numLanes() * numVectors()) + return this; + lanes[i] = uint8_t(temp); + } + + if (numVectors() == 1) + return MSimdSwizzle::New(alloc, vector(0), lanes.data()); + + MOZ_ASSERT(numVectors() == 2); + return MSimdShuffle::New(alloc, vector(0), vector(1), lanes.data()); +} + +MInstruction* +MSimdConvert::AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* obj, + MIRType toType, SimdSign sign, wasm::TrapOffset trapOffset) +{ + MIRType fromType = obj->type(); + + if (SupportsUint32x4FloatConversions || sign != SimdSign::Unsigned) { + MInstruction* ins = New(alloc, obj, toType, sign, trapOffset); + addTo->add(ins); + return ins; + } + + // This architecture can't do Uint32x4 <-> Float32x4 conversions (Hi SSE!) + MOZ_ASSERT(sign == SimdSign::Unsigned); + if (fromType == MIRType::Int32x4 && toType == MIRType::Float32x4) { + // Converting Uint32x4 -> Float32x4. This algorithm is from LLVM. + // + // Split the input number into high and low parts: + // + // uint32_t hi = x >> 16; + // uint32_t lo = x & 0xffff; + // + // Insert these parts as the low mantissa bits in a float32 number with + // the corresponding exponent: + // + // float fhi = (bits-as-float)(hi | 0x53000000); // 0x1.0p39f + hi*2^16 + // float flo = (bits-as-float)(lo | 0x4b000000); // 0x1.0p23f + lo + // + // Subtract the bias from the hi part: + // + // fhi -= (0x1.0p39 + 0x1.0p23) // hi*2^16 - 0x1.0p23 + // + // And finally combine: + // + // result = flo + fhi // lo + hi*2^16. + + // Compute hi = obj >> 16 (lane-wise unsigned shift). + MInstruction* c16 = MConstant::New(alloc, Int32Value(16)); + addTo->add(c16); + MInstruction* hi = MSimdShift::AddLegalized(alloc, addTo, obj, c16, MSimdShift::ursh); + + // Compute lo = obj & 0xffff (lane-wise). + MInstruction* m16 = + MSimdConstant::New(alloc, SimdConstant::SplatX4(0xffff), MIRType::Int32x4); + addTo->add(m16); + MInstruction* lo = MSimdBinaryBitwise::New(alloc, obj, m16, MSimdBinaryBitwise::and_); + addTo->add(lo); + + // Mix in the exponents. + MInstruction* exphi = + MSimdConstant::New(alloc, SimdConstant::SplatX4(0x53000000), MIRType::Int32x4); + addTo->add(exphi); + MInstruction* mhi = MSimdBinaryBitwise::New(alloc, hi, exphi, MSimdBinaryBitwise::or_); + addTo->add(mhi); + MInstruction* explo = + MSimdConstant::New(alloc, SimdConstant::SplatX4(0x4b000000), MIRType::Int32x4); + addTo->add(explo); + MInstruction* mlo = MSimdBinaryBitwise::New(alloc, lo, explo, MSimdBinaryBitwise::or_); + addTo->add(mlo); + + // Bit-cast both to Float32x4. + MInstruction* fhi = MSimdReinterpretCast::New(alloc, mhi, MIRType::Float32x4); + addTo->add(fhi); + MInstruction* flo = MSimdReinterpretCast::New(alloc, mlo, MIRType::Float32x4); + addTo->add(flo); + + // Subtract out the bias: 0x1.0p39f + 0x1.0p23f. + // MSVC doesn't support the hexadecimal float syntax. + const float BiasValue = 549755813888.f + 8388608.f; + MInstruction* bias = + MSimdConstant::New(alloc, SimdConstant::SplatX4(BiasValue), MIRType::Float32x4); + addTo->add(bias); + MInstruction* fhi_debiased = + MSimdBinaryArith::AddLegalized(alloc, addTo, fhi, bias, MSimdBinaryArith::Op_sub); + + // Compute the final result. + return MSimdBinaryArith::AddLegalized(alloc, addTo, fhi_debiased, flo, + MSimdBinaryArith::Op_add); + } + + if (fromType == MIRType::Float32x4 && toType == MIRType::Int32x4) { + // The Float32x4 -> Uint32x4 conversion can throw if the input is out of + // range. This is handled by the LFloat32x4ToUint32x4 expansion. + MInstruction* ins = New(alloc, obj, toType, sign, trapOffset); + addTo->add(ins); + return ins; + } + + MOZ_CRASH("Unhandled SIMD type conversion"); +} + +MInstruction* +MSimdBinaryComp::AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* left, + MDefinition* right, Operation op, SimdSign sign) +{ + MOZ_ASSERT(left->type() == right->type()); + MIRType opType = left->type(); + MOZ_ASSERT(IsSimdType(opType)); + bool IsEquality = op == equal || op == notEqual; + + // Check if this is an unsupported unsigned compare that needs to be biased. + // If so, put the bias vector in `bias`. + if (sign == SimdSign::Unsigned && !IsEquality) { + MInstruction* bias = nullptr; + + // This is an order comparison of Uint32x4 vectors which are not supported on this target. + // Simply offset |left| and |right| by INT_MIN, then do a signed comparison. + if (!SupportsUint32x4Compares && opType == MIRType::Int32x4) + bias = MSimdConstant::New(alloc, SimdConstant::SplatX4(int32_t(0x80000000)), opType); + else if (!SupportsUint16x8Compares && opType == MIRType::Int16x8) + bias = MSimdConstant::New(alloc, SimdConstant::SplatX8(int16_t(0x8000)), opType); + if (!SupportsUint8x16Compares && opType == MIRType::Int8x16) + bias = MSimdConstant::New(alloc, SimdConstant::SplatX16(int8_t(0x80)), opType); + + if (bias) { + addTo->add(bias); + + // Add the bias. + MInstruction* bleft = + MSimdBinaryArith::AddLegalized(alloc, addTo, left, bias, MSimdBinaryArith::Op_add); + MInstruction* bright = + MSimdBinaryArith::AddLegalized(alloc, addTo, right, bias, MSimdBinaryArith::Op_add); + + // Do the equivalent signed comparison. + MInstruction* result = + MSimdBinaryComp::New(alloc, bleft, bright, op, SimdSign::Signed); + addTo->add(result); + + return result; + } + } + + if (sign == SimdSign::Unsigned && + ((!SupportsUint32x4Compares && opType == MIRType::Int32x4) || + (!SupportsUint16x8Compares && opType == MIRType::Int16x8) || + (!SupportsUint8x16Compares && opType == MIRType::Int8x16))) { + // The sign doesn't matter for equality tests. Flip it to make the + // backend assertions happy. + MOZ_ASSERT(IsEquality); + sign = SimdSign::Signed; + } + + // This is a legal operation already. Just create the instruction requested. + MInstruction* result = MSimdBinaryComp::New(alloc, left, right, op, sign); + addTo->add(result); + return result; +} + +MInstruction* +MSimdBinaryArith::AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* left, + MDefinition* right, Operation op) +{ + MOZ_ASSERT(left->type() == right->type()); + MIRType opType = left->type(); + MOZ_ASSERT(IsSimdType(opType)); + + // SSE does not have 8x16 multiply instructions. + if (opType == MIRType::Int8x16 && op == Op_mul) { + // Express the multiply in terms of Int16x8 multiplies by handling the + // even and odd lanes separately. + + MInstruction* wideL = MSimdReinterpretCast::New(alloc, left, MIRType::Int16x8); + addTo->add(wideL); + MInstruction* wideR = MSimdReinterpretCast::New(alloc, right, MIRType::Int16x8); + addTo->add(wideR); + + // wideL = yyxx yyxx yyxx yyxx yyxx yyxx yyxx yyxx + // wideR = bbaa bbaa bbaa bbaa bbaa bbaa bbaa bbaa + + // Shift the odd lanes down to the low bits of the 16x8 vectors. + MInstruction* eight = MConstant::New(alloc, Int32Value(8)); + addTo->add(eight); + MInstruction* evenL = wideL; + MInstruction* evenR = wideR; + MInstruction* oddL = + MSimdShift::AddLegalized(alloc, addTo, wideL, eight, MSimdShift::ursh); + MInstruction* oddR = + MSimdShift::AddLegalized(alloc, addTo, wideR, eight, MSimdShift::ursh); + + // evenL = yyxx yyxx yyxx yyxx yyxx yyxx yyxx yyxx + // evenR = bbaa bbaa bbaa bbaa bbaa bbaa bbaa bbaa + // oddL = 00yy 00yy 00yy 00yy 00yy 00yy 00yy 00yy + // oddR = 00bb 00bb 00bb 00bb 00bb 00bb 00bb 00bb + + // Now do two 16x8 multiplications. We can use the low bits of each. + MInstruction* even = MSimdBinaryArith::AddLegalized(alloc, addTo, evenL, evenR, Op_mul); + MInstruction* odd = MSimdBinaryArith::AddLegalized(alloc, addTo, oddL, oddR, Op_mul); + + // even = ~~PP ~~PP ~~PP ~~PP ~~PP ~~PP ~~PP ~~PP + // odd = ~~QQ ~~QQ ~~QQ ~~QQ ~~QQ ~~QQ ~~QQ ~~QQ + + MInstruction* mask = + MSimdConstant::New(alloc, SimdConstant::SplatX8(int16_t(0x00ff)), MIRType::Int16x8); + addTo->add(mask); + even = MSimdBinaryBitwise::New(alloc, even, mask, MSimdBinaryBitwise::and_); + addTo->add(even); + odd = MSimdShift::AddLegalized(alloc, addTo, odd, eight, MSimdShift::lsh); + + // even = 00PP 00PP 00PP 00PP 00PP 00PP 00PP 00PP + // odd = QQ00 QQ00 QQ00 QQ00 QQ00 QQ00 QQ00 QQ00 + + // Combine: + MInstruction* result = MSimdBinaryBitwise::New(alloc, even, odd, MSimdBinaryBitwise::or_); + addTo->add(result); + result = MSimdReinterpretCast::New(alloc, result, opType); + addTo->add(result); + return result; + } + + // This is a legal operation already. Just create the instruction requested. + MInstruction* result = MSimdBinaryArith::New(alloc, left, right, op); + addTo->add(result); + return result; +} + +MInstruction* +MSimdShift::AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* left, + MDefinition* right, Operation op) +{ + MIRType opType = left->type(); + MOZ_ASSERT(IsIntegerSimdType(opType)); + + // SSE does not provide 8x16 shift instructions. + if (opType == MIRType::Int8x16) { + // Express the shift in terms of Int16x8 shifts by splitting into even + // and odd lanes, place 8-bit lanes into the high bits of Int16x8 + // vectors `even` and `odd`. Shift, mask, combine. + // + // wide = Int16x8.fromInt8x16Bits(left); + // shiftBy = right & 7 + // mask = Int16x8.splat(0xff00); + // + MInstruction* wide = MSimdReinterpretCast::New(alloc, left, MIRType::Int16x8); + addTo->add(wide); + + // wide = yyxx yyxx yyxx yyxx yyxx yyxx yyxx yyxx + + MInstruction* shiftMask = MConstant::New(alloc, Int32Value(7)); + addTo->add(shiftMask); + MBinaryBitwiseInstruction* shiftBy = MBitAnd::New(alloc, right, shiftMask); + shiftBy->setInt32Specialization(); + addTo->add(shiftBy); + + // Move the even 8x16 lanes into the high bits of the 16x8 lanes. + MInstruction* eight = MConstant::New(alloc, Int32Value(8)); + addTo->add(eight); + MInstruction* even = MSimdShift::AddLegalized(alloc, addTo, wide, eight, lsh); + + // Leave the odd lanes in place. + MInstruction* odd = wide; + + // even = xx00 xx00 xx00 xx00 xx00 xx00 xx00 xx00 + // odd = yyxx yyxx yyxx yyxx yyxx yyxx yyxx yyxx + + MInstruction* mask = + MSimdConstant::New(alloc, SimdConstant::SplatX8(int16_t(0xff00)), MIRType::Int16x8); + addTo->add(mask); + + // Left-shift: Clear the low bits in `odd` before shifting. + if (op == lsh) { + odd = MSimdBinaryBitwise::New(alloc, odd, mask, MSimdBinaryBitwise::and_); + addTo->add(odd); + // odd = yy00 yy00 yy00 yy00 yy00 yy00 yy00 yy00 + } + + // Do the real shift twice: once for the even lanes, once for the odd + // lanes. This is a recursive call, but with a different type. + even = MSimdShift::AddLegalized(alloc, addTo, even, shiftBy, op); + odd = MSimdShift::AddLegalized(alloc, addTo, odd, shiftBy, op); + + // even = XX~~ XX~~ XX~~ XX~~ XX~~ XX~~ XX~~ XX~~ + // odd = YY~~ YY~~ YY~~ YY~~ YY~~ YY~~ YY~~ YY~~ + + // Right-shift: Clear the low bits in `odd` after shifting. + if (op != lsh) { + odd = MSimdBinaryBitwise::New(alloc, odd, mask, MSimdBinaryBitwise::and_); + addTo->add(odd); + // odd = YY00 YY00 YY00 YY00 YY00 YY00 YY00 YY00 + } + + // Move the even lanes back to their original place. + even = MSimdShift::AddLegalized(alloc, addTo, even, eight, ursh); + + // Now, `odd` contains the odd lanes properly shifted, and `even` + // contains the even lanes properly shifted: + // + // even = 00XX 00XX 00XX 00XX 00XX 00XX 00XX 00XX + // odd = YY00 YY00 YY00 YY00 YY00 YY00 YY00 YY00 + // + // Combine: + MInstruction* result = MSimdBinaryBitwise::New(alloc, even, odd, MSimdBinaryBitwise::or_); + addTo->add(result); + result = MSimdReinterpretCast::New(alloc, result, opType); + addTo->add(result); + return result; + } + + // This is a legal operation already. Just create the instruction requested. + MInstruction* result = MSimdShift::New(alloc, left, right, op); + addTo->add(result); + return result; +} + +template <typename T> +static void +PrintOpcodeOperation(T* mir, GenericPrinter& out) +{ + mir->MDefinition::printOpcode(out); + out.printf(" (%s)", T::OperationName(mir->operation())); +} + +void +MSimdBinaryArith::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeOperation(this, out); +} +void +MSimdBinarySaturating::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeOperation(this, out); +} +void +MSimdBinaryBitwise::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeOperation(this, out); +} +void +MSimdUnaryArith::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeOperation(this, out); +} +void +MSimdBinaryComp::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeOperation(this, out); +} +void +MSimdShift::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeOperation(this, out); +} + +void +MSimdInsertElement::printOpcode(GenericPrinter& out) const +{ + MDefinition::printOpcode(out); + out.printf(" (lane %u)", lane()); +} + +void +MSimdBox::printOpcode(GenericPrinter& out) const +{ + MDefinition::printOpcode(out); + out.printf(" (%s%s)", SimdTypeToString(simdType()), + initialHeap() == gc::TenuredHeap ? ", tenured" : ""); +} + +void +MSimdUnbox::printOpcode(GenericPrinter& out) const +{ + MDefinition::printOpcode(out); + out.printf(" (%s)", SimdTypeToString(simdType())); +} + +void +MControlInstruction::printOpcode(GenericPrinter& out) const +{ + MDefinition::printOpcode(out); + for (size_t j = 0; j < numSuccessors(); j++) { + if (getSuccessor(j)) + out.printf(" block%u", getSuccessor(j)->id()); + else + out.printf(" (null-to-be-patched)"); + } +} + +void +MCompare::printOpcode(GenericPrinter& out) const +{ + MDefinition::printOpcode(out); + out.printf(" %s", CodeName[jsop()]); +} + +void +MConstantElements::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeName(out, op()); + out.printf(" 0x%" PRIxPTR, value().asValue()); +} + +void +MLoadUnboxedScalar::printOpcode(GenericPrinter& out) const +{ + MDefinition::printOpcode(out); + out.printf(" %s", ScalarTypeDescr::typeName(storageType())); +} + +void +MAssertRange::printOpcode(GenericPrinter& out) const +{ + MDefinition::printOpcode(out); + out.put(" "); + assertedRange()->dump(out); +} + +const char* +MMathFunction::FunctionName(Function function) +{ + switch (function) { + case Log: return "Log"; + case Sin: return "Sin"; + case Cos: return "Cos"; + case Exp: return "Exp"; + case Tan: return "Tan"; + case ACos: return "ACos"; + case ASin: return "ASin"; + case ATan: return "ATan"; + case Log10: return "Log10"; + case Log2: return "Log2"; + case Log1P: return "Log1P"; + case ExpM1: return "ExpM1"; + case CosH: return "CosH"; + case SinH: return "SinH"; + case TanH: return "TanH"; + case ACosH: return "ACosH"; + case ASinH: return "ASinH"; + case ATanH: return "ATanH"; + case Sign: return "Sign"; + case Trunc: return "Trunc"; + case Cbrt: return "Cbrt"; + case Floor: return "Floor"; + case Ceil: return "Ceil"; + case Round: return "Round"; + default: + MOZ_CRASH("Unknown math function"); + } +} + +void +MMathFunction::printOpcode(GenericPrinter& out) const +{ + MDefinition::printOpcode(out); + out.printf(" %s", FunctionName(function())); +} + +MDefinition* +MMathFunction::foldsTo(TempAllocator& alloc) +{ + MDefinition* input = getOperand(0); + if (!input->isConstant() || !input->toConstant()->isTypeRepresentableAsDouble()) + return this; + + double in = input->toConstant()->numberToDouble(); + double out; + switch (function_) { + case Log: + out = js::math_log_uncached(in); + break; + case Sin: + out = js::math_sin_uncached(in); + break; + case Cos: + out = js::math_cos_uncached(in); + break; + case Exp: + out = js::math_exp_uncached(in); + break; + case Tan: + out = js::math_tan_uncached(in); + break; + case ACos: + out = js::math_acos_uncached(in); + break; + case ASin: + out = js::math_asin_uncached(in); + break; + case ATan: + out = js::math_atan_uncached(in); + break; + case Log10: + out = js::math_log10_uncached(in); + break; + case Log2: + out = js::math_log2_uncached(in); + break; + case Log1P: + out = js::math_log1p_uncached(in); + break; + case ExpM1: + out = js::math_expm1_uncached(in); + break; + case CosH: + out = js::math_cosh_uncached(in); + break; + case SinH: + out = js::math_sinh_uncached(in); + break; + case TanH: + out = js::math_tanh_uncached(in); + break; + case ACosH: + out = js::math_acosh_uncached(in); + break; + case ASinH: + out = js::math_asinh_uncached(in); + break; + case ATanH: + out = js::math_atanh_uncached(in); + break; + case Sign: + out = js::math_sign_uncached(in); + break; + case Trunc: + out = js::math_trunc_uncached(in); + break; + case Cbrt: + out = js::math_cbrt_uncached(in); + break; + case Floor: + out = js::math_floor_impl(in); + break; + case Ceil: + out = js::math_ceil_impl(in); + break; + case Round: + out = js::math_round_impl(in); + break; + default: + return this; + } + + if (input->type() == MIRType::Float32) + return MConstant::NewFloat32(alloc, out); + return MConstant::New(alloc, DoubleValue(out)); +} + +MDefinition* +MAtomicIsLockFree::foldsTo(TempAllocator& alloc) +{ + MDefinition* input = getOperand(0); + if (!input->isConstant() || input->type() != MIRType::Int32) + return this; + + int32_t i = input->toConstant()->toInt32(); + return MConstant::New(alloc, BooleanValue(AtomicOperations::isLockfree(i))); +} + +// Define |THIS_SLOT| as part of this translation unit, as it is used to +// specialized the parameterized |New| function calls introduced by +// TRIVIAL_NEW_WRAPPERS. +const int32_t MParameter::THIS_SLOT; + +void +MParameter::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeName(out, op()); + if (index() == THIS_SLOT) + out.printf(" THIS_SLOT"); + else + out.printf(" %d", index()); +} + +HashNumber +MParameter::valueHash() const +{ + HashNumber hash = MDefinition::valueHash(); + hash = addU32ToHash(hash, index_); + return hash; +} + +bool +MParameter::congruentTo(const MDefinition* ins) const +{ + if (!ins->isParameter()) + return false; + + return ins->toParameter()->index() == index_; +} + +WrappedFunction::WrappedFunction(JSFunction* fun) + : fun_(fun), + nargs_(fun->nargs()), + isNative_(fun->isNative()), + isConstructor_(fun->isConstructor()), + isClassConstructor_(fun->isClassConstructor()), + isSelfHostedBuiltin_(fun->isSelfHostedBuiltin()) +{} + +MCall* +MCall::New(TempAllocator& alloc, JSFunction* target, size_t maxArgc, size_t numActualArgs, + bool construct, bool isDOMCall) +{ + WrappedFunction* wrappedTarget = target ? new(alloc) WrappedFunction(target) : nullptr; + MOZ_ASSERT(maxArgc >= numActualArgs); + MCall* ins; + if (isDOMCall) { + MOZ_ASSERT(!construct); + ins = new(alloc) MCallDOMNative(wrappedTarget, numActualArgs); + } else { + ins = new(alloc) MCall(wrappedTarget, numActualArgs, construct); + } + if (!ins->init(alloc, maxArgc + NumNonArgumentOperands)) + return nullptr; + return ins; +} + +AliasSet +MCallDOMNative::getAliasSet() const +{ + const JSJitInfo* jitInfo = getJitInfo(); + + // If we don't know anything about the types of our arguments, we have to + // assume that type-coercions can have side-effects, so we need to alias + // everything. + if (jitInfo->aliasSet() == JSJitInfo::AliasEverything || !jitInfo->isTypedMethodJitInfo()) + return AliasSet::Store(AliasSet::Any); + + uint32_t argIndex = 0; + const JSTypedMethodJitInfo* methodInfo = + reinterpret_cast<const JSTypedMethodJitInfo*>(jitInfo); + for (const JSJitInfo::ArgType* argType = methodInfo->argTypes; + *argType != JSJitInfo::ArgTypeListEnd; + ++argType, ++argIndex) + { + if (argIndex >= numActualArgs()) { + // Passing through undefined can't have side-effects + continue; + } + // getArg(0) is "this", so skip it + MDefinition* arg = getArg(argIndex+1); + MIRType actualType = arg->type(); + // The only way to reliably avoid side-effects given the information we + // have here is if we're passing in a known primitive value to an + // argument that expects a primitive value. + // + // XXXbz maybe we need to communicate better information. For example, + // a sequence argument will sort of unavoidably have side effects, while + // a typed array argument won't have any, but both are claimed to be + // JSJitInfo::Object. But if we do that, we need to watch out for our + // movability/DCE-ability bits: if we have an arg type that can reliably + // throw an exception on conversion, that might not affect our alias set + // per se, but it should prevent us being moved or DCE-ed, unless we + // know the incoming things match that arg type and won't throw. + // + if ((actualType == MIRType::Value || actualType == MIRType::Object) || + (*argType & JSJitInfo::Object)) + { + return AliasSet::Store(AliasSet::Any); + } + } + + // We checked all the args, and they check out. So we only alias DOM + // mutations or alias nothing, depending on the alias set in the jitinfo. + if (jitInfo->aliasSet() == JSJitInfo::AliasNone) + return AliasSet::None(); + + MOZ_ASSERT(jitInfo->aliasSet() == JSJitInfo::AliasDOMSets); + return AliasSet::Load(AliasSet::DOMProperty); +} + +void +MCallDOMNative::computeMovable() +{ + // We are movable if the jitinfo says we can be and if we're also not + // effectful. The jitinfo can't check for the latter, since it depends on + // the types of our arguments. + const JSJitInfo* jitInfo = getJitInfo(); + + MOZ_ASSERT_IF(jitInfo->isMovable, + jitInfo->aliasSet() != JSJitInfo::AliasEverything); + + if (jitInfo->isMovable && !isEffectful()) + setMovable(); +} + +bool +MCallDOMNative::congruentTo(const MDefinition* ins) const +{ + if (!isMovable()) + return false; + + if (!ins->isCall()) + return false; + + const MCall* call = ins->toCall(); + + if (!call->isCallDOMNative()) + return false; + + if (getSingleTarget() != call->getSingleTarget()) + return false; + + if (isConstructing() != call->isConstructing()) + return false; + + if (numActualArgs() != call->numActualArgs()) + return false; + + if (needsArgCheck() != call->needsArgCheck()) + return false; + + if (!congruentIfOperandsEqual(call)) + return false; + + // The other call had better be movable at this point! + MOZ_ASSERT(call->isMovable()); + + return true; +} + +const JSJitInfo* +MCallDOMNative::getJitInfo() const +{ + MOZ_ASSERT(getSingleTarget() && getSingleTarget()->isNative()); + + const JSJitInfo* jitInfo = getSingleTarget()->jitInfo(); + MOZ_ASSERT(jitInfo); + + return jitInfo; +} + +MDefinition* +MStringLength::foldsTo(TempAllocator& alloc) +{ + if (type() == MIRType::Int32 && string()->isConstant()) { + JSAtom* atom = &string()->toConstant()->toString()->asAtom(); + return MConstant::New(alloc, Int32Value(atom->length())); + } + + return this; +} + +MDefinition* +MConcat::foldsTo(TempAllocator& alloc) +{ + if (lhs()->isConstant() && lhs()->toConstant()->toString()->empty()) + return rhs(); + + if (rhs()->isConstant() && rhs()->toConstant()->toString()->empty()) + return lhs(); + + return this; +} + +static bool +EnsureFloatInputOrConvert(MUnaryInstruction* owner, TempAllocator& alloc) +{ + MDefinition* input = owner->input(); + if (!input->canProduceFloat32()) { + if (input->type() == MIRType::Float32) + ConvertDefinitionToDouble<0>(alloc, input, owner); + return false; + } + return true; +} + +void +MFloor::trySpecializeFloat32(TempAllocator& alloc) +{ + MOZ_ASSERT(type() == MIRType::Int32); + if (EnsureFloatInputOrConvert(this, alloc)) + specialization_ = MIRType::Float32; +} + +void +MCeil::trySpecializeFloat32(TempAllocator& alloc) +{ + MOZ_ASSERT(type() == MIRType::Int32); + if (EnsureFloatInputOrConvert(this, alloc)) + specialization_ = MIRType::Float32; +} + +void +MRound::trySpecializeFloat32(TempAllocator& alloc) +{ + MOZ_ASSERT(type() == MIRType::Int32); + if (EnsureFloatInputOrConvert(this, alloc)) + specialization_ = MIRType::Float32; +} + +MTableSwitch* +MTableSwitch::New(TempAllocator& alloc, MDefinition* ins, int32_t low, int32_t high) +{ + return new(alloc) MTableSwitch(alloc, ins, low, high); +} + +MGoto* +MGoto::New(TempAllocator& alloc, MBasicBlock* target) +{ + MOZ_ASSERT(target); + return new(alloc) MGoto(target); +} + +MGoto* +MGoto::New(TempAllocator::Fallible alloc, MBasicBlock* target) +{ + MOZ_ASSERT(target); + return new(alloc) MGoto(target); +} + +MGoto* +MGoto::New(TempAllocator& alloc) +{ + return new(alloc) MGoto(nullptr); +} + +void +MUnbox::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeName(out, op()); + out.printf(" "); + getOperand(0)->printName(out); + out.printf(" "); + + switch (type()) { + case MIRType::Int32: out.printf("to Int32"); break; + case MIRType::Double: out.printf("to Double"); break; + case MIRType::Boolean: out.printf("to Boolean"); break; + case MIRType::String: out.printf("to String"); break; + case MIRType::Symbol: out.printf("to Symbol"); break; + case MIRType::Object: out.printf("to Object"); break; + default: break; + } + + switch (mode()) { + case Fallible: out.printf(" (fallible)"); break; + case Infallible: out.printf(" (infallible)"); break; + case TypeBarrier: out.printf(" (typebarrier)"); break; + default: break; + } +} + +MDefinition* +MUnbox::foldsTo(TempAllocator &alloc) +{ + if (!input()->isLoadFixedSlot()) + return this; + MLoadFixedSlot* load = input()->toLoadFixedSlot(); + if (load->type() != MIRType::Value) + return this; + if (type() != MIRType::Boolean && !IsNumberType(type())) + return this; + // Only optimize if the load comes immediately before the unbox, so it's + // safe to copy the load's dependency field. + MInstructionIterator iter(load->block()->begin(load)); + ++iter; + if (*iter != this) + return this; + + MLoadFixedSlotAndUnbox* ins = MLoadFixedSlotAndUnbox::New(alloc, load->object(), load->slot(), + mode(), type(), bailoutKind()); + // As GVN runs after the Alias Analysis, we have to set the dependency by hand + ins->setDependency(load->dependency()); + return ins; +} + +void +MTypeBarrier::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeName(out, op()); + out.printf(" "); + getOperand(0)->printName(out); +} + +bool +MTypeBarrier::congruentTo(const MDefinition* def) const +{ + if (!def->isTypeBarrier()) + return false; + const MTypeBarrier* other = def->toTypeBarrier(); + if (barrierKind() != other->barrierKind() || isGuard() != other->isGuard()) + return false; + if (!resultTypeSet()->equals(other->resultTypeSet())) + return false; + return congruentIfOperandsEqual(other); +} + +#ifdef DEBUG +void +MPhi::assertLoopPhi() const +{ + // getLoopPredecessorOperand and getLoopBackedgeOperand rely on these + // predecessors being at indices 0 and 1. + MBasicBlock* pred = block()->getPredecessor(0); + MBasicBlock* back = block()->getPredecessor(1); + MOZ_ASSERT(pred == block()->loopPredecessor()); + MOZ_ASSERT(pred->successorWithPhis() == block()); + MOZ_ASSERT(pred->positionInPhiSuccessor() == 0); + MOZ_ASSERT(back == block()->backedge()); + MOZ_ASSERT(back->successorWithPhis() == block()); + MOZ_ASSERT(back->positionInPhiSuccessor() == 1); +} +#endif + +void +MPhi::removeOperand(size_t index) +{ + MOZ_ASSERT(index < numOperands()); + MOZ_ASSERT(getUseFor(index)->index() == index); + MOZ_ASSERT(getUseFor(index)->consumer() == this); + + // If we have phi(..., a, b, c, d, ..., z) and we plan + // on removing a, then first shift downward so that we have + // phi(..., b, c, d, ..., z, z): + MUse* p = inputs_.begin() + index; + MUse* e = inputs_.end(); + p->producer()->removeUse(p); + for (; p < e - 1; ++p) { + MDefinition* producer = (p + 1)->producer(); + p->setProducerUnchecked(producer); + producer->replaceUse(p + 1, p); + } + + // truncate the inputs_ list: + inputs_.popBack(); +} + +void +MPhi::removeAllOperands() +{ + for (MUse& p : inputs_) + p.producer()->removeUse(&p); + inputs_.clear(); +} + +MDefinition* +MPhi::foldsTernary(TempAllocator& alloc) +{ + /* Look if this MPhi is a ternary construct. + * This is a very loose term as it actually only checks for + * + * MTest X + * / \ + * ... ... + * \ / + * MPhi X Y + * + * Which we will simply call: + * x ? x : y or x ? y : x + */ + + if (numOperands() != 2) + return nullptr; + + MOZ_ASSERT(block()->numPredecessors() == 2); + + MBasicBlock* pred = block()->immediateDominator(); + if (!pred || !pred->lastIns()->isTest()) + return nullptr; + + MTest* test = pred->lastIns()->toTest(); + + // True branch may only dominate one edge of MPhi. + if (test->ifTrue()->dominates(block()->getPredecessor(0)) == + test->ifTrue()->dominates(block()->getPredecessor(1))) + { + return nullptr; + } + + // False branch may only dominate one edge of MPhi. + if (test->ifFalse()->dominates(block()->getPredecessor(0)) == + test->ifFalse()->dominates(block()->getPredecessor(1))) + { + return nullptr; + } + + // True and false branch must dominate different edges of MPhi. + if (test->ifTrue()->dominates(block()->getPredecessor(0)) == + test->ifFalse()->dominates(block()->getPredecessor(0))) + { + return nullptr; + } + + // We found a ternary construct. + bool firstIsTrueBranch = test->ifTrue()->dominates(block()->getPredecessor(0)); + MDefinition* trueDef = firstIsTrueBranch ? getOperand(0) : getOperand(1); + MDefinition* falseDef = firstIsTrueBranch ? getOperand(1) : getOperand(0); + + // Accept either + // testArg ? testArg : constant or + // testArg ? constant : testArg + if (!trueDef->isConstant() && !falseDef->isConstant()) + return nullptr; + + MConstant* c = trueDef->isConstant() ? trueDef->toConstant() : falseDef->toConstant(); + MDefinition* testArg = (trueDef == c) ? falseDef : trueDef; + if (testArg != test->input()) + return nullptr; + + // This check should be a tautology, except that the constant might be the + // result of the removal of a branch. In such case the domination scope of + // the block which is holding the constant might be incomplete. This + // condition is used to prevent doing this optimization based on incomplete + // information. + // + // As GVN removed a branch, it will update the dominations rules before + // trying to fold this MPhi again. Thus, this condition does not inhibit + // this optimization. + MBasicBlock* truePred = block()->getPredecessor(firstIsTrueBranch ? 0 : 1); + MBasicBlock* falsePred = block()->getPredecessor(firstIsTrueBranch ? 1 : 0); + if (!trueDef->block()->dominates(truePred) || + !falseDef->block()->dominates(falsePred)) + { + return nullptr; + } + + // If testArg is an int32 type we can: + // - fold testArg ? testArg : 0 to testArg + // - fold testArg ? 0 : testArg to 0 + if (testArg->type() == MIRType::Int32 && c->numberToDouble() == 0) { + testArg->setGuardRangeBailoutsUnchecked(); + + // When folding to the constant we need to hoist it. + if (trueDef == c && !c->block()->dominates(block())) + c->block()->moveBefore(pred->lastIns(), c); + return trueDef; + } + + // If testArg is an double type we can: + // - fold testArg ? testArg : 0.0 to MNaNToZero(testArg) + if (testArg->type() == MIRType::Double && mozilla::IsPositiveZero(c->numberToDouble()) && + c != trueDef) + { + MNaNToZero* replace = MNaNToZero::New(alloc, testArg); + test->block()->insertBefore(test, replace); + return replace; + } + + // If testArg is a string type we can: + // - fold testArg ? testArg : "" to testArg + // - fold testArg ? "" : testArg to "" + if (testArg->type() == MIRType::String && + c->toString() == GetJitContext()->runtime->emptyString()) + { + // When folding to the constant we need to hoist it. + if (trueDef == c && !c->block()->dominates(block())) + c->block()->moveBefore(pred->lastIns(), c); + return trueDef; + } + + return nullptr; +} + +MDefinition* +MPhi::operandIfRedundant() +{ + if (inputs_.length() == 0) + return nullptr; + + // If this phi is redundant (e.g., phi(a,a) or b=phi(a,this)), + // returns the operand that it will always be equal to (a, in + // those two cases). + MDefinition* first = getOperand(0); + for (size_t i = 1, e = numOperands(); i < e; i++) { + MDefinition* op = getOperand(i); + if (op != first && op != this) + return nullptr; + } + return first; +} + +MDefinition* +MPhi::foldsFilterTypeSet() +{ + // Fold phi with as operands a combination of 'subject' and + // MFilterTypeSet(subject) to 'subject'. + + if (inputs_.length() == 0) + return nullptr; + + MDefinition* subject = getOperand(0); + if (subject->isFilterTypeSet()) + subject = subject->toFilterTypeSet()->input(); + + // Not same type, don't fold. + if (subject->type() != type()) + return nullptr; + + // Phi is better typed (has typeset). Don't fold. + if (resultTypeSet() && !subject->resultTypeSet()) + return nullptr; + + // Phi is better typed (according to typeset). Don't fold. + if (subject->resultTypeSet() && resultTypeSet()) { + if (!subject->resultTypeSet()->isSubset(resultTypeSet())) + return nullptr; + } + + for (size_t i = 1, e = numOperands(); i < e; i++) { + MDefinition* op = getOperand(i); + if (op == subject) + continue; + if (op->isFilterTypeSet() && op->toFilterTypeSet()->input() == subject) + continue; + + return nullptr; + } + + return subject; +} + +MDefinition* +MPhi::foldsTo(TempAllocator& alloc) +{ + if (MDefinition* def = operandIfRedundant()) + return def; + + if (MDefinition* def = foldsTernary(alloc)) + return def; + + if (MDefinition* def = foldsFilterTypeSet()) + return def; + + return this; +} + +bool +MPhi::congruentTo(const MDefinition* ins) const +{ + if (!ins->isPhi()) + return false; + + // Phis in different blocks may have different control conditions. + // For example, these phis: + // + // if (p) + // goto a + // a: + // t = phi(x, y) + // + // if (q) + // goto b + // b: + // s = phi(x, y) + // + // have identical operands, but they are not equvalent because t is + // effectively p?x:y and s is effectively q?x:y. + // + // For now, consider phis in different blocks incongruent. + if (ins->block() != block()) + return false; + + return congruentIfOperandsEqual(ins); +} + +static inline TemporaryTypeSet* +MakeMIRTypeSet(TempAllocator& alloc, MIRType type) +{ + MOZ_ASSERT(type != MIRType::Value); + TypeSet::Type ntype = type == MIRType::Object + ? TypeSet::AnyObjectType() + : TypeSet::PrimitiveType(ValueTypeFromMIRType(type)); + return alloc.lifoAlloc()->new_<TemporaryTypeSet>(alloc.lifoAlloc(), ntype); +} + +bool +jit::MergeTypes(TempAllocator& alloc, MIRType* ptype, TemporaryTypeSet** ptypeSet, + MIRType newType, TemporaryTypeSet* newTypeSet) +{ + if (newTypeSet && newTypeSet->empty()) + return true; + LifoAlloc::AutoFallibleScope fallibleAllocator(alloc.lifoAlloc()); + if (newType != *ptype) { + if (IsTypeRepresentableAsDouble(newType) && IsTypeRepresentableAsDouble(*ptype)) { + *ptype = MIRType::Double; + } else if (*ptype != MIRType::Value) { + if (!*ptypeSet) { + *ptypeSet = MakeMIRTypeSet(alloc, *ptype); + if (!*ptypeSet) + return false; + } + *ptype = MIRType::Value; + } else if (*ptypeSet && (*ptypeSet)->empty()) { + *ptype = newType; + } + } + if (*ptypeSet) { + if (!newTypeSet && newType != MIRType::Value) { + newTypeSet = MakeMIRTypeSet(alloc, newType); + if (!newTypeSet) + return false; + } + if (newTypeSet) { + if (!newTypeSet->isSubset(*ptypeSet)) { + *ptypeSet = TypeSet::unionSets(*ptypeSet, newTypeSet, alloc.lifoAlloc()); + if (!*ptypeSet) + return false; + } + } else { + *ptypeSet = nullptr; + } + } + return true; +} + +// Tests whether 'types' includes all possible values represented by +// input/inputTypes. +bool +jit::TypeSetIncludes(TypeSet* types, MIRType input, TypeSet* inputTypes) +{ + if (!types) + return inputTypes && inputTypes->empty(); + + switch (input) { + case MIRType::Undefined: + case MIRType::Null: + case MIRType::Boolean: + case MIRType::Int32: + case MIRType::Double: + case MIRType::Float32: + case MIRType::String: + case MIRType::Symbol: + case MIRType::MagicOptimizedArguments: + return types->hasType(TypeSet::PrimitiveType(ValueTypeFromMIRType(input))); + + case MIRType::Object: + return types->unknownObject() || (inputTypes && inputTypes->isSubset(types)); + + case MIRType::Value: + return types->unknown() || (inputTypes && inputTypes->isSubset(types)); + + default: + MOZ_CRASH("Bad input type"); + } +} + +// Tests if two type combos (type/typeset) are equal. +bool +jit::EqualTypes(MIRType type1, TemporaryTypeSet* typeset1, + MIRType type2, TemporaryTypeSet* typeset2) +{ + // Types should equal. + if (type1 != type2) + return false; + + // Both have equal type and no typeset. + if (!typeset1 && !typeset2) + return true; + + // If only one instructions has a typeset. + // Test if the typset contains the same information as the MIRType. + if (typeset1 && !typeset2) + return TypeSetIncludes(typeset1, type2, nullptr); + if (!typeset1 && typeset2) + return TypeSetIncludes(typeset2, type1, nullptr); + + // Typesets should equal. + return typeset1->equals(typeset2); +} + +// Tests whether input/inputTypes can always be stored to an unboxed +// object/array property with the given unboxed type. +bool +jit::CanStoreUnboxedType(TempAllocator& alloc, + JSValueType unboxedType, MIRType input, TypeSet* inputTypes) +{ + TemporaryTypeSet types; + + switch (unboxedType) { + case JSVAL_TYPE_BOOLEAN: + case JSVAL_TYPE_INT32: + case JSVAL_TYPE_DOUBLE: + case JSVAL_TYPE_STRING: + types.addType(TypeSet::PrimitiveType(unboxedType), alloc.lifoAlloc()); + break; + + case JSVAL_TYPE_OBJECT: + types.addType(TypeSet::AnyObjectType(), alloc.lifoAlloc()); + types.addType(TypeSet::NullType(), alloc.lifoAlloc()); + break; + + default: + MOZ_CRASH("Bad unboxed type"); + } + + return TypeSetIncludes(&types, input, inputTypes); +} + +static bool +CanStoreUnboxedType(TempAllocator& alloc, JSValueType unboxedType, MDefinition* value) +{ + return CanStoreUnboxedType(alloc, unboxedType, value->type(), value->resultTypeSet()); +} + +bool +MPhi::specializeType(TempAllocator& alloc) +{ +#ifdef DEBUG + MOZ_ASSERT(!specialized_); + specialized_ = true; +#endif + + MOZ_ASSERT(!inputs_.empty()); + + size_t start; + if (hasBackedgeType_) { + // The type of this phi has already been populated with potential types + // that could come in via loop backedges. + start = 0; + } else { + setResultType(getOperand(0)->type()); + setResultTypeSet(getOperand(0)->resultTypeSet()); + start = 1; + } + + MIRType resultType = this->type(); + TemporaryTypeSet* resultTypeSet = this->resultTypeSet(); + + for (size_t i = start; i < inputs_.length(); i++) { + MDefinition* def = getOperand(i); + if (!MergeTypes(alloc, &resultType, &resultTypeSet, def->type(), def->resultTypeSet())) + return false; + } + + setResultType(resultType); + setResultTypeSet(resultTypeSet); + return true; +} + +bool +MPhi::addBackedgeType(TempAllocator& alloc, MIRType type, TemporaryTypeSet* typeSet) +{ + MOZ_ASSERT(!specialized_); + + if (hasBackedgeType_) { + MIRType resultType = this->type(); + TemporaryTypeSet* resultTypeSet = this->resultTypeSet(); + + if (!MergeTypes(alloc, &resultType, &resultTypeSet, type, typeSet)) + return false; + + setResultType(resultType); + setResultTypeSet(resultTypeSet); + } else { + setResultType(type); + setResultTypeSet(typeSet); + hasBackedgeType_ = true; + } + return true; +} + +bool +MPhi::typeIncludes(MDefinition* def) +{ + if (def->type() == MIRType::Int32 && this->type() == MIRType::Double) + return true; + + if (TemporaryTypeSet* types = def->resultTypeSet()) { + if (this->resultTypeSet()) + return types->isSubset(this->resultTypeSet()); + if (this->type() == MIRType::Value || types->empty()) + return true; + return this->type() == types->getKnownMIRType(); + } + + if (def->type() == MIRType::Value) { + // This phi must be able to be any value. + return this->type() == MIRType::Value + && (!this->resultTypeSet() || this->resultTypeSet()->unknown()); + } + + return this->mightBeType(def->type()); +} + +bool +MPhi::checkForTypeChange(TempAllocator& alloc, MDefinition* ins, bool* ptypeChange) +{ + MIRType resultType = this->type(); + TemporaryTypeSet* resultTypeSet = this->resultTypeSet(); + + if (!MergeTypes(alloc, &resultType, &resultTypeSet, ins->type(), ins->resultTypeSet())) + return false; + + if (resultType != this->type() || resultTypeSet != this->resultTypeSet()) { + *ptypeChange = true; + setResultType(resultType); + setResultTypeSet(resultTypeSet); + } + return true; +} + +void +MCall::addArg(size_t argnum, MDefinition* arg) +{ + // The operand vector is initialized in reverse order by the IonBuilder. + // It cannot be checked for consistency until all arguments are added. + // FixedList doesn't initialize its elements, so do an unchecked init. + initOperand(argnum + NumNonArgumentOperands, arg); +} + +static inline bool +IsConstant(MDefinition* def, double v) +{ + if (!def->isConstant()) + return false; + + return NumbersAreIdentical(def->toConstant()->numberToDouble(), v); +} + +MDefinition* +MBinaryBitwiseInstruction::foldsTo(TempAllocator& alloc) +{ + if (specialization_ != MIRType::Int32) + return this; + + if (MDefinition* folded = EvaluateConstantOperands(alloc, this)) + return folded; + + return this; +} + +MDefinition* +MBinaryBitwiseInstruction::foldUnnecessaryBitop() +{ + if (specialization_ != MIRType::Int32) + return this; + + // Fold unsigned shift right operator when the second operand is zero and + // the only use is an unsigned modulo. Thus, the expression + // |(x >>> 0) % y| becomes |x % y|. + if (isUrsh() && hasOneDefUse() && IsUint32Type(this)) { + MUseDefIterator use(this); + if (use.def()->isMod() && use.def()->toMod()->isUnsigned()) + return getOperand(0); + MOZ_ASSERT(!(++use)); + } + + // Eliminate bitwise operations that are no-ops when used on integer + // inputs, such as (x | 0). + + MDefinition* lhs = getOperand(0); + MDefinition* rhs = getOperand(1); + + if (IsConstant(lhs, 0)) + return foldIfZero(0); + + if (IsConstant(rhs, 0)) + return foldIfZero(1); + + if (IsConstant(lhs, -1)) + return foldIfNegOne(0); + + if (IsConstant(rhs, -1)) + return foldIfNegOne(1); + + if (lhs == rhs) + return foldIfEqual(); + + if (maskMatchesRightRange) { + MOZ_ASSERT(lhs->isConstant()); + MOZ_ASSERT(lhs->type() == MIRType::Int32); + return foldIfAllBitsSet(0); + } + + if (maskMatchesLeftRange) { + MOZ_ASSERT(rhs->isConstant()); + MOZ_ASSERT(rhs->type() == MIRType::Int32); + return foldIfAllBitsSet(1); + } + + return this; +} + +void +MBinaryBitwiseInstruction::infer(BaselineInspector*, jsbytecode*) +{ + if (getOperand(0)->mightBeType(MIRType::Object) || getOperand(0)->mightBeType(MIRType::Symbol) || + getOperand(1)->mightBeType(MIRType::Object) || getOperand(1)->mightBeType(MIRType::Symbol)) + { + specialization_ = MIRType::None; + } else { + specializeAs(MIRType::Int32); + } +} + +void +MBinaryBitwiseInstruction::specializeAs(MIRType type) +{ + MOZ_ASSERT(type == MIRType::Int32 || type == MIRType::Int64); + MOZ_ASSERT(this->type() == type); + + specialization_ = type; + + if (isBitOr() || isBitAnd() || isBitXor()) + setCommutative(); +} + +void +MShiftInstruction::infer(BaselineInspector*, jsbytecode*) +{ + if (getOperand(0)->mightBeType(MIRType::Object) || getOperand(1)->mightBeType(MIRType::Object) || + getOperand(0)->mightBeType(MIRType::Symbol) || getOperand(1)->mightBeType(MIRType::Symbol)) + specialization_ = MIRType::None; + else + specialization_ = MIRType::Int32; +} + +void +MUrsh::infer(BaselineInspector* inspector, jsbytecode* pc) +{ + if (getOperand(0)->mightBeType(MIRType::Object) || getOperand(1)->mightBeType(MIRType::Object) || + getOperand(0)->mightBeType(MIRType::Symbol) || getOperand(1)->mightBeType(MIRType::Symbol)) + { + specialization_ = MIRType::None; + setResultType(MIRType::Value); + return; + } + + if (inspector->hasSeenDoubleResult(pc)) { + specialization_ = MIRType::Double; + setResultType(MIRType::Double); + return; + } + + specialization_ = MIRType::Int32; + setResultType(MIRType::Int32); +} + +static inline bool +CanProduceNegativeZero(MDefinition* def) +{ + // Test if this instruction can produce negative zero even when bailing out + // and changing types. + switch (def->op()) { + case MDefinition::Op_Constant: + if (def->type() == MIRType::Double && def->toConstant()->toDouble() == -0.0) + return true; + MOZ_FALLTHROUGH; + case MDefinition::Op_BitAnd: + case MDefinition::Op_BitOr: + case MDefinition::Op_BitXor: + case MDefinition::Op_BitNot: + case MDefinition::Op_Lsh: + case MDefinition::Op_Rsh: + return false; + default: + return true; + } +} + +static inline bool +NeedNegativeZeroCheck(MDefinition* def) +{ + if (def->isGuardRangeBailouts()) + return true; + + // Test if all uses have the same semantics for -0 and 0 + for (MUseIterator use = def->usesBegin(); use != def->usesEnd(); use++) { + if (use->consumer()->isResumePoint()) + continue; + + MDefinition* use_def = use->consumer()->toDefinition(); + switch (use_def->op()) { + case MDefinition::Op_Add: { + // If add is truncating -0 and 0 are observed as the same. + if (use_def->toAdd()->isTruncated()) + break; + + // x + y gives -0, when both x and y are -0 + + // Figure out the order in which the addition's operands will + // execute. EdgeCaseAnalysis::analyzeLate has renumbered the MIR + // definitions for us so that this just requires comparing ids. + MDefinition* first = use_def->toAdd()->lhs(); + MDefinition* second = use_def->toAdd()->rhs(); + if (first->id() > second->id()) { + MDefinition* temp = first; + first = second; + second = temp; + } + // Negative zero checks can be removed on the first executed + // operand only if it is guaranteed the second executed operand + // will produce a value other than -0. While the second is + // typed as an int32, a bailout taken between execution of the + // operands may change that type and cause a -0 to flow to the + // second. + // + // There is no way to test whether there are any bailouts + // between execution of the operands, so remove negative + // zero checks from the first only if the second's type is + // independent from type changes that may occur after bailing. + if (def == first && CanProduceNegativeZero(second)) + return true; + + // The negative zero check can always be removed on the second + // executed operand; by the time this executes the first will have + // been evaluated as int32 and the addition's result cannot be -0. + break; + } + case MDefinition::Op_Sub: { + // If sub is truncating -0 and 0 are observed as the same + if (use_def->toSub()->isTruncated()) + break; + + // x + y gives -0, when x is -0 and y is 0 + + // We can remove the negative zero check on the rhs, only if we + // are sure the lhs isn't negative zero. + + // The lhs is typed as integer (i.e. not -0.0), but it can bailout + // and change type. This should be fine if the lhs is executed + // first. However if the rhs is executed first, the lhs can bail, + // change type and become -0.0 while the rhs has already been + // optimized to not make a difference between zero and negative zero. + MDefinition* lhs = use_def->toSub()->lhs(); + MDefinition* rhs = use_def->toSub()->rhs(); + if (rhs->id() < lhs->id() && CanProduceNegativeZero(lhs)) + return true; + + MOZ_FALLTHROUGH; + } + case MDefinition::Op_StoreElement: + case MDefinition::Op_StoreElementHole: + case MDefinition::Op_FallibleStoreElement: + case MDefinition::Op_LoadElement: + case MDefinition::Op_LoadElementHole: + case MDefinition::Op_LoadUnboxedScalar: + case MDefinition::Op_LoadTypedArrayElementHole: + case MDefinition::Op_CharCodeAt: + case MDefinition::Op_Mod: + // Only allowed to remove check when definition is the second operand + if (use_def->getOperand(0) == def) + return true; + for (size_t i = 2, e = use_def->numOperands(); i < e; i++) { + if (use_def->getOperand(i) == def) + return true; + } + break; + case MDefinition::Op_BoundsCheck: + // Only allowed to remove check when definition is the first operand + if (use_def->toBoundsCheck()->getOperand(1) == def) + return true; + break; + case MDefinition::Op_ToString: + case MDefinition::Op_FromCharCode: + case MDefinition::Op_TableSwitch: + case MDefinition::Op_Compare: + case MDefinition::Op_BitAnd: + case MDefinition::Op_BitOr: + case MDefinition::Op_BitXor: + case MDefinition::Op_Abs: + case MDefinition::Op_TruncateToInt32: + // Always allowed to remove check. No matter which operand. + break; + default: + return true; + } + } + return false; +} + +void +MBinaryArithInstruction::printOpcode(GenericPrinter& out) const +{ + MDefinition::printOpcode(out); + + switch (type()) { + case MIRType::Int32: + if (isDiv()) + out.printf(" [%s]", toDiv()->isUnsigned() ? "uint32" : "int32"); + else if (isMod()) + out.printf(" [%s]", toMod()->isUnsigned() ? "uint32" : "int32"); + else + out.printf(" [int32]"); + break; + case MIRType::Int64: + if (isDiv()) + out.printf(" [%s]", toDiv()->isUnsigned() ? "uint64" : "int64"); + else if (isMod()) + out.printf(" [%s]", toMod()->isUnsigned() ? "uint64" : "int64"); + else + out.printf(" [int64]"); + break; + case MIRType::Float32: + out.printf(" [float]"); + break; + case MIRType::Double: + out.printf(" [double]"); + break; + default: + break; + } +} + +MBinaryArithInstruction* +MBinaryArithInstruction::New(TempAllocator& alloc, Opcode op, + MDefinition* left, MDefinition* right) +{ + switch (op) { + case Op_Add: + return MAdd::New(alloc, left, right); + case Op_Sub: + return MSub::New(alloc, left, right); + case Op_Mul: + return MMul::New(alloc, left, right); + case Op_Div: + return MDiv::New(alloc, left, right); + case Op_Mod: + return MMod::New(alloc, left, right); + default: + MOZ_CRASH("unexpected binary opcode"); + } +} + +void +MBinaryArithInstruction::setNumberSpecialization(TempAllocator& alloc, BaselineInspector* inspector, + jsbytecode* pc) +{ + setSpecialization(MIRType::Double); + + // Try to specialize as int32. + if (getOperand(0)->type() == MIRType::Int32 && getOperand(1)->type() == MIRType::Int32) { + bool seenDouble = inspector->hasSeenDoubleResult(pc); + + // Use int32 specialization if the operation doesn't overflow on its + // constant operands and if the operation has never overflowed. + if (!seenDouble && !constantDoubleResult(alloc)) + setInt32Specialization(); + } +} + +bool +MBinaryArithInstruction::constantDoubleResult(TempAllocator& alloc) +{ + bool typeChange = false; + EvaluateConstantOperands(alloc, this, &typeChange); + return typeChange; +} + +MDefinition* +MRsh::foldsTo(TempAllocator& alloc) +{ + MDefinition* f = MBinaryBitwiseInstruction::foldsTo(alloc); + + if (f != this) + return f; + + MDefinition* lhs = getOperand(0); + MDefinition* rhs = getOperand(1); + + if (!lhs->isLsh() || !rhs->isConstant() || rhs->type() != MIRType::Int32) + return this; + + if (!lhs->getOperand(1)->isConstant() || lhs->getOperand(1)->type() != MIRType::Int32) + return this; + + uint32_t shift = rhs->toConstant()->toInt32(); + uint32_t shift_lhs = lhs->getOperand(1)->toConstant()->toInt32(); + if (shift != shift_lhs) + return this; + + switch (shift) { + case 16: + return MSignExtend::New(alloc, lhs->getOperand(0), MSignExtend::Half); + case 24: + return MSignExtend::New(alloc, lhs->getOperand(0), MSignExtend::Byte); + } + + return this; +} + +MDefinition* +MBinaryArithInstruction::foldsTo(TempAllocator& alloc) +{ + if (specialization_ == MIRType::None) + return this; + + if (specialization_ == MIRType::Int64) + return this; + + MDefinition* lhs = getOperand(0); + MDefinition* rhs = getOperand(1); + if (MConstant* folded = EvaluateConstantOperands(alloc, this)) { + if (isTruncated()) { + if (!folded->block()) + block()->insertBefore(this, folded); + return MTruncateToInt32::New(alloc, folded); + } + return folded; + } + + if (mustPreserveNaN_) + return this; + + // 0 + -0 = 0. So we can't remove addition + if (isAdd() && specialization_ != MIRType::Int32) + return this; + + if (IsConstant(rhs, getIdentity())) { + if (isTruncated()) + return MTruncateToInt32::New(alloc, lhs); + return lhs; + } + + // subtraction isn't commutative. So we can't remove subtraction when lhs equals 0 + if (isSub()) + return this; + + if (IsConstant(lhs, getIdentity())) { + if (isTruncated()) + return MTruncateToInt32::New(alloc, rhs); + return rhs; // x op id => x + } + + return this; +} + +void +MFilterTypeSet::trySpecializeFloat32(TempAllocator& alloc) +{ + MDefinition* in = input(); + if (in->type() != MIRType::Float32) + return; + + setResultType(MIRType::Float32); +} + +bool +MFilterTypeSet::canProduceFloat32() const +{ + // A FilterTypeSet should be a producer if the input is a producer too. + // Also, be overly conservative by marking as not float32 producer when the + // input is a phi, as phis can be cyclic (phiA -> FilterTypeSet -> phiB -> + // phiA) and FilterTypeSet doesn't belong in the Float32 phi analysis. + return !input()->isPhi() && input()->canProduceFloat32(); +} + +bool +MFilterTypeSet::canConsumeFloat32(MUse* operand) const +{ + MOZ_ASSERT(getUseFor(0) == operand); + // A FilterTypeSet should be a consumer if all uses are consumer. See also + // comment below MFilterTypeSet::canProduceFloat32. + bool allConsumerUses = true; + for (MUseDefIterator use(this); allConsumerUses && use; use++) + allConsumerUses &= !use.def()->isPhi() && use.def()->canConsumeFloat32(use.use()); + return allConsumerUses; +} + +void +MBinaryArithInstruction::trySpecializeFloat32(TempAllocator& alloc) +{ + // Do not use Float32 if we can use int32. + if (specialization_ == MIRType::Int32) + return; + if (specialization_ == MIRType::None) + return; + + MDefinition* left = lhs(); + MDefinition* right = rhs(); + + if (!left->canProduceFloat32() || !right->canProduceFloat32() || + !CheckUsesAreFloat32Consumers(this)) + { + if (left->type() == MIRType::Float32) + ConvertDefinitionToDouble<0>(alloc, left, this); + if (right->type() == MIRType::Float32) + ConvertDefinitionToDouble<1>(alloc, right, this); + return; + } + + specialization_ = MIRType::Float32; + setResultType(MIRType::Float32); +} + +void +MMinMax::trySpecializeFloat32(TempAllocator& alloc) +{ + if (specialization_ == MIRType::Int32) + return; + + MDefinition* left = lhs(); + MDefinition* right = rhs(); + + if (!(left->canProduceFloat32() || (left->isMinMax() && left->type() == MIRType::Float32)) || + !(right->canProduceFloat32() || (right->isMinMax() && right->type() == MIRType::Float32))) + { + if (left->type() == MIRType::Float32) + ConvertDefinitionToDouble<0>(alloc, left, this); + if (right->type() == MIRType::Float32) + ConvertDefinitionToDouble<1>(alloc, right, this); + return; + } + + specialization_ = MIRType::Float32; + setResultType(MIRType::Float32); +} + +MDefinition* +MMinMax::foldsTo(TempAllocator& alloc) +{ + if (!lhs()->isConstant() && !rhs()->isConstant()) + return this; + + // Directly apply math utility to compare the rhs() and lhs() when + // they are both constants. + if (lhs()->isConstant() && rhs()->isConstant()) { + if (!lhs()->toConstant()->isTypeRepresentableAsDouble() || + !rhs()->toConstant()->isTypeRepresentableAsDouble()) + { + return this; + } + + double lnum = lhs()->toConstant()->numberToDouble(); + double rnum = rhs()->toConstant()->numberToDouble(); + + double result; + if (isMax()) + result = js::math_max_impl(lnum, rnum); + else + result = js::math_min_impl(lnum, rnum); + + // The folded MConstant should maintain the same MIRType with + // the original MMinMax. + if (type() == MIRType::Int32) { + int32_t cast; + if (mozilla::NumberEqualsInt32(result, &cast)) + return MConstant::New(alloc, Int32Value(cast)); + } else if (type() == MIRType::Float32) { + return MConstant::New(alloc, wasm::RawF32(float(result))); + } else { + MOZ_ASSERT(type() == MIRType::Double); + return MConstant::New(alloc, wasm::RawF64(result)); + } + } + + MDefinition* operand = lhs()->isConstant() ? rhs() : lhs(); + MConstant* constant = lhs()->isConstant() ? lhs()->toConstant() : rhs()->toConstant(); + + if (operand->isToDouble() && operand->getOperand(0)->type() == MIRType::Int32) { + // min(int32, cte >= INT32_MAX) = int32 + if (!isMax() && + constant->isTypeRepresentableAsDouble() && + constant->numberToDouble() >= INT32_MAX) + { + MLimitedTruncate* limit = + MLimitedTruncate::New(alloc, operand->getOperand(0), MDefinition::NoTruncate); + block()->insertBefore(this, limit); + MToDouble* toDouble = MToDouble::New(alloc, limit); + return toDouble; + } + + // max(int32, cte <= INT32_MIN) = int32 + if (isMax() && + constant->isTypeRepresentableAsDouble() && + constant->numberToDouble() <= INT32_MIN) + { + MLimitedTruncate* limit = + MLimitedTruncate::New(alloc, operand->getOperand(0), MDefinition::NoTruncate); + block()->insertBefore(this, limit); + MToDouble* toDouble = MToDouble::New(alloc, limit); + return toDouble; + } + } + return this; +} + +MDefinition* +MPow::foldsTo(TempAllocator& alloc) +{ + if (!power()->isConstant() || !power()->toConstant()->isTypeRepresentableAsDouble()) + return this; + + double pow = power()->toConstant()->numberToDouble(); + MIRType outputType = type(); + + // Math.pow(x, 0.5) is a sqrt with edge-case detection. + if (pow == 0.5) + return MPowHalf::New(alloc, input()); + + // Math.pow(x, -0.5) == 1 / Math.pow(x, 0.5), even for edge cases. + if (pow == -0.5) { + MPowHalf* half = MPowHalf::New(alloc, input()); + block()->insertBefore(this, half); + MConstant* one = MConstant::New(alloc, DoubleValue(1.0)); + block()->insertBefore(this, one); + return MDiv::New(alloc, one, half, MIRType::Double); + } + + // Math.pow(x, 1) == x. + if (pow == 1.0) + return input(); + + // Math.pow(x, 2) == x*x. + if (pow == 2.0) + return MMul::New(alloc, input(), input(), outputType); + + // Math.pow(x, 3) == x*x*x. + if (pow == 3.0) { + MMul* mul1 = MMul::New(alloc, input(), input(), outputType); + block()->insertBefore(this, mul1); + return MMul::New(alloc, input(), mul1, outputType); + } + + // Math.pow(x, 4) == y*y, where y = x*x. + if (pow == 4.0) { + MMul* y = MMul::New(alloc, input(), input(), outputType); + block()->insertBefore(this, y); + return MMul::New(alloc, y, y, outputType); + } + + return this; +} + +bool +MAbs::fallible() const +{ + return !implicitTruncate_ && (!range() || !range()->hasInt32Bounds()); +} + +void +MAbs::trySpecializeFloat32(TempAllocator& alloc) +{ + // Do not use Float32 if we can use int32. + if (input()->type() == MIRType::Int32) + return; + + if (!input()->canProduceFloat32() || !CheckUsesAreFloat32Consumers(this)) { + if (input()->type() == MIRType::Float32) + ConvertDefinitionToDouble<0>(alloc, input(), this); + return; + } + + setResultType(MIRType::Float32); + specialization_ = MIRType::Float32; +} + +MDefinition* +MDiv::foldsTo(TempAllocator& alloc) +{ + if (specialization_ == MIRType::None) + return this; + + if (specialization_ == MIRType::Int64) + return this; + + if (MDefinition* folded = EvaluateConstantOperands(alloc, this)) + return folded; + + if (MDefinition* folded = EvaluateExactReciprocal(alloc, this)) + return folded; + + return this; +} + +void +MDiv::analyzeEdgeCasesForward() +{ + // This is only meaningful when doing integer division. + if (specialization_ != MIRType::Int32) + return; + + MOZ_ASSERT(lhs()->type() == MIRType::Int32); + MOZ_ASSERT(rhs()->type() == MIRType::Int32); + + // Try removing divide by zero check + if (rhs()->isConstant() && !rhs()->toConstant()->isInt32(0)) + canBeDivideByZero_ = false; + + // If lhs is a constant int != INT32_MIN, then + // negative overflow check can be skipped. + if (lhs()->isConstant() && !lhs()->toConstant()->isInt32(INT32_MIN)) + canBeNegativeOverflow_ = false; + + // If rhs is a constant int != -1, likewise. + if (rhs()->isConstant() && !rhs()->toConstant()->isInt32(-1)) + canBeNegativeOverflow_ = false; + + // If lhs is != 0, then negative zero check can be skipped. + if (lhs()->isConstant() && !lhs()->toConstant()->isInt32(0)) + setCanBeNegativeZero(false); + + // If rhs is >= 0, likewise. + if (rhs()->isConstant() && rhs()->type() == MIRType::Int32) { + if (rhs()->toConstant()->toInt32() >= 0) + setCanBeNegativeZero(false); + } +} + +void +MDiv::analyzeEdgeCasesBackward() +{ + if (canBeNegativeZero() && !NeedNegativeZeroCheck(this)) + setCanBeNegativeZero(false); +} + +bool +MDiv::fallible() const +{ + return !isTruncated(); +} + +MDefinition* +MMod::foldsTo(TempAllocator& alloc) +{ + if (specialization_ == MIRType::None) + return this; + + if (specialization_ == MIRType::Int64) + return this; + + if (MDefinition* folded = EvaluateConstantOperands(alloc, this)) + return folded; + + return this; +} + +void +MMod::analyzeEdgeCasesForward() +{ + // These optimizations make sense only for integer division + if (specialization_ != MIRType::Int32) + return; + + if (rhs()->isConstant() && !rhs()->toConstant()->isInt32(0)) + canBeDivideByZero_ = false; + + if (rhs()->isConstant()) { + int32_t n = rhs()->toConstant()->toInt32(); + if (n > 0 && !IsPowerOfTwo(uint32_t(n))) + canBePowerOfTwoDivisor_ = false; + } +} + +bool +MMod::fallible() const +{ + return !isTruncated() && + (isUnsigned() || canBeDivideByZero() || canBeNegativeDividend()); +} + +void +MMathFunction::trySpecializeFloat32(TempAllocator& alloc) +{ + if (!input()->canProduceFloat32() || !CheckUsesAreFloat32Consumers(this)) { + if (input()->type() == MIRType::Float32) + ConvertDefinitionToDouble<0>(alloc, input(), this); + return; + } + + setResultType(MIRType::Float32); + specialization_ = MIRType::Float32; +} + +MHypot* MHypot::New(TempAllocator& alloc, const MDefinitionVector & vector) +{ + uint32_t length = vector.length(); + MHypot * hypot = new(alloc) MHypot; + if (!hypot->init(alloc, length)) + return nullptr; + + for (uint32_t i = 0; i < length; ++i) + hypot->initOperand(i, vector[i]); + return hypot; +} + +bool +MAdd::fallible() const +{ + // the add is fallible if range analysis does not say that it is finite, AND + // either the truncation analysis shows that there are non-truncated uses. + if (truncateKind() >= IndirectTruncate) + return false; + if (range() && range()->hasInt32Bounds()) + return false; + return true; +} + +bool +MSub::fallible() const +{ + // see comment in MAdd::fallible() + if (truncateKind() >= IndirectTruncate) + return false; + if (range() && range()->hasInt32Bounds()) + return false; + return true; +} + +MDefinition* +MMul::foldsTo(TempAllocator& alloc) +{ + MDefinition* out = MBinaryArithInstruction::foldsTo(alloc); + if (out != this) + return out; + + if (specialization() != MIRType::Int32) + return this; + + if (lhs() == rhs()) + setCanBeNegativeZero(false); + + return this; +} + +void +MMul::analyzeEdgeCasesForward() +{ + // Try to remove the check for negative zero + // This only makes sense when using the integer multiplication + if (specialization() != MIRType::Int32) + return; + + // If lhs is > 0, no need for negative zero check. + if (lhs()->isConstant() && lhs()->type() == MIRType::Int32) { + if (lhs()->toConstant()->toInt32() > 0) + setCanBeNegativeZero(false); + } + + // If rhs is > 0, likewise. + if (rhs()->isConstant() && rhs()->type() == MIRType::Int32) { + if (rhs()->toConstant()->toInt32() > 0) + setCanBeNegativeZero(false); + } +} + +void +MMul::analyzeEdgeCasesBackward() +{ + if (canBeNegativeZero() && !NeedNegativeZeroCheck(this)) + setCanBeNegativeZero(false); +} + +bool +MMul::updateForReplacement(MDefinition* ins_) +{ + MMul* ins = ins_->toMul(); + bool negativeZero = canBeNegativeZero() || ins->canBeNegativeZero(); + setCanBeNegativeZero(negativeZero); + // Remove the imul annotation when merging imul and normal multiplication. + if (mode_ == Integer && ins->mode() != Integer) + mode_ = Normal; + return true; +} + +bool +MMul::canOverflow() const +{ + if (isTruncated()) + return false; + return !range() || !range()->hasInt32Bounds(); +} + +bool +MUrsh::fallible() const +{ + if (bailoutsDisabled()) + return false; + return !range() || !range()->hasInt32Bounds(); +} + +static inline bool +SimpleArithOperand(MDefinition* op) +{ + return !op->mightBeType(MIRType::Object) + && !op->mightBeType(MIRType::String) + && !op->mightBeType(MIRType::Symbol) + && !op->mightBeType(MIRType::MagicOptimizedArguments) + && !op->mightBeType(MIRType::MagicHole) + && !op->mightBeType(MIRType::MagicIsConstructing); +} + +static bool +SafelyCoercesToDouble(MDefinition* op) +{ + // Strings and symbols are unhandled -- visitToDouble() doesn't support them yet. + // Null is unhandled -- ToDouble(null) == 0, but (0 == null) is false. + return SimpleArithOperand(op) && !op->mightBeType(MIRType::Null); +} + +MIRType +MCompare::inputType() +{ + switch(compareType_) { + case Compare_Undefined: + return MIRType::Undefined; + case Compare_Null: + return MIRType::Null; + case Compare_Boolean: + return MIRType::Boolean; + case Compare_UInt32: + case Compare_Int32: + case Compare_Int32MaybeCoerceBoth: + case Compare_Int32MaybeCoerceLHS: + case Compare_Int32MaybeCoerceRHS: + return MIRType::Int32; + case Compare_Double: + case Compare_DoubleMaybeCoerceLHS: + case Compare_DoubleMaybeCoerceRHS: + return MIRType::Double; + case Compare_Float32: + return MIRType::Float32; + case Compare_String: + case Compare_StrictString: + return MIRType::String; + case Compare_Object: + return MIRType::Object; + case Compare_Unknown: + case Compare_Bitwise: + return MIRType::Value; + default: + MOZ_CRASH("No known conversion"); + } +} + +static inline bool +MustBeUInt32(MDefinition* def, MDefinition** pwrapped) +{ + if (def->isUrsh()) { + *pwrapped = def->toUrsh()->lhs(); + MDefinition* rhs = def->toUrsh()->rhs(); + return def->toUrsh()->bailoutsDisabled() && + rhs->maybeConstantValue() && + rhs->maybeConstantValue()->isInt32(0); + } + + if (MConstant* defConst = def->maybeConstantValue()) { + *pwrapped = defConst; + return defConst->type() == MIRType::Int32 && defConst->toInt32() >= 0; + } + + *pwrapped = nullptr; // silence GCC warning + return false; +} + +/* static */ bool +MBinaryInstruction::unsignedOperands(MDefinition* left, MDefinition* right) +{ + MDefinition* replace; + if (!MustBeUInt32(left, &replace)) + return false; + if (replace->type() != MIRType::Int32) + return false; + if (!MustBeUInt32(right, &replace)) + return false; + if (replace->type() != MIRType::Int32) + return false; + return true; +} + +bool +MBinaryInstruction::unsignedOperands() +{ + return unsignedOperands(getOperand(0), getOperand(1)); +} + +void +MBinaryInstruction::replaceWithUnsignedOperands() +{ + MOZ_ASSERT(unsignedOperands()); + + for (size_t i = 0; i < numOperands(); i++) { + MDefinition* replace; + MustBeUInt32(getOperand(i), &replace); + if (replace == getOperand(i)) + continue; + + getOperand(i)->setImplicitlyUsedUnchecked(); + replaceOperand(i, replace); + } +} + +MCompare::CompareType +MCompare::determineCompareType(JSOp op, MDefinition* left, MDefinition* right) +{ + MIRType lhs = left->type(); + MIRType rhs = right->type(); + + bool looseEq = op == JSOP_EQ || op == JSOP_NE; + bool strictEq = op == JSOP_STRICTEQ || op == JSOP_STRICTNE; + bool relationalEq = !(looseEq || strictEq); + + // Comparisons on unsigned integers may be treated as UInt32. + if (unsignedOperands(left, right)) + return Compare_UInt32; + + // Integer to integer or boolean to boolean comparisons may be treated as Int32. + if ((lhs == MIRType::Int32 && rhs == MIRType::Int32) || + (lhs == MIRType::Boolean && rhs == MIRType::Boolean)) + { + return Compare_Int32MaybeCoerceBoth; + } + + // Loose/relational cross-integer/boolean comparisons may be treated as Int32. + if (!strictEq && + (lhs == MIRType::Int32 || lhs == MIRType::Boolean) && + (rhs == MIRType::Int32 || rhs == MIRType::Boolean)) + { + return Compare_Int32MaybeCoerceBoth; + } + + // Numeric comparisons against a double coerce to double. + if (IsTypeRepresentableAsDouble(lhs) && IsTypeRepresentableAsDouble(rhs)) + return Compare_Double; + + // Any comparison is allowed except strict eq. + if (!strictEq && IsFloatingPointType(rhs) && SafelyCoercesToDouble(left)) + return Compare_DoubleMaybeCoerceLHS; + if (!strictEq && IsFloatingPointType(lhs) && SafelyCoercesToDouble(right)) + return Compare_DoubleMaybeCoerceRHS; + + // Handle object comparison. + if (!relationalEq && lhs == MIRType::Object && rhs == MIRType::Object) + return Compare_Object; + + // Handle string comparisons. (Relational string compares are still unsupported). + if (!relationalEq && lhs == MIRType::String && rhs == MIRType::String) + return Compare_String; + + // Handle strict string compare. + if (strictEq && lhs == MIRType::String) + return Compare_StrictString; + if (strictEq && rhs == MIRType::String) + return Compare_StrictString; + + // Handle compare with lhs or rhs being Undefined or Null. + if (!relationalEq && IsNullOrUndefined(lhs)) + return (lhs == MIRType::Null) ? Compare_Null : Compare_Undefined; + if (!relationalEq && IsNullOrUndefined(rhs)) + return (rhs == MIRType::Null) ? Compare_Null : Compare_Undefined; + + // Handle strict comparison with lhs/rhs being typed Boolean. + if (strictEq && (lhs == MIRType::Boolean || rhs == MIRType::Boolean)) { + // bool/bool case got an int32 specialization earlier. + MOZ_ASSERT(!(lhs == MIRType::Boolean && rhs == MIRType::Boolean)); + return Compare_Boolean; + } + + return Compare_Unknown; +} + +void +MCompare::cacheOperandMightEmulateUndefined(CompilerConstraintList* constraints) +{ + MOZ_ASSERT(operandMightEmulateUndefined()); + + if (getOperand(0)->maybeEmulatesUndefined(constraints)) + return; + if (getOperand(1)->maybeEmulatesUndefined(constraints)) + return; + + markNoOperandEmulatesUndefined(); +} + +MBitNot* +MBitNot::NewInt32(TempAllocator& alloc, MDefinition* input) +{ + MBitNot* ins = new(alloc) MBitNot(input); + ins->specialization_ = MIRType::Int32; + MOZ_ASSERT(ins->type() == MIRType::Int32); + return ins; +} + +MDefinition* +MBitNot::foldsTo(TempAllocator& alloc) +{ + if (specialization_ != MIRType::Int32) + return this; + + MDefinition* input = getOperand(0); + + if (input->isConstant()) { + js::Value v = Int32Value(~(input->toConstant()->toInt32())); + return MConstant::New(alloc, v); + } + + if (input->isBitNot() && input->toBitNot()->specialization_ == MIRType::Int32) { + MOZ_ASSERT(input->toBitNot()->getOperand(0)->type() == MIRType::Int32); + return MTruncateToInt32::New(alloc, input->toBitNot()->input()); // ~~x => x | 0 + } + + return this; +} + +MDefinition* +MTypeOf::foldsTo(TempAllocator& alloc) +{ + // Note: we can't use input->type() here, type analysis has + // boxed the input. + MOZ_ASSERT(input()->type() == MIRType::Value); + + JSType type; + + switch (inputType()) { + case MIRType::Double: + case MIRType::Float32: + case MIRType::Int32: + type = JSTYPE_NUMBER; + break; + case MIRType::String: + type = JSTYPE_STRING; + break; + case MIRType::Symbol: + type = JSTYPE_SYMBOL; + break; + case MIRType::Null: + type = JSTYPE_OBJECT; + break; + case MIRType::Undefined: + type = JSTYPE_VOID; + break; + case MIRType::Boolean: + type = JSTYPE_BOOLEAN; + break; + case MIRType::Object: + if (!inputMaybeCallableOrEmulatesUndefined()) { + // Object is not callable and does not emulate undefined, so it's + // safe to fold to "object". + type = JSTYPE_OBJECT; + break; + } + MOZ_FALLTHROUGH; + default: + return this; + } + + return MConstant::New(alloc, StringValue(TypeName(type, GetJitContext()->runtime->names()))); +} + +void +MTypeOf::cacheInputMaybeCallableOrEmulatesUndefined(CompilerConstraintList* constraints) +{ + MOZ_ASSERT(inputMaybeCallableOrEmulatesUndefined()); + + if (!input()->maybeEmulatesUndefined(constraints) && !MaybeCallable(constraints, input())) + markInputNotCallableOrEmulatesUndefined(); +} + +MBitAnd* +MBitAnd::New(TempAllocator& alloc, MDefinition* left, MDefinition* right) +{ + return new(alloc) MBitAnd(left, right, MIRType::Int32); +} + +MBitAnd* +MBitAnd::New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type) +{ + MBitAnd* ins = new(alloc) MBitAnd(left, right, type); + ins->specializeAs(type); + return ins; +} + +MBitOr* +MBitOr::New(TempAllocator& alloc, MDefinition* left, MDefinition* right) +{ + return new(alloc) MBitOr(left, right, MIRType::Int32); +} + +MBitOr* +MBitOr::New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type) +{ + MBitOr* ins = new(alloc) MBitOr(left, right, type); + ins->specializeAs(type); + return ins; +} + +MBitXor* +MBitXor::New(TempAllocator& alloc, MDefinition* left, MDefinition* right) +{ + return new(alloc) MBitXor(left, right, MIRType::Int32); +} + +MBitXor* +MBitXor::New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type) +{ + MBitXor* ins = new(alloc) MBitXor(left, right, type); + ins->specializeAs(type); + return ins; +} + +MLsh* +MLsh::New(TempAllocator& alloc, MDefinition* left, MDefinition* right) +{ + return new(alloc) MLsh(left, right, MIRType::Int32); +} + +MLsh* +MLsh::New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type) +{ + MLsh* ins = new(alloc) MLsh(left, right, type); + ins->specializeAs(type); + return ins; +} + +MRsh* +MRsh::New(TempAllocator& alloc, MDefinition* left, MDefinition* right) +{ + return new(alloc) MRsh(left, right, MIRType::Int32); +} + +MRsh* +MRsh::New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type) +{ + MRsh* ins = new(alloc) MRsh(left, right, type); + ins->specializeAs(type); + return ins; +} + +MUrsh* +MUrsh::New(TempAllocator& alloc, MDefinition* left, MDefinition* right) +{ + return new(alloc) MUrsh(left, right, MIRType::Int32); +} + +MUrsh* +MUrsh::New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type) +{ + MUrsh* ins = new(alloc) MUrsh(left, right, type); + ins->specializeAs(type); + + // Since Ion has no UInt32 type, we use Int32 and we have a special + // exception to the type rules: we can return values in + // (INT32_MIN,UINT32_MAX] and still claim that we have an Int32 type + // without bailing out. This is necessary because Ion has no UInt32 + // type and we can't have bailouts in wasm code. + ins->bailoutsDisabled_ = true; + + return ins; +} + +MResumePoint* +MResumePoint::New(TempAllocator& alloc, MBasicBlock* block, jsbytecode* pc, + Mode mode) +{ + MResumePoint* resume = new(alloc) MResumePoint(block, pc, mode); + if (!resume->init(alloc)) { + block->discardPreAllocatedResumePoint(resume); + return nullptr; + } + resume->inherit(block); + return resume; +} + +MResumePoint* +MResumePoint::New(TempAllocator& alloc, MBasicBlock* block, MResumePoint* model, + const MDefinitionVector& operands) +{ + MResumePoint* resume = new(alloc) MResumePoint(block, model->pc(), model->mode()); + + // Allocate the same number of operands as the original resume point, and + // copy operands from the operands vector and not the not from the current + // block stack. + if (!resume->operands_.init(alloc, model->numAllocatedOperands())) { + block->discardPreAllocatedResumePoint(resume); + return nullptr; + } + + // Copy the operands. + for (size_t i = 0; i < operands.length(); i++) + resume->initOperand(i, operands[i]); + + return resume; +} + +MResumePoint* +MResumePoint::Copy(TempAllocator& alloc, MResumePoint* src) +{ + MResumePoint* resume = new(alloc) MResumePoint(src->block(), src->pc(), + src->mode()); + // Copy the operands from the original resume point, and not from the + // current block stack. + if (!resume->operands_.init(alloc, src->numAllocatedOperands())) { + src->block()->discardPreAllocatedResumePoint(resume); + return nullptr; + } + + // Copy the operands. + for (size_t i = 0; i < resume->numOperands(); i++) + resume->initOperand(i, src->getOperand(i)); + return resume; +} + +MResumePoint::MResumePoint(MBasicBlock* block, jsbytecode* pc, Mode mode) + : MNode(block), + pc_(pc), + instruction_(nullptr), + mode_(mode) +{ + block->addResumePoint(this); +} + +bool +MResumePoint::init(TempAllocator& alloc) +{ + return operands_.init(alloc, block()->stackDepth()); +} + +MResumePoint* +MResumePoint::caller() const +{ + return block_->callerResumePoint(); +} + +void +MResumePoint::inherit(MBasicBlock* block) +{ + // FixedList doesn't initialize its elements, so do unchecked inits. + for (size_t i = 0; i < stackDepth(); i++) + initOperand(i, block->getSlot(i)); +} + +void +MResumePoint::addStore(TempAllocator& alloc, MDefinition* store, const MResumePoint* cache) +{ + MOZ_ASSERT(block()->outerResumePoint() != this); + MOZ_ASSERT_IF(cache, !cache->stores_.empty()); + + if (cache && cache->stores_.begin()->operand == store) { + // If the last resume point had the same side-effect stack, then we can + // reuse the current side effect without cloning it. This is a simple + // way to share common context by making a spaghetti stack. + if (++cache->stores_.begin() == stores_.begin()) { + stores_.copy(cache->stores_); + return; + } + } + + // Ensure that the store would not be deleted by DCE. + MOZ_ASSERT(store->isEffectful()); + + MStoreToRecover* top = new(alloc) MStoreToRecover(store); + stores_.push(top); +} + +void +MResumePoint::dump(GenericPrinter& out) const +{ + out.printf("resumepoint mode="); + + switch (mode()) { + case MResumePoint::ResumeAt: + out.printf("At"); + break; + case MResumePoint::ResumeAfter: + out.printf("After"); + break; + case MResumePoint::Outer: + out.printf("Outer"); + break; + } + + if (MResumePoint* c = caller()) + out.printf(" (caller in block%u)", c->block()->id()); + + for (size_t i = 0; i < numOperands(); i++) { + out.printf(" "); + if (operands_[i].hasProducer()) + getOperand(i)->printName(out); + else + out.printf("(null)"); + } + out.printf("\n"); +} + +void +MResumePoint::dump() const +{ + Fprinter out(stderr); + dump(out); + out.finish(); +} + +bool +MResumePoint::isObservableOperand(MUse* u) const +{ + return isObservableOperand(indexOf(u)); +} + +bool +MResumePoint::isObservableOperand(size_t index) const +{ + return block()->info().isObservableSlot(index); +} + +bool +MResumePoint::isRecoverableOperand(MUse* u) const +{ + return block()->info().isRecoverableOperand(indexOf(u)); +} + +MDefinition* +MToInt32::foldsTo(TempAllocator& alloc) +{ + MDefinition* input = getOperand(0); + + // Fold this operation if the input operand is constant. + if (input->isConstant()) { + DebugOnly<MacroAssembler::IntConversionInputKind> convert = conversion(); + switch (input->type()) { + case MIRType::Null: + MOZ_ASSERT(convert == MacroAssembler::IntConversion_Any); + return MConstant::New(alloc, Int32Value(0)); + case MIRType::Boolean: + MOZ_ASSERT(convert == MacroAssembler::IntConversion_Any || + convert == MacroAssembler::IntConversion_NumbersOrBoolsOnly); + return MConstant::New(alloc, Int32Value(input->toConstant()->toBoolean())); + case MIRType::Int32: + return MConstant::New(alloc, Int32Value(input->toConstant()->toInt32())); + case MIRType::Float32: + case MIRType::Double: + int32_t ival; + // Only the value within the range of Int32 can be substituted as constant. + if (mozilla::NumberIsInt32(input->toConstant()->numberToDouble(), &ival)) + return MConstant::New(alloc, Int32Value(ival)); + break; + default: + break; + } + } + + // Do not fold the TruncateToInt32 node when the input is uint32 (e.g. ursh + // with a zero constant. Consider the test jit-test/tests/ion/bug1247880.js, + // where the relevant code is: |(imul(1, x >>> 0) % 2)|. The imul operator + // is folded to a MTruncateToInt32 node, which will result in this MIR: + // MMod(MTruncateToInt32(MUrsh(x, MConstant(0))), MConstant(2)). Note that + // the MUrsh node's type is int32 (since uint32 is not implemented), and + // that would fold the MTruncateToInt32 node. This will make the modulo + // unsigned, while is should have been signed. + if (input->type() == MIRType::Int32 && !IsUint32Type(input)) + return input; + + return this; +} + +void +MToInt32::analyzeEdgeCasesBackward() +{ + if (!NeedNegativeZeroCheck(this)) + setCanBeNegativeZero(false); +} + +MDefinition* +MTruncateToInt32::foldsTo(TempAllocator& alloc) +{ + MDefinition* input = getOperand(0); + if (input->isBox()) + input = input->getOperand(0); + + // Do not fold the TruncateToInt32 node when the input is uint32 (e.g. ursh + // with a zero constant. Consider the test jit-test/tests/ion/bug1247880.js, + // where the relevant code is: |(imul(1, x >>> 0) % 2)|. The imul operator + // is folded to a MTruncateToInt32 node, which will result in this MIR: + // MMod(MTruncateToInt32(MUrsh(x, MConstant(0))), MConstant(2)). Note that + // the MUrsh node's type is int32 (since uint32 is not implemented), and + // that would fold the MTruncateToInt32 node. This will make the modulo + // unsigned, while is should have been signed. + if (input->type() == MIRType::Int32 && !IsUint32Type(input)) + return input; + + if (input->type() == MIRType::Double && input->isConstant()) { + int32_t ret = ToInt32(input->toConstant()->toDouble()); + return MConstant::New(alloc, Int32Value(ret)); + } + + return this; +} + +MDefinition* +MWasmTruncateToInt32::foldsTo(TempAllocator& alloc) +{ + MDefinition* input = getOperand(0); + if (input->type() == MIRType::Int32) + return input; + + if (input->type() == MIRType::Double && input->isConstant()) { + double d = input->toConstant()->toDouble(); + if (IsNaN(d)) + return this; + + if (!isUnsigned_ && d <= double(INT32_MAX) && d >= double(INT32_MIN)) + return MConstant::New(alloc, Int32Value(ToInt32(d))); + + if (isUnsigned_ && d <= double(UINT32_MAX) && d >= 0) + return MConstant::New(alloc, Int32Value(ToInt32(d))); + } + + if (input->type() == MIRType::Float32 && input->isConstant()) { + double f = double(input->toConstant()->toFloat32()); + if (IsNaN(f)) + return this; + + if (!isUnsigned_ && f <= double(INT32_MAX) && f >= double(INT32_MIN)) + return MConstant::New(alloc, Int32Value(ToInt32(f))); + + if (isUnsigned_ && f <= double(UINT32_MAX) && f >= 0) + return MConstant::New(alloc, Int32Value(ToInt32(f))); + } + + return this; +} + +MDefinition* +MWrapInt64ToInt32::foldsTo(TempAllocator& alloc) +{ + MDefinition* input = this->input(); + if (input->isConstant()) { + uint64_t c = input->toConstant()->toInt64(); + int32_t output = bottomHalf() ? int32_t(c) : int32_t(c >> 32); + return MConstant::New(alloc, Int32Value(output)); + } + + return this; +} + +MDefinition* +MExtendInt32ToInt64::foldsTo(TempAllocator& alloc) +{ + MDefinition* input = this->input(); + if (input->isConstant()) { + int32_t c = input->toConstant()->toInt32(); + int64_t res = isUnsigned() ? int64_t(uint32_t(c)) : int64_t(c); + return MConstant::NewInt64(alloc, res); + } + + return this; +} + +MDefinition* +MToDouble::foldsTo(TempAllocator& alloc) +{ + MDefinition* input = getOperand(0); + if (input->isBox()) + input = input->getOperand(0); + + if (input->type() == MIRType::Double) + return input; + + if (input->isConstant() && input->toConstant()->isTypeRepresentableAsDouble()) { + double out = input->toConstant()->numberToDouble(); + return MConstant::New(alloc, wasm::RawF64(out)); + } + + return this; +} + +MDefinition* +MToFloat32::foldsTo(TempAllocator& alloc) +{ + MDefinition* input = getOperand(0); + if (input->isBox()) + input = input->getOperand(0); + + if (input->type() == MIRType::Float32) + return input; + + // If x is a Float32, Float32(Double(x)) == x + if (!mustPreserveNaN_ && + input->isToDouble() && + input->toToDouble()->input()->type() == MIRType::Float32) + { + return input->toToDouble()->input(); + } + + if (input->isConstant() && input->toConstant()->isTypeRepresentableAsDouble()) { + float out = float(input->toConstant()->numberToDouble()); + return MConstant::New(alloc, wasm::RawF32(out)); + } + + return this; +} + +MDefinition* +MToString::foldsTo(TempAllocator& alloc) +{ + MDefinition* in = input(); + if (in->isBox()) + in = in->getOperand(0); + + if (in->type() == MIRType::String) + return in; + return this; +} + +MDefinition* +MClampToUint8::foldsTo(TempAllocator& alloc) +{ + if (MConstant* inputConst = input()->maybeConstantValue()) { + if (inputConst->isTypeRepresentableAsDouble()) { + int32_t clamped = ClampDoubleToUint8(inputConst->numberToDouble()); + return MConstant::New(alloc, Int32Value(clamped)); + } + } + return this; +} + +bool +MCompare::tryFoldEqualOperands(bool* result) +{ + if (lhs() != rhs()) + return false; + + // Intuitively somebody would think that if lhs == rhs, + // then we can just return true. (Or false for !==) + // However NaN !== NaN is true! So we spend some time trying + // to eliminate this case. + + if (jsop() != JSOP_STRICTEQ && jsop() != JSOP_STRICTNE) + return false; + + if (compareType_ == Compare_Unknown) + return false; + + MOZ_ASSERT(compareType_ == Compare_Undefined || compareType_ == Compare_Null || + compareType_ == Compare_Boolean || compareType_ == Compare_Int32 || + compareType_ == Compare_Int32MaybeCoerceBoth || + compareType_ == Compare_Int32MaybeCoerceLHS || + compareType_ == Compare_Int32MaybeCoerceRHS || compareType_ == Compare_UInt32 || + compareType_ == Compare_Double || compareType_ == Compare_DoubleMaybeCoerceLHS || + compareType_ == Compare_DoubleMaybeCoerceRHS || compareType_ == Compare_Float32 || + compareType_ == Compare_String || compareType_ == Compare_StrictString || + compareType_ == Compare_Object || compareType_ == Compare_Bitwise); + + if (isDoubleComparison() || isFloat32Comparison()) { + if (!operandsAreNeverNaN()) + return false; + } + + lhs()->setGuardRangeBailoutsUnchecked(); + + *result = (jsop() == JSOP_STRICTEQ); + return true; +} + +bool +MCompare::tryFoldTypeOf(bool* result) +{ + if (!lhs()->isTypeOf() && !rhs()->isTypeOf()) + return false; + if (!lhs()->isConstant() && !rhs()->isConstant()) + return false; + + MTypeOf* typeOf = lhs()->isTypeOf() ? lhs()->toTypeOf() : rhs()->toTypeOf(); + MConstant* constant = lhs()->isConstant() ? lhs()->toConstant() : rhs()->toConstant(); + + if (constant->type() != MIRType::String) + return false; + + if (jsop() != JSOP_STRICTEQ && jsop() != JSOP_STRICTNE && + jsop() != JSOP_EQ && jsop() != JSOP_NE) + { + return false; + } + + const JSAtomState& names = GetJitContext()->runtime->names(); + if (constant->toString() == TypeName(JSTYPE_VOID, names)) { + if (!typeOf->input()->mightBeType(MIRType::Undefined) && + !typeOf->inputMaybeCallableOrEmulatesUndefined()) + { + *result = (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE); + return true; + } + } else if (constant->toString() == TypeName(JSTYPE_BOOLEAN, names)) { + if (!typeOf->input()->mightBeType(MIRType::Boolean)) { + *result = (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE); + return true; + } + } else if (constant->toString() == TypeName(JSTYPE_NUMBER, names)) { + if (!typeOf->input()->mightBeType(MIRType::Int32) && + !typeOf->input()->mightBeType(MIRType::Float32) && + !typeOf->input()->mightBeType(MIRType::Double)) + { + *result = (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE); + return true; + } + } else if (constant->toString() == TypeName(JSTYPE_STRING, names)) { + if (!typeOf->input()->mightBeType(MIRType::String)) { + *result = (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE); + return true; + } + } else if (constant->toString() == TypeName(JSTYPE_SYMBOL, names)) { + if (!typeOf->input()->mightBeType(MIRType::Symbol)) { + *result = (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE); + return true; + } + } else if (constant->toString() == TypeName(JSTYPE_OBJECT, names)) { + if (!typeOf->input()->mightBeType(MIRType::Object) && + !typeOf->input()->mightBeType(MIRType::Null)) + { + *result = (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE); + return true; + } + } else if (constant->toString() == TypeName(JSTYPE_FUNCTION, names)) { + if (!typeOf->inputMaybeCallableOrEmulatesUndefined()) { + *result = (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE); + return true; + } + } + + return false; +} + +bool +MCompare::tryFold(bool* result) +{ + JSOp op = jsop(); + + if (tryFoldEqualOperands(result)) + return true; + + if (tryFoldTypeOf(result)) + return true; + + if (compareType_ == Compare_Null || compareType_ == Compare_Undefined) { + // The LHS is the value we want to test against null or undefined. + if (op == JSOP_STRICTEQ || op == JSOP_STRICTNE) { + if (lhs()->type() == inputType()) { + *result = (op == JSOP_STRICTEQ); + return true; + } + if (!lhs()->mightBeType(inputType())) { + *result = (op == JSOP_STRICTNE); + return true; + } + } else { + MOZ_ASSERT(op == JSOP_EQ || op == JSOP_NE); + if (IsNullOrUndefined(lhs()->type())) { + *result = (op == JSOP_EQ); + return true; + } + if (!lhs()->mightBeType(MIRType::Null) && + !lhs()->mightBeType(MIRType::Undefined) && + !(lhs()->mightBeType(MIRType::Object) && operandMightEmulateUndefined())) + { + *result = (op == JSOP_NE); + return true; + } + } + return false; + } + + if (compareType_ == Compare_Boolean) { + MOZ_ASSERT(op == JSOP_STRICTEQ || op == JSOP_STRICTNE); + MOZ_ASSERT(rhs()->type() == MIRType::Boolean); + MOZ_ASSERT(lhs()->type() != MIRType::Boolean, "Should use Int32 comparison"); + + if (!lhs()->mightBeType(MIRType::Boolean)) { + *result = (op == JSOP_STRICTNE); + return true; + } + return false; + } + + if (compareType_ == Compare_StrictString) { + MOZ_ASSERT(op == JSOP_STRICTEQ || op == JSOP_STRICTNE); + MOZ_ASSERT(rhs()->type() == MIRType::String); + MOZ_ASSERT(lhs()->type() != MIRType::String, "Should use String comparison"); + + if (!lhs()->mightBeType(MIRType::String)) { + *result = (op == JSOP_STRICTNE); + return true; + } + return false; + } + + return false; +} + +template <typename T> +static bool +FoldComparison(JSOp op, T left, T right) +{ + switch (op) { + case JSOP_LT: return left < right; + case JSOP_LE: return left <= right; + case JSOP_GT: return left > right; + case JSOP_GE: return left >= right; + case JSOP_STRICTEQ: case JSOP_EQ: return left == right; + case JSOP_STRICTNE: case JSOP_NE: return left != right; + default: MOZ_CRASH("Unexpected op."); + } +} + +bool +MCompare::evaluateConstantOperands(TempAllocator& alloc, bool* result) +{ + if (type() != MIRType::Boolean && type() != MIRType::Int32) + return false; + + MDefinition* left = getOperand(0); + MDefinition* right = getOperand(1); + + if (compareType() == Compare_Double) { + // Optimize "MCompare MConstant (MToDouble SomethingInInt32Range). + // In most cases the MToDouble was added, because the constant is + // a double. + // e.g. v < 9007199254740991, where v is an int32 is always true. + if (!lhs()->isConstant() && !rhs()->isConstant()) + return false; + + MDefinition* operand = left->isConstant() ? right : left; + MConstant* constant = left->isConstant() ? left->toConstant() : right->toConstant(); + MOZ_ASSERT(constant->type() == MIRType::Double); + double cte = constant->toDouble(); + + if (operand->isToDouble() && operand->getOperand(0)->type() == MIRType::Int32) { + bool replaced = false; + switch (jsop_) { + case JSOP_LT: + if (cte > INT32_MAX || cte < INT32_MIN) { + *result = !((constant == lhs()) ^ (cte < INT32_MIN)); + replaced = true; + } + break; + case JSOP_LE: + if (constant == lhs()) { + if (cte > INT32_MAX || cte <= INT32_MIN) { + *result = (cte <= INT32_MIN); + replaced = true; + } + } else { + if (cte >= INT32_MAX || cte < INT32_MIN) { + *result = (cte >= INT32_MIN); + replaced = true; + } + } + break; + case JSOP_GT: + if (cte > INT32_MAX || cte < INT32_MIN) { + *result = !((constant == rhs()) ^ (cte < INT32_MIN)); + replaced = true; + } + break; + case JSOP_GE: + if (constant == lhs()) { + if (cte >= INT32_MAX || cte < INT32_MIN) { + *result = (cte >= INT32_MAX); + replaced = true; + } + } else { + if (cte > INT32_MAX || cte <= INT32_MIN) { + *result = (cte <= INT32_MIN); + replaced = true; + } + } + break; + case JSOP_STRICTEQ: // Fall through. + case JSOP_EQ: + if (cte > INT32_MAX || cte < INT32_MIN) { + *result = false; + replaced = true; + } + break; + case JSOP_STRICTNE: // Fall through. + case JSOP_NE: + if (cte > INT32_MAX || cte < INT32_MIN) { + *result = true; + replaced = true; + } + break; + default: + MOZ_CRASH("Unexpected op."); + } + if (replaced) { + MLimitedTruncate* limit = + MLimitedTruncate::New(alloc, operand->getOperand(0), MDefinition::NoTruncate); + limit->setGuardUnchecked(); + block()->insertBefore(this, limit); + return true; + } + } + } + + if (!left->isConstant() || !right->isConstant()) + return false; + + MConstant* lhs = left->toConstant(); + MConstant* rhs = right->toConstant(); + + // Fold away some String equality comparisons. + if (lhs->type() == MIRType::String && rhs->type() == MIRType::String) { + int32_t comp = 0; // Default to equal. + if (left != right) + comp = CompareAtoms(&lhs->toString()->asAtom(), &rhs->toString()->asAtom()); + *result = FoldComparison(jsop_, comp, 0); + return true; + } + + if (compareType_ == Compare_UInt32) { + *result = FoldComparison(jsop_, uint32_t(lhs->toInt32()), uint32_t(rhs->toInt32())); + return true; + } + + if (compareType_ == Compare_Int64) { + *result = FoldComparison(jsop_, lhs->toInt64(), rhs->toInt64()); + return true; + } + + if (compareType_ == Compare_UInt64) { + *result = FoldComparison(jsop_, uint64_t(lhs->toInt64()), uint64_t(rhs->toInt64())); + return true; + } + + if (lhs->isTypeRepresentableAsDouble() && rhs->isTypeRepresentableAsDouble()) { + *result = FoldComparison(jsop_, lhs->numberToDouble(), rhs->numberToDouble()); + return true; + } + + return false; +} + +MDefinition* +MCompare::foldsTo(TempAllocator& alloc) +{ + bool result; + + if (tryFold(&result) || evaluateConstantOperands(alloc, &result)) { + if (type() == MIRType::Int32) + return MConstant::New(alloc, Int32Value(result)); + + MOZ_ASSERT(type() == MIRType::Boolean); + return MConstant::New(alloc, BooleanValue(result)); + } + + return this; +} + +void +MCompare::trySpecializeFloat32(TempAllocator& alloc) +{ + MDefinition* lhs = getOperand(0); + MDefinition* rhs = getOperand(1); + + if (lhs->canProduceFloat32() && rhs->canProduceFloat32() && compareType_ == Compare_Double) { + compareType_ = Compare_Float32; + } else { + if (lhs->type() == MIRType::Float32) + ConvertDefinitionToDouble<0>(alloc, lhs, this); + if (rhs->type() == MIRType::Float32) + ConvertDefinitionToDouble<1>(alloc, rhs, this); + } +} + +void +MCompare::filtersUndefinedOrNull(bool trueBranch, MDefinition** subject, bool* filtersUndefined, + bool* filtersNull) +{ + *filtersNull = *filtersUndefined = false; + *subject = nullptr; + + if (compareType() != Compare_Undefined && compareType() != Compare_Null) + return; + + MOZ_ASSERT(jsop() == JSOP_STRICTNE || jsop() == JSOP_NE || + jsop() == JSOP_STRICTEQ || jsop() == JSOP_EQ); + + // JSOP_*NE only removes undefined/null from if/true branch + if (!trueBranch && (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE)) + return; + + // JSOP_*EQ only removes undefined/null from else/false branch + if (trueBranch && (jsop() == JSOP_STRICTEQ || jsop() == JSOP_EQ)) + return; + + if (jsop() == JSOP_STRICTEQ || jsop() == JSOP_STRICTNE) { + *filtersUndefined = compareType() == Compare_Undefined; + *filtersNull = compareType() == Compare_Null; + } else { + *filtersUndefined = *filtersNull = true; + } + + *subject = lhs(); +} + +void +MNot::cacheOperandMightEmulateUndefined(CompilerConstraintList* constraints) +{ + MOZ_ASSERT(operandMightEmulateUndefined()); + + if (!getOperand(0)->maybeEmulatesUndefined(constraints)) + markNoOperandEmulatesUndefined(); +} + +MDefinition* +MNot::foldsTo(TempAllocator& alloc) +{ + // Fold if the input is constant + if (MConstant* inputConst = input()->maybeConstantValue()) { + bool b; + if (inputConst->valueToBoolean(&b)) { + if (type() == MIRType::Int32 || type() == MIRType::Int64) + return MConstant::New(alloc, Int32Value(!b)); + return MConstant::New(alloc, BooleanValue(!b)); + } + } + + // If the operand of the Not is itself a Not, they cancel out. But we can't + // always convert Not(Not(x)) to x because that may loose the conversion to + // boolean. We can simplify Not(Not(Not(x))) to Not(x) though. + MDefinition* op = getOperand(0); + if (op->isNot()) { + MDefinition* opop = op->getOperand(0); + if (opop->isNot()) + return opop; + } + + // NOT of an undefined or null value is always true + if (input()->type() == MIRType::Undefined || input()->type() == MIRType::Null) + return MConstant::New(alloc, BooleanValue(true)); + + // NOT of a symbol is always false. + if (input()->type() == MIRType::Symbol) + return MConstant::New(alloc, BooleanValue(false)); + + // NOT of an object that can't emulate undefined is always false. + if (input()->type() == MIRType::Object && !operandMightEmulateUndefined()) + return MConstant::New(alloc, BooleanValue(false)); + + return this; +} + +void +MNot::trySpecializeFloat32(TempAllocator& alloc) +{ + MDefinition* in = input(); + if (!in->canProduceFloat32() && in->type() == MIRType::Float32) + ConvertDefinitionToDouble<0>(alloc, in, this); +} + +void +MBeta::printOpcode(GenericPrinter& out) const +{ + MDefinition::printOpcode(out); + + out.printf(" "); + comparison_->dump(out); +} + +bool +MCreateThisWithTemplate::canRecoverOnBailout() const +{ + MOZ_ASSERT(templateObject()->is<PlainObject>() || templateObject()->is<UnboxedPlainObject>()); + MOZ_ASSERT_IF(templateObject()->is<PlainObject>(), + !templateObject()->as<PlainObject>().denseElementsAreCopyOnWrite()); + return true; +} + +bool +OperandIndexMap::init(TempAllocator& alloc, JSObject* templateObject) +{ + const UnboxedLayout& layout = + templateObject->as<UnboxedPlainObject>().layoutDontCheckGeneration(); + + const UnboxedLayout::PropertyVector& properties = layout.properties(); + MOZ_ASSERT(properties.length() < 255); + + // Allocate an array of indexes, where the top of each field correspond to + // the index of the operand in the MObjectState instance. + if (!map.init(alloc, layout.size())) + return false; + + // Reset all indexes to 0, which is an error code. + for (size_t i = 0; i < map.length(); i++) + map[i] = 0; + + // Map the property offsets to the indexes of MObjectState operands. + uint8_t index = 1; + for (size_t i = 0; i < properties.length(); i++, index++) + map[properties[i].offset] = index; + + return true; +} + +MObjectState::MObjectState(MObjectState* state) + : numSlots_(state->numSlots_), + numFixedSlots_(state->numFixedSlots_), + operandIndex_(state->operandIndex_) +{ + // This instruction is only used as a summary for bailout paths. + setResultType(MIRType::Object); + setRecoveredOnBailout(); +} + +MObjectState::MObjectState(JSObject *templateObject, OperandIndexMap* operandIndex) +{ + // This instruction is only used as a summary for bailout paths. + setResultType(MIRType::Object); + setRecoveredOnBailout(); + + if (templateObject->is<NativeObject>()) { + NativeObject* nativeObject = &templateObject->as<NativeObject>(); + numSlots_ = nativeObject->slotSpan(); + numFixedSlots_ = nativeObject->numFixedSlots(); + } else { + const UnboxedLayout& layout = + templateObject->as<UnboxedPlainObject>().layoutDontCheckGeneration(); + // Same as UnboxedLayout::makeNativeGroup + numSlots_ = layout.properties().length(); + numFixedSlots_ = gc::GetGCKindSlots(layout.getAllocKind()); + } + + operandIndex_ = operandIndex; +} + +JSObject* +MObjectState::templateObjectOf(MDefinition* obj) +{ + if (obj->isNewObject()) + return obj->toNewObject()->templateObject(); + else if (obj->isCreateThisWithTemplate()) + return obj->toCreateThisWithTemplate()->templateObject(); + else + return obj->toNewCallObject()->templateObject(); + + return nullptr; +} + +bool +MObjectState::init(TempAllocator& alloc, MDefinition* obj) +{ + if (!MVariadicInstruction::init(alloc, numSlots() + 1)) + return false; + // +1, for the Object. + initOperand(0, obj); + return true; +} + +bool +MObjectState::initFromTemplateObject(TempAllocator& alloc, MDefinition* undefinedVal) +{ + JSObject* templateObject = templateObjectOf(object()); + + // Initialize all the slots of the object state with the value contained in + // the template object. This is needed to account values which are baked in + // the template objects and not visible in IonMonkey, such as the + // uninitialized-lexical magic value of call objects. + if (templateObject->is<UnboxedPlainObject>()) { + UnboxedPlainObject& unboxedObject = templateObject->as<UnboxedPlainObject>(); + const UnboxedLayout& layout = unboxedObject.layoutDontCheckGeneration(); + const UnboxedLayout::PropertyVector& properties = layout.properties(); + + for (size_t i = 0; i < properties.length(); i++) { + Value val = unboxedObject.getValue(properties[i], /* maybeUninitialized = */ true); + MDefinition *def = undefinedVal; + if (!val.isUndefined()) { + MConstant* ins = val.isObject() ? + MConstant::NewConstraintlessObject(alloc, &val.toObject()) : + MConstant::New(alloc, val); + block()->insertBefore(this, ins); + def = ins; + } + initSlot(i, def); + } + } else { + NativeObject& nativeObject = templateObject->as<NativeObject>(); + MOZ_ASSERT(nativeObject.slotSpan() == numSlots()); + + for (size_t i = 0; i < numSlots(); i++) { + Value val = nativeObject.getSlot(i); + MDefinition *def = undefinedVal; + if (!val.isUndefined()) { + MConstant* ins = val.isObject() ? + MConstant::NewConstraintlessObject(alloc, &val.toObject()) : + MConstant::New(alloc, val); + block()->insertBefore(this, ins); + def = ins; + } + initSlot(i, def); + } + } + return true; +} + +MObjectState* +MObjectState::New(TempAllocator& alloc, MDefinition* obj) +{ + JSObject* templateObject = templateObjectOf(obj); + MOZ_ASSERT(templateObject, "Unexpected object creation."); + + OperandIndexMap* operandIndex = nullptr; + if (templateObject->is<UnboxedPlainObject>()) { + operandIndex = new(alloc) OperandIndexMap; + if (!operandIndex || !operandIndex->init(alloc, templateObject)) + return nullptr; + } + + MObjectState* res = new(alloc) MObjectState(templateObject, operandIndex); + if (!res || !res->init(alloc, obj)) + return nullptr; + return res; +} + +MObjectState* +MObjectState::Copy(TempAllocator& alloc, MObjectState* state) +{ + MObjectState* res = new(alloc) MObjectState(state); + if (!res || !res->init(alloc, state->object())) + return nullptr; + for (size_t i = 0; i < res->numSlots(); i++) + res->initSlot(i, state->getSlot(i)); + return res; +} + +MArrayState::MArrayState(MDefinition* arr) +{ + // This instruction is only used as a summary for bailout paths. + setResultType(MIRType::Object); + setRecoveredOnBailout(); + numElements_ = arr->toNewArray()->length(); +} + +bool +MArrayState::init(TempAllocator& alloc, MDefinition* obj, MDefinition* len) +{ + if (!MVariadicInstruction::init(alloc, numElements() + 2)) + return false; + // +1, for the Array object. + initOperand(0, obj); + // +1, for the length value of the array. + initOperand(1, len); + return true; +} + +MArrayState* +MArrayState::New(TempAllocator& alloc, MDefinition* arr, MDefinition* undefinedVal, + MDefinition* initLength) +{ + MArrayState* res = new(alloc) MArrayState(arr); + if (!res || !res->init(alloc, arr, initLength)) + return nullptr; + for (size_t i = 0; i < res->numElements(); i++) + res->initElement(i, undefinedVal); + return res; +} + +MArrayState* +MArrayState::Copy(TempAllocator& alloc, MArrayState* state) +{ + MDefinition* arr = state->array(); + MDefinition* len = state->initializedLength(); + MArrayState* res = new(alloc) MArrayState(arr); + if (!res || !res->init(alloc, arr, len)) + return nullptr; + for (size_t i = 0; i < res->numElements(); i++) + res->initElement(i, state->getElement(i)); + return res; +} + +MNewArray::MNewArray(CompilerConstraintList* constraints, uint32_t length, MConstant* templateConst, + gc::InitialHeap initialHeap, jsbytecode* pc, bool vmCall) + : MUnaryInstruction(templateConst), + length_(length), + initialHeap_(initialHeap), + convertDoubleElements_(false), + pc_(pc), + vmCall_(vmCall) +{ + setResultType(MIRType::Object); + if (templateObject()) { + if (TemporaryTypeSet* types = MakeSingletonTypeSet(constraints, templateObject())) { + setResultTypeSet(types); + if (types->convertDoubleElements(constraints) == TemporaryTypeSet::AlwaysConvertToDoubles) + convertDoubleElements_ = true; + } + } +} + +MDefinition::AliasType +MLoadFixedSlot::mightAlias(const MDefinition* def) const +{ + if (def->isStoreFixedSlot()) { + const MStoreFixedSlot* store = def->toStoreFixedSlot(); + if (store->slot() != slot()) + return AliasType::NoAlias; + if (store->object() != object()) + return AliasType::MayAlias; + return AliasType::MustAlias; + } + return AliasType::MayAlias; +} + +MDefinition* +MLoadFixedSlot::foldsTo(TempAllocator& alloc) +{ + if (MDefinition* def = foldsToStore(alloc)) + return def; + + return this; +} + +MDefinition::AliasType +MLoadFixedSlotAndUnbox::mightAlias(const MDefinition* def) const +{ + if (def->isStoreFixedSlot()) { + const MStoreFixedSlot* store = def->toStoreFixedSlot(); + if (store->slot() != slot()) + return AliasType::NoAlias; + if (store->object() != object()) + return AliasType::MayAlias; + return AliasType::MustAlias; + } + return AliasType::MayAlias; +} + +MDefinition* +MLoadFixedSlotAndUnbox::foldsTo(TempAllocator& alloc) +{ + if (MDefinition* def = foldsToStore(alloc)) + return def; + + return this; +} + +MDefinition* +MWasmAddOffset::foldsTo(TempAllocator& alloc) +{ + MDefinition* baseArg = base(); + if (!baseArg->isConstant()) + return this; + + MOZ_ASSERT(baseArg->type() == MIRType::Int32); + CheckedInt<uint32_t> ptr = baseArg->toConstant()->toInt32(); + + ptr += offset(); + + if (!ptr.isValid()) + return this; + + return MConstant::New(alloc, Int32Value(ptr.value())); +} + +MDefinition::AliasType +MAsmJSLoadHeap::mightAlias(const MDefinition* def) const +{ + if (def->isAsmJSStoreHeap()) { + const MAsmJSStoreHeap* store = def->toAsmJSStoreHeap(); + if (store->accessType() != accessType()) + return AliasType::MayAlias; + if (!base()->isConstant() || !store->base()->isConstant()) + return AliasType::MayAlias; + const MConstant* otherBase = store->base()->toConstant(); + if (base()->toConstant()->equals(otherBase) && offset() == store->offset()) + return AliasType::MayAlias; + return AliasType::NoAlias; + } + return AliasType::MayAlias; +} + +bool +MAsmJSLoadHeap::congruentTo(const MDefinition* ins) const +{ + if (!ins->isAsmJSLoadHeap()) + return false; + const MAsmJSLoadHeap* load = ins->toAsmJSLoadHeap(); + return load->accessType() == accessType() && + load->offset() == offset() && + congruentIfOperandsEqual(load); +} + +MDefinition::AliasType +MWasmLoadGlobalVar::mightAlias(const MDefinition* def) const +{ + if (def->isWasmStoreGlobalVar()) { + const MWasmStoreGlobalVar* store = def->toWasmStoreGlobalVar(); + // Global variables can't alias each other or be type-reinterpreted. + return (store->globalDataOffset() == globalDataOffset_) ? AliasType::MayAlias : + AliasType::NoAlias; + } + return AliasType::MayAlias; +} + +HashNumber +MWasmLoadGlobalVar::valueHash() const +{ + HashNumber hash = MDefinition::valueHash(); + hash = addU32ToHash(hash, globalDataOffset_); + return hash; +} + +bool +MWasmLoadGlobalVar::congruentTo(const MDefinition* ins) const +{ + if (ins->isWasmLoadGlobalVar()) + return globalDataOffset_ == ins->toWasmLoadGlobalVar()->globalDataOffset_; + return false; +} + +MDefinition* +MWasmLoadGlobalVar::foldsTo(TempAllocator& alloc) +{ + if (!dependency() || !dependency()->isWasmStoreGlobalVar()) + return this; + + MWasmStoreGlobalVar* store = dependency()->toWasmStoreGlobalVar(); + if (!store->block()->dominates(block())) + return this; + + if (store->globalDataOffset() != globalDataOffset()) + return this; + + if (store->value()->type() != type()) + return this; + + return store->value(); +} + +MDefinition::AliasType +MLoadSlot::mightAlias(const MDefinition* def) const +{ + if (def->isStoreSlot()) { + const MStoreSlot* store = def->toStoreSlot(); + if (store->slot() != slot()) + return AliasType::NoAlias; + + if (store->slots() != slots()) + return AliasType::MayAlias; + + return AliasType::MustAlias; + } + return AliasType::MayAlias; +} + +HashNumber +MLoadSlot::valueHash() const +{ + HashNumber hash = MDefinition::valueHash(); + hash = addU32ToHash(hash, slot_); + return hash; +} + +MDefinition* +MLoadSlot::foldsTo(TempAllocator& alloc) +{ + if (MDefinition* def = foldsToStore(alloc)) + return def; + + return this; +} + +void +MLoadSlot::printOpcode(GenericPrinter& out) const +{ + MDefinition::printOpcode(out); + out.printf(" %d", slot()); +} + +void +MStoreSlot::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeName(out, op()); + out.printf(" "); + getOperand(0)->printName(out); + out.printf(" %d ", slot()); + getOperand(1)->printName(out); +} + +MDefinition* +MFunctionEnvironment::foldsTo(TempAllocator& alloc) +{ + if (!input()->isLambda()) + return this; + + return input()->toLambda()->environmentChain(); +} + +static bool +AddIsANonZeroAdditionOf(MAdd* add, MDefinition* ins) +{ + if (add->lhs() != ins && add->rhs() != ins) + return false; + MDefinition* other = (add->lhs() == ins) ? add->rhs() : add->lhs(); + if (!IsNumberType(other->type())) + return false; + if (!other->isConstant()) + return false; + if (other->toConstant()->numberToDouble() == 0) + return false; + return true; +} + +static bool +DefinitelyDifferentValue(MDefinition* ins1, MDefinition* ins2) +{ + if (ins1 == ins2) + return false; + + // Drop the MToInt32 added by the TypePolicy for double and float values. + if (ins1->isToInt32()) + return DefinitelyDifferentValue(ins1->toToInt32()->input(), ins2); + if (ins2->isToInt32()) + return DefinitelyDifferentValue(ins2->toToInt32()->input(), ins1); + + // Ignore the bounds check, which in most cases will contain the same info. + if (ins1->isBoundsCheck()) + return DefinitelyDifferentValue(ins1->toBoundsCheck()->index(), ins2); + if (ins2->isBoundsCheck()) + return DefinitelyDifferentValue(ins2->toBoundsCheck()->index(), ins1); + + // For constants check they are not equal. + if (ins1->isConstant() && ins2->isConstant()) + return !ins1->toConstant()->equals(ins2->toConstant()); + + // Check if "ins1 = ins2 + cte", which would make both instructions + // have different values. + if (ins1->isAdd()) { + if (AddIsANonZeroAdditionOf(ins1->toAdd(), ins2)) + return true; + } + if (ins2->isAdd()) { + if (AddIsANonZeroAdditionOf(ins2->toAdd(), ins1)) + return true; + } + + return false; +} + +MDefinition::AliasType +MLoadElement::mightAlias(const MDefinition* def) const +{ + if (def->isStoreElement()) { + const MStoreElement* store = def->toStoreElement(); + if (store->index() != index()) { + if (DefinitelyDifferentValue(store->index(), index())) + return AliasType::NoAlias; + return AliasType::MayAlias; + } + + if (store->elements() != elements()) + return AliasType::MayAlias; + + return AliasType::MustAlias; + } + return AliasType::MayAlias; +} + +MDefinition* +MLoadElement::foldsTo(TempAllocator& alloc) +{ + if (MDefinition* def = foldsToStore(alloc)) + return def; + + return this; +} + +MDefinition::AliasType +MLoadUnboxedObjectOrNull::mightAlias(const MDefinition* def) const +{ + if (def->isStoreUnboxedObjectOrNull()) { + const MStoreUnboxedObjectOrNull* store = def->toStoreUnboxedObjectOrNull(); + if (store->index() != index()) { + if (DefinitelyDifferentValue(store->index(), index())) + return AliasType::NoAlias; + return AliasType::MayAlias; + } + + if (store->elements() != elements()) + return AliasType::MayAlias; + + if (store->offsetAdjustment() != offsetAdjustment()) + return AliasType::MayAlias; + + return AliasType::MustAlias; + } + return AliasType::MayAlias; +} + +MDefinition* +MLoadUnboxedObjectOrNull::foldsTo(TempAllocator& alloc) +{ + if (MDefinition* def = foldsToStore(alloc)) + return def; + + return this; +} + +bool +MGuardReceiverPolymorphic::congruentTo(const MDefinition* ins) const +{ + if (!ins->isGuardReceiverPolymorphic()) + return false; + + const MGuardReceiverPolymorphic* other = ins->toGuardReceiverPolymorphic(); + + if (numReceivers() != other->numReceivers()) + return false; + for (size_t i = 0; i < numReceivers(); i++) { + if (receiver(i) != other->receiver(i)) + return false; + } + + return congruentIfOperandsEqual(ins); +} + +void +InlinePropertyTable::trimTo(const ObjectVector& targets, const BoolVector& choiceSet) +{ + for (size_t i = 0; i < targets.length(); i++) { + // If the target was inlined, don't erase the entry. + if (choiceSet[i]) + continue; + + JSFunction* target = &targets[i]->as<JSFunction>(); + + // Eliminate all entries containing the vetoed function from the map. + size_t j = 0; + while (j < numEntries()) { + if (entries_[j]->func == target) + entries_.erase(&entries_[j]); + else + j++; + } + } +} + +void +InlinePropertyTable::trimToTargets(const ObjectVector& targets) +{ + JitSpew(JitSpew_Inlining, "Got inlineable property cache with %d cases", + (int)numEntries()); + + size_t i = 0; + while (i < numEntries()) { + bool foundFunc = false; + for (size_t j = 0; j < targets.length(); j++) { + if (entries_[i]->func == targets[j]) { + foundFunc = true; + break; + } + } + if (!foundFunc) + entries_.erase(&(entries_[i])); + else + i++; + } + + JitSpew(JitSpew_Inlining, "%d inlineable cases left after trimming to %d targets", + (int)numEntries(), (int)targets.length()); +} + +bool +InlinePropertyTable::hasFunction(JSFunction* func) const +{ + for (size_t i = 0; i < numEntries(); i++) { + if (entries_[i]->func == func) + return true; + } + return false; +} + +bool +InlinePropertyTable::hasObjectGroup(ObjectGroup* group) const +{ + for (size_t i = 0; i < numEntries(); i++) { + if (entries_[i]->group == group) + return true; + } + return false; +} + +TemporaryTypeSet* +InlinePropertyTable::buildTypeSetForFunction(JSFunction* func) const +{ + LifoAlloc* alloc = GetJitContext()->temp->lifoAlloc(); + TemporaryTypeSet* types = alloc->new_<TemporaryTypeSet>(); + if (!types) + return nullptr; + for (size_t i = 0; i < numEntries(); i++) { + if (entries_[i]->func == func) + types->addType(TypeSet::ObjectType(entries_[i]->group), alloc); + } + return types; +} + +bool +InlinePropertyTable::appendRoots(MRootList& roots) const +{ + for (const Entry* entry : entries_) { + if (!entry->appendRoots(roots)) + return false; + } + return true; +} + +SharedMem<void*> +MLoadTypedArrayElementStatic::base() const +{ + return someTypedArray_->as<TypedArrayObject>().viewDataEither(); +} + +size_t +MLoadTypedArrayElementStatic::length() const +{ + return someTypedArray_->as<TypedArrayObject>().byteLength(); +} + +bool +MLoadTypedArrayElementStatic::congruentTo(const MDefinition* ins) const +{ + if (!ins->isLoadTypedArrayElementStatic()) + return false; + const MLoadTypedArrayElementStatic* other = ins->toLoadTypedArrayElementStatic(); + if (offset() != other->offset()) + return false; + if (needsBoundsCheck() != other->needsBoundsCheck()) + return false; + if (accessType() != other->accessType()) + return false; + if (base() != other->base()) + return false; + return congruentIfOperandsEqual(other); +} + +SharedMem<void*> +MStoreTypedArrayElementStatic::base() const +{ + return someTypedArray_->as<TypedArrayObject>().viewDataEither(); +} + +bool +MGetPropertyCache::allowDoubleResult() const +{ + if (!resultTypeSet()) + return true; + + return resultTypeSet()->hasType(TypeSet::DoubleType()); +} + +size_t +MStoreTypedArrayElementStatic::length() const +{ + return someTypedArray_->as<TypedArrayObject>().byteLength(); +} + +MDefinition::AliasType +MGetPropertyPolymorphic::mightAlias(const MDefinition* store) const +{ + // Allow hoisting this instruction if the store does not write to a + // slot read by this instruction. + + if (!store->isStoreFixedSlot() && !store->isStoreSlot()) + return AliasType::MayAlias; + + for (size_t i = 0; i < numReceivers(); i++) { + const Shape* shape = this->shape(i); + if (!shape) + continue; + if (shape->slot() < shape->numFixedSlots()) { + // Fixed slot. + uint32_t slot = shape->slot(); + if (store->isStoreFixedSlot() && store->toStoreFixedSlot()->slot() != slot) + continue; + if (store->isStoreSlot()) + continue; + } else { + // Dynamic slot. + uint32_t slot = shape->slot() - shape->numFixedSlots(); + if (store->isStoreSlot() && store->toStoreSlot()->slot() != slot) + continue; + if (store->isStoreFixedSlot()) + continue; + } + + return AliasType::MayAlias; + } + + return AliasType::NoAlias; +} + +bool +MGetPropertyPolymorphic::appendRoots(MRootList& roots) const +{ + if (!roots.append(name_)) + return false; + + for (const PolymorphicEntry& entry : receivers_) { + if (!entry.appendRoots(roots)) + return false; + } + + return true; +} + +bool +MSetPropertyPolymorphic::appendRoots(MRootList& roots) const +{ + if (!roots.append(name_)) + return false; + + for (const PolymorphicEntry& entry : receivers_) { + if (!entry.appendRoots(roots)) + return false; + } + + return true; +} + +bool +MGuardReceiverPolymorphic::appendRoots(MRootList& roots) const +{ + for (const ReceiverGuard& guard : receivers_) { + if (!roots.append(guard)) + return false; + } + return true; +} + +bool +MDispatchInstruction::appendRoots(MRootList& roots) const +{ + for (const Entry& entry : map_) { + if (!entry.appendRoots(roots)) + return false; + } + return true; +} + +bool +MObjectGroupDispatch::appendRoots(MRootList& roots) const +{ + if (inlinePropertyTable_ && !inlinePropertyTable_->appendRoots(roots)) + return false; + return MDispatchInstruction::appendRoots(roots); +} + +bool +MFunctionDispatch::appendRoots(MRootList& roots) const +{ + return MDispatchInstruction::appendRoots(roots); +} + +bool +MConstant::appendRoots(MRootList& roots) const +{ + switch (type()) { + case MIRType::String: + return roots.append(toString()); + case MIRType::Symbol: + return roots.append(toSymbol()); + case MIRType::Object: + return roots.append(&toObject()); + case MIRType::Undefined: + case MIRType::Null: + case MIRType::Boolean: + case MIRType::Int32: + case MIRType::Double: + case MIRType::Float32: + case MIRType::MagicOptimizedArguments: + case MIRType::MagicOptimizedOut: + case MIRType::MagicHole: + case MIRType::MagicIsConstructing: + case MIRType::MagicUninitializedLexical: + return true; + default: + MOZ_CRASH("Unexpected type"); + } +} + +void +MGetPropertyCache::setBlock(MBasicBlock* block) +{ + MDefinition::setBlock(block); + // Track where we started. + if (!location_.pc) { + location_.pc = block->trackedPc(); + location_.script = block->info().script(); + } +} + +bool +MGetPropertyCache::updateForReplacement(MDefinition* ins) +{ + MGetPropertyCache* other = ins->toGetPropertyCache(); + location_.append(&other->location_); + return true; +} + +MDefinition* +MWasmUnsignedToDouble::foldsTo(TempAllocator& alloc) +{ + if (input()->isConstant() && input()->type() == MIRType::Int32) + return MConstant::New(alloc, DoubleValue(uint32_t(input()->toConstant()->toInt32()))); + + return this; +} + +MDefinition* +MWasmUnsignedToFloat32::foldsTo(TempAllocator& alloc) +{ + if (input()->isConstant() && input()->type() == MIRType::Int32) { + double dval = double(uint32_t(input()->toConstant()->toInt32())); + if (IsFloat32Representable(dval)) + return MConstant::New(alloc, JS::Float32Value(float(dval)), MIRType::Float32); + } + + return this; +} + +MWasmCall* +MWasmCall::New(TempAllocator& alloc, const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee, + const Args& args, MIRType resultType, uint32_t spIncrement, uint32_t tlsStackOffset, + MDefinition* tableIndex) +{ + MWasmCall* call = new(alloc) MWasmCall(desc, callee, spIncrement, tlsStackOffset); + call->setResultType(resultType); + + if (!call->argRegs_.init(alloc, args.length())) + return nullptr; + for (size_t i = 0; i < call->argRegs_.length(); i++) + call->argRegs_[i] = args[i].reg; + + if (!call->init(alloc, call->argRegs_.length() + (callee.isTable() ? 1 : 0))) + return nullptr; + // FixedList doesn't initialize its elements, so do an unchecked init. + for (size_t i = 0; i < call->argRegs_.length(); i++) + call->initOperand(i, args[i].def); + if (callee.isTable()) + call->initOperand(call->argRegs_.length(), tableIndex); + + return call; +} + +MWasmCall* +MWasmCall::NewBuiltinInstanceMethodCall(TempAllocator& alloc, + const wasm::CallSiteDesc& desc, + const wasm::SymbolicAddress builtin, + const ABIArg& instanceArg, + const Args& args, + MIRType resultType, + uint32_t spIncrement, + uint32_t tlsStackOffset) +{ + auto callee = wasm::CalleeDesc::builtinInstanceMethod(builtin); + MWasmCall* call = MWasmCall::New(alloc, desc, callee, args, resultType, spIncrement, + tlsStackOffset, nullptr); + if (!call) + return nullptr; + + MOZ_ASSERT(instanceArg != ABIArg()); + call->instanceArg_ = instanceArg; + return call; +} + +void +MSqrt::trySpecializeFloat32(TempAllocator& alloc) { + if (!input()->canProduceFloat32() || !CheckUsesAreFloat32Consumers(this)) { + if (input()->type() == MIRType::Float32) + ConvertDefinitionToDouble<0>(alloc, input(), this); + return; + } + + setResultType(MIRType::Float32); + specialization_ = MIRType::Float32; +} + +MDefinition* +MClz::foldsTo(TempAllocator& alloc) +{ + if (num()->isConstant()) { + MConstant* c = num()->toConstant(); + if (type() == MIRType::Int32) { + int32_t n = c->toInt32(); + if (n == 0) + return MConstant::New(alloc, Int32Value(32)); + return MConstant::New(alloc, Int32Value(mozilla::CountLeadingZeroes32(n))); + } + int64_t n = c->toInt64(); + if (n == 0) + return MConstant::NewInt64(alloc, int64_t(64)); + return MConstant::NewInt64(alloc, int64_t(mozilla::CountLeadingZeroes64(n))); + } + + return this; +} + +MDefinition* +MCtz::foldsTo(TempAllocator& alloc) +{ + if (num()->isConstant()) { + MConstant* c = num()->toConstant(); + if (type() == MIRType::Int32) { + int32_t n = num()->toConstant()->toInt32(); + if (n == 0) + return MConstant::New(alloc, Int32Value(32)); + return MConstant::New(alloc, Int32Value(mozilla::CountTrailingZeroes32(n))); + } + int64_t n = c->toInt64(); + if (n == 0) + return MConstant::NewInt64(alloc, int64_t(64)); + return MConstant::NewInt64(alloc, int64_t(mozilla::CountTrailingZeroes64(n))); + } + + return this; +} + +MDefinition* +MPopcnt::foldsTo(TempAllocator& alloc) +{ + if (num()->isConstant()) { + MConstant* c = num()->toConstant(); + if (type() == MIRType::Int32) { + int32_t n = num()->toConstant()->toInt32(); + return MConstant::New(alloc, Int32Value(mozilla::CountPopulation32(n))); + } + int64_t n = c->toInt64(); + return MConstant::NewInt64(alloc, int64_t(mozilla::CountPopulation64(n))); + } + + return this; +} + +MDefinition* +MBoundsCheck::foldsTo(TempAllocator& alloc) +{ + if (index()->isConstant() && length()->isConstant()) { + uint32_t len = length()->toConstant()->toInt32(); + uint32_t idx = index()->toConstant()->toInt32(); + if (idx + uint32_t(minimum()) < len && idx + uint32_t(maximum()) < len) + return index(); + } + + return this; +} + +MDefinition* +MTableSwitch::foldsTo(TempAllocator& alloc) +{ + MDefinition* op = getOperand(0); + + // If we only have one successor, convert to a plain goto to the only + // successor. TableSwitch indices are numeric; other types will always go to + // the only successor. + if (numSuccessors() == 1 || (op->type() != MIRType::Value && !IsNumberType(op->type()))) + return MGoto::New(alloc, getDefault()); + + if (MConstant* opConst = op->maybeConstantValue()) { + if (op->type() == MIRType::Int32) { + int32_t i = opConst->toInt32() - low_; + MBasicBlock* target; + if (size_t(i) < numCases()) + target = getCase(size_t(i)); + else + target = getDefault(); + MOZ_ASSERT(target); + return MGoto::New(alloc, target); + } + } + + return this; +} + +MDefinition* +MArrayJoin::foldsTo(TempAllocator& alloc) +{ + MDefinition* arr = array(); + + if (!arr->isStringSplit()) + return this; + + setRecoveredOnBailout(); + if (arr->hasLiveDefUses()) { + setNotRecoveredOnBailout(); + return this; + } + + // The MStringSplit won't generate any code. + arr->setRecoveredOnBailout(); + + // We're replacing foo.split(bar).join(baz) by + // foo.replace(bar, baz). MStringSplit could be recovered by + // a bailout. As we are removing its last use, and its result + // could be captured by a resume point, this MStringSplit will + // be executed on the bailout path. + MDefinition* string = arr->toStringSplit()->string(); + MDefinition* pattern = arr->toStringSplit()->separator(); + MDefinition* replacement = sep(); + + MStringReplace *substr = MStringReplace::New(alloc, string, pattern, replacement); + substr->setFlatReplacement(); + return substr; +} + +MDefinition* +MGetFirstDollarIndex::foldsTo(TempAllocator& alloc) +{ + MDefinition* strArg = str(); + if (!strArg->isConstant()) + return this; + + JSAtom* atom = &strArg->toConstant()->toString()->asAtom(); + int32_t index = GetFirstDollarIndexRawFlat(atom); + return MConstant::New(alloc, Int32Value(index)); +} + +MConvertUnboxedObjectToNative* +MConvertUnboxedObjectToNative::New(TempAllocator& alloc, MDefinition* obj, ObjectGroup* group) +{ + MConvertUnboxedObjectToNative* res = new(alloc) MConvertUnboxedObjectToNative(obj, group); + + ObjectGroup* nativeGroup = group->unboxedLayout().nativeGroup(); + + // Make a new type set for the result of this instruction which replaces + // the input group with the native group we will convert it to. + TemporaryTypeSet* types = obj->resultTypeSet(); + if (types && !types->unknownObject()) { + TemporaryTypeSet* newTypes = types->cloneWithoutObjects(alloc.lifoAlloc()); + if (newTypes) { + for (size_t i = 0; i < types->getObjectCount(); i++) { + TypeSet::ObjectKey* key = types->getObject(i); + if (!key) + continue; + if (key->unknownProperties() || !key->isGroup() || key->group() != group) + newTypes->addType(TypeSet::ObjectType(key), alloc.lifoAlloc()); + else + newTypes->addType(TypeSet::ObjectType(nativeGroup), alloc.lifoAlloc()); + } + res->setResultTypeSet(newTypes); + } + } + + return res; +} + +bool +jit::ElementAccessIsDenseNative(CompilerConstraintList* constraints, + MDefinition* obj, MDefinition* id) +{ + if (obj->mightBeType(MIRType::String)) + return false; + + if (id->type() != MIRType::Int32 && id->type() != MIRType::Double) + return false; + + TemporaryTypeSet* types = obj->resultTypeSet(); + if (!types) + return false; + + // Typed arrays are native classes but do not have dense elements. + const Class* clasp = types->getKnownClass(constraints); + return clasp && clasp->isNative() && !IsTypedArrayClass(clasp); +} + +JSValueType +jit::UnboxedArrayElementType(CompilerConstraintList* constraints, MDefinition* obj, + MDefinition* id) +{ + if (obj->mightBeType(MIRType::String)) + return JSVAL_TYPE_MAGIC; + + if (id && id->type() != MIRType::Int32 && id->type() != MIRType::Double) + return JSVAL_TYPE_MAGIC; + + TemporaryTypeSet* types = obj->resultTypeSet(); + if (!types || types->unknownObject()) + return JSVAL_TYPE_MAGIC; + + JSValueType elementType = JSVAL_TYPE_MAGIC; + for (unsigned i = 0; i < types->getObjectCount(); i++) { + TypeSet::ObjectKey* key = types->getObject(i); + if (!key) + continue; + + if (key->unknownProperties() || !key->isGroup()) + return JSVAL_TYPE_MAGIC; + + if (key->clasp() != &UnboxedArrayObject::class_) + return JSVAL_TYPE_MAGIC; + + const UnboxedLayout &layout = key->group()->unboxedLayout(); + + if (layout.nativeGroup()) + return JSVAL_TYPE_MAGIC; + + if (elementType == layout.elementType() || elementType == JSVAL_TYPE_MAGIC) + elementType = layout.elementType(); + else + return JSVAL_TYPE_MAGIC; + + key->watchStateChangeForUnboxedConvertedToNative(constraints); + } + + return elementType; +} + +bool +jit::ElementAccessIsTypedArray(CompilerConstraintList* constraints, + MDefinition* obj, MDefinition* id, + Scalar::Type* arrayType) +{ + if (obj->mightBeType(MIRType::String)) + return false; + + if (id->type() != MIRType::Int32 && id->type() != MIRType::Double) + return false; + + TemporaryTypeSet* types = obj->resultTypeSet(); + if (!types) + return false; + + *arrayType = types->getTypedArrayType(constraints); + return *arrayType != Scalar::MaxTypedArrayViewType; +} + +bool +jit::ElementAccessIsPacked(CompilerConstraintList* constraints, MDefinition* obj) +{ + TemporaryTypeSet* types = obj->resultTypeSet(); + return types && !types->hasObjectFlags(constraints, OBJECT_FLAG_NON_PACKED); +} + +bool +jit::ElementAccessMightBeCopyOnWrite(CompilerConstraintList* constraints, MDefinition* obj) +{ + TemporaryTypeSet* types = obj->resultTypeSet(); + return !types || types->hasObjectFlags(constraints, OBJECT_FLAG_COPY_ON_WRITE); +} + +bool +jit::ElementAccessMightBeFrozen(CompilerConstraintList* constraints, MDefinition* obj) +{ + TemporaryTypeSet* types = obj->resultTypeSet(); + return !types || types->hasObjectFlags(constraints, OBJECT_FLAG_FROZEN); +} + +bool +jit::ElementAccessHasExtraIndexedProperty(IonBuilder* builder, MDefinition* obj) +{ + TemporaryTypeSet* types = obj->resultTypeSet(); + + if (!types || types->hasObjectFlags(builder->constraints(), OBJECT_FLAG_LENGTH_OVERFLOW)) + return true; + + return TypeCanHaveExtraIndexedProperties(builder, types); +} + +MIRType +jit::DenseNativeElementType(CompilerConstraintList* constraints, MDefinition* obj) +{ + TemporaryTypeSet* types = obj->resultTypeSet(); + MIRType elementType = MIRType::None; + unsigned count = types->getObjectCount(); + + for (unsigned i = 0; i < count; i++) { + TypeSet::ObjectKey* key = types->getObject(i); + if (!key) + continue; + + if (key->unknownProperties()) + return MIRType::None; + + HeapTypeSetKey elementTypes = key->property(JSID_VOID); + + MIRType type = elementTypes.knownMIRType(constraints); + if (type == MIRType::None) + return MIRType::None; + + if (elementType == MIRType::None) + elementType = type; + else if (elementType != type) + return MIRType::None; + } + + return elementType; +} + +static BarrierKind +PropertyReadNeedsTypeBarrier(CompilerConstraintList* constraints, + TypeSet::ObjectKey* key, PropertyName* name, + TypeSet* observed) +{ + // If the object being read from has types for the property which haven't + // been observed at this access site, the read could produce a new type and + // a barrier is needed. Note that this only covers reads from properties + // which are accounted for by type information, i.e. native data properties + // and elements. + // + // We also need a barrier if the object is a proxy, because then all bets + // are off, just as if it has unknown properties. + if (key->unknownProperties() || observed->empty() || + key->clasp()->isProxy()) + { + return BarrierKind::TypeSet; + } + + if (!name && IsTypedArrayClass(key->clasp())) { + Scalar::Type arrayType = Scalar::Type(key->clasp() - &TypedArrayObject::classes[0]); + MIRType type = MIRTypeForTypedArrayRead(arrayType, true); + if (observed->mightBeMIRType(type)) + return BarrierKind::NoBarrier; + return BarrierKind::TypeSet; + } + + jsid id = name ? NameToId(name) : JSID_VOID; + HeapTypeSetKey property = key->property(id); + if (property.maybeTypes()) { + if (!TypeSetIncludes(observed, MIRType::Value, property.maybeTypes())) { + // If all possible objects have been observed, we don't have to + // guard on the specific object types. + if (property.maybeTypes()->objectsAreSubset(observed)) { + property.freeze(constraints); + return BarrierKind::TypeTagOnly; + } + return BarrierKind::TypeSet; + } + } + + // Type information for global objects is not required to reflect the + // initial 'undefined' value for properties, in particular global + // variables declared with 'var'. Until the property is assigned a value + // other than undefined, a barrier is required. + if (key->isSingleton()) { + JSObject* obj = key->singleton(); + if (name && CanHaveEmptyPropertyTypesForOwnProperty(obj) && + (!property.maybeTypes() || property.maybeTypes()->empty())) + { + return BarrierKind::TypeSet; + } + } + + property.freeze(constraints); + return BarrierKind::NoBarrier; +} + +static bool +ObjectSubsumes(TypeSet::ObjectKey* first, TypeSet::ObjectKey* second) +{ + if (first->isSingleton() || + second->isSingleton() || + first->clasp() != second->clasp() || + first->unknownProperties() || + second->unknownProperties()) + { + return false; + } + + if (first->clasp() == &ArrayObject::class_) { + HeapTypeSetKey firstElements = first->property(JSID_VOID); + HeapTypeSetKey secondElements = second->property(JSID_VOID); + + return firstElements.maybeTypes() && secondElements.maybeTypes() && + firstElements.maybeTypes()->equals(secondElements.maybeTypes()); + } + + if (first->clasp() == &UnboxedArrayObject::class_) { + return first->group()->unboxedLayout().elementType() == + second->group()->unboxedLayout().elementType(); + } + + return false; +} + +BarrierKind +jit::PropertyReadNeedsTypeBarrier(JSContext* propertycx, + CompilerConstraintList* constraints, + TypeSet::ObjectKey* key, PropertyName* name, + TemporaryTypeSet* observed, bool updateObserved) +{ + if (!updateObserved) + return PropertyReadNeedsTypeBarrier(constraints, key, name, observed); + + // If this access has never executed, try to add types to the observed set + // according to any property which exists on the object or its prototype. + if (observed->empty() && name) { + JSObject* obj; + if (key->isSingleton()) + obj = key->singleton(); + else + obj = key->proto().isDynamic() ? nullptr : key->proto().toObjectOrNull(); + + while (obj) { + if (!obj->getClass()->isNative()) + break; + + TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(obj); + if (propertycx) + key->ensureTrackedProperty(propertycx, NameToId(name)); + + if (!key->unknownProperties()) { + HeapTypeSetKey property = key->property(NameToId(name)); + if (property.maybeTypes()) { + TypeSet::TypeList types; + if (!property.maybeTypes()->enumerateTypes(&types)) + break; + if (types.length() == 1) { + // Note: the return value here is ignored. + observed->addType(types[0], GetJitContext()->temp->lifoAlloc()); + break; + } + } + } + + obj = obj->staticPrototype(); + } + } + + // If any objects which could be observed are similar to ones that have + // already been observed, add them to the observed type set. + if (!key->unknownProperties()) { + HeapTypeSetKey property = key->property(name ? NameToId(name) : JSID_VOID); + + if (property.maybeTypes() && !property.maybeTypes()->unknownObject()) { + for (size_t i = 0; i < property.maybeTypes()->getObjectCount(); i++) { + TypeSet::ObjectKey* key = property.maybeTypes()->getObject(i); + if (!key || observed->unknownObject()) + continue; + + for (size_t j = 0; j < observed->getObjectCount(); j++) { + TypeSet::ObjectKey* observedKey = observed->getObject(j); + if (observedKey && ObjectSubsumes(observedKey, key)) { + // Note: the return value here is ignored. + observed->addType(TypeSet::ObjectType(key), + GetJitContext()->temp->lifoAlloc()); + break; + } + } + } + } + } + + return PropertyReadNeedsTypeBarrier(constraints, key, name, observed); +} + +BarrierKind +jit::PropertyReadNeedsTypeBarrier(JSContext* propertycx, + CompilerConstraintList* constraints, + MDefinition* obj, PropertyName* name, + TemporaryTypeSet* observed) +{ + if (observed->unknown()) + return BarrierKind::NoBarrier; + + TypeSet* types = obj->resultTypeSet(); + if (!types || types->unknownObject()) + return BarrierKind::TypeSet; + + BarrierKind res = BarrierKind::NoBarrier; + + bool updateObserved = types->getObjectCount() == 1; + for (size_t i = 0; i < types->getObjectCount(); i++) { + if (TypeSet::ObjectKey* key = types->getObject(i)) { + BarrierKind kind = PropertyReadNeedsTypeBarrier(propertycx, constraints, key, name, + observed, updateObserved); + if (kind == BarrierKind::TypeSet) + return BarrierKind::TypeSet; + + if (kind == BarrierKind::TypeTagOnly) { + MOZ_ASSERT(res == BarrierKind::NoBarrier || res == BarrierKind::TypeTagOnly); + res = BarrierKind::TypeTagOnly; + } else { + MOZ_ASSERT(kind == BarrierKind::NoBarrier); + } + } + } + + return res; +} + +ResultWithOOM<BarrierKind> +jit::PropertyReadOnPrototypeNeedsTypeBarrier(IonBuilder* builder, + MDefinition* obj, PropertyName* name, + TemporaryTypeSet* observed) +{ + if (observed->unknown()) + return ResultWithOOM<BarrierKind>::ok(BarrierKind::NoBarrier); + + TypeSet* types = obj->resultTypeSet(); + if (!types || types->unknownObject()) + return ResultWithOOM<BarrierKind>::ok(BarrierKind::TypeSet); + + BarrierKind res = BarrierKind::NoBarrier; + + for (size_t i = 0; i < types->getObjectCount(); i++) { + TypeSet::ObjectKey* key = types->getObject(i); + if (!key) + continue; + while (true) { + if (!builder->alloc().ensureBallast()) + return ResultWithOOM<BarrierKind>::fail(); + if (!key->hasStableClassAndProto(builder->constraints())) + return ResultWithOOM<BarrierKind>::ok(BarrierKind::TypeSet); + if (!key->proto().isObject()) + break; + JSObject* proto = builder->checkNurseryObject(key->proto().toObject()); + key = TypeSet::ObjectKey::get(proto); + BarrierKind kind = PropertyReadNeedsTypeBarrier(builder->constraints(), + key, name, observed); + if (kind == BarrierKind::TypeSet) + return ResultWithOOM<BarrierKind>::ok(BarrierKind::TypeSet); + + if (kind == BarrierKind::TypeTagOnly) { + MOZ_ASSERT(res == BarrierKind::NoBarrier || res == BarrierKind::TypeTagOnly); + res = BarrierKind::TypeTagOnly; + } else { + MOZ_ASSERT(kind == BarrierKind::NoBarrier); + } + } + } + + return ResultWithOOM<BarrierKind>::ok(res); +} + +bool +jit::PropertyReadIsIdempotent(CompilerConstraintList* constraints, + MDefinition* obj, PropertyName* name) +{ + // Determine if reading a property from obj is likely to be idempotent. + + TypeSet* types = obj->resultTypeSet(); + if (!types || types->unknownObject()) + return false; + + for (size_t i = 0; i < types->getObjectCount(); i++) { + if (TypeSet::ObjectKey* key = types->getObject(i)) { + if (key->unknownProperties()) + return false; + + // Check if the property has been reconfigured or is a getter. + HeapTypeSetKey property = key->property(NameToId(name)); + if (property.nonData(constraints)) + return false; + } + } + + return true; +} + +void +jit::AddObjectsForPropertyRead(MDefinition* obj, PropertyName* name, + TemporaryTypeSet* observed) +{ + // Add objects to observed which *could* be observed by reading name from obj, + // to hopefully avoid unnecessary type barriers and code invalidations. + + LifoAlloc* alloc = GetJitContext()->temp->lifoAlloc(); + + TemporaryTypeSet* types = obj->resultTypeSet(); + if (!types || types->unknownObject()) { + observed->addType(TypeSet::AnyObjectType(), alloc); + return; + } + + for (size_t i = 0; i < types->getObjectCount(); i++) { + TypeSet::ObjectKey* key = types->getObject(i); + if (!key) + continue; + + if (key->unknownProperties()) { + observed->addType(TypeSet::AnyObjectType(), alloc); + return; + } + + jsid id = name ? NameToId(name) : JSID_VOID; + HeapTypeSetKey property = key->property(id); + HeapTypeSet* types = property.maybeTypes(); + if (!types) + continue; + + if (types->unknownObject()) { + observed->addType(TypeSet::AnyObjectType(), alloc); + return; + } + + for (size_t i = 0; i < types->getObjectCount(); i++) { + if (TypeSet::ObjectKey* key = types->getObject(i)) + observed->addType(TypeSet::ObjectType(key), alloc); + } + } +} + +static bool +PrototypeHasIndexedProperty(IonBuilder* builder, JSObject* obj) +{ + do { + TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(builder->checkNurseryObject(obj)); + if (ClassCanHaveExtraProperties(key->clasp())) + return true; + if (key->unknownProperties()) + return true; + HeapTypeSetKey index = key->property(JSID_VOID); + if (index.nonData(builder->constraints()) || index.isOwnProperty(builder->constraints())) + return true; + obj = obj->staticPrototype(); + } while (obj); + + return false; +} + +// Whether Array.prototype, or an object on its proto chain, has an indexed property. +bool +jit::ArrayPrototypeHasIndexedProperty(IonBuilder* builder, JSScript* script) +{ + if (JSObject* proto = script->global().maybeGetArrayPrototype()) + return PrototypeHasIndexedProperty(builder, proto); + return true; +} + +// Whether obj or any of its prototypes have an indexed property. +bool +jit::TypeCanHaveExtraIndexedProperties(IonBuilder* builder, TemporaryTypeSet* types) +{ + const Class* clasp = types->getKnownClass(builder->constraints()); + + // Note: typed arrays have indexed properties not accounted for by type + // information, though these are all in bounds and will be accounted for + // by JIT paths. + if (!clasp || (ClassCanHaveExtraProperties(clasp) && !IsTypedArrayClass(clasp))) + return true; + + if (types->hasObjectFlags(builder->constraints(), OBJECT_FLAG_SPARSE_INDEXES)) + return true; + + JSObject* proto; + if (!types->getCommonPrototype(builder->constraints(), &proto)) + return true; + + if (!proto) + return false; + + return PrototypeHasIndexedProperty(builder, proto); +} + +static bool +PropertyTypeIncludes(TempAllocator& alloc, HeapTypeSetKey property, + MDefinition* value, MIRType implicitType) +{ + // If implicitType is not MIRType::None, it is an additional type which the + // property implicitly includes. In this case, make a new type set which + // explicitly contains the type. + TypeSet* types = property.maybeTypes(); + if (implicitType != MIRType::None) { + TypeSet::Type newType = TypeSet::PrimitiveType(ValueTypeFromMIRType(implicitType)); + if (types) + types = types->clone(alloc.lifoAlloc()); + else + types = alloc.lifoAlloc()->new_<TemporaryTypeSet>(); + if (!types) { + return false; + } + types->addType(newType, alloc.lifoAlloc()); + } + + return TypeSetIncludes(types, value->type(), value->resultTypeSet()); +} + +static bool +TryAddTypeBarrierForWrite(TempAllocator& alloc, CompilerConstraintList* constraints, + MBasicBlock* current, TemporaryTypeSet* objTypes, + PropertyName* name, MDefinition** pvalue, MIRType implicitType) +{ + // Return whether pvalue was modified to include a type barrier ensuring + // that writing the value to objTypes/id will not require changing type + // information. + + // All objects in the set must have the same types for name. Otherwise, we + // could bail out without subsequently triggering a type change that + // invalidates the compiled code. + Maybe<HeapTypeSetKey> aggregateProperty; + + for (size_t i = 0; i < objTypes->getObjectCount(); i++) { + TypeSet::ObjectKey* key = objTypes->getObject(i); + if (!key) + continue; + + if (key->unknownProperties()) + return false; + + jsid id = name ? NameToId(name) : JSID_VOID; + HeapTypeSetKey property = key->property(id); + if (!property.maybeTypes() || property.couldBeConstant(constraints)) + return false; + + if (PropertyTypeIncludes(alloc, property, *pvalue, implicitType)) + return false; + + // This freeze is not required for correctness, but ensures that we + // will recompile if the property types change and the barrier can + // potentially be removed. + property.freeze(constraints); + + if (!aggregateProperty) { + aggregateProperty.emplace(property); + } else { + if (!aggregateProperty->maybeTypes()->equals(property.maybeTypes())) + return false; + } + } + + MOZ_ASSERT(aggregateProperty); + + MIRType propertyType = aggregateProperty->knownMIRType(constraints); + switch (propertyType) { + case MIRType::Boolean: + case MIRType::Int32: + case MIRType::Double: + case MIRType::String: + case MIRType::Symbol: { + // The property is a particular primitive type, guard by unboxing the + // value before the write. + if (!(*pvalue)->mightBeType(propertyType)) { + // The value's type does not match the property type. Just do a VM + // call as it will always trigger invalidation of the compiled code. + MOZ_ASSERT_IF((*pvalue)->type() != MIRType::Value, (*pvalue)->type() != propertyType); + return false; + } + MInstruction* ins = MUnbox::New(alloc, *pvalue, propertyType, MUnbox::Fallible); + current->add(ins); + *pvalue = ins; + return true; + } + default:; + } + + if ((*pvalue)->type() != MIRType::Value) + return false; + + TemporaryTypeSet* types = aggregateProperty->maybeTypes()->clone(alloc.lifoAlloc()); + if (!types) + return false; + + // If all possible objects can be stored without a barrier, we don't have to + // guard on the specific object types. + BarrierKind kind = BarrierKind::TypeSet; + if ((*pvalue)->resultTypeSet() && (*pvalue)->resultTypeSet()->objectsAreSubset(types)) + kind = BarrierKind::TypeTagOnly; + + MInstruction* ins = MMonitorTypes::New(alloc, *pvalue, types, kind); + current->add(ins); + return true; +} + +static MInstruction* +AddGroupGuard(TempAllocator& alloc, MBasicBlock* current, MDefinition* obj, + TypeSet::ObjectKey* key, bool bailOnEquality) +{ + MInstruction* guard; + + if (key->isGroup()) { + guard = MGuardObjectGroup::New(alloc, obj, key->group(), bailOnEquality, + Bailout_ObjectIdentityOrTypeGuard); + } else { + MConstant* singletonConst = MConstant::NewConstraintlessObject(alloc, key->singleton()); + current->add(singletonConst); + guard = MGuardObjectIdentity::New(alloc, obj, singletonConst, bailOnEquality); + } + + current->add(guard); + + // For now, never move object group / identity guards. + guard->setNotMovable(); + + return guard; +} + +// Whether value can be written to property without changing type information. +bool +jit::CanWriteProperty(TempAllocator& alloc, CompilerConstraintList* constraints, + HeapTypeSetKey property, MDefinition* value, + MIRType implicitType /* = MIRType::None */) +{ + if (property.couldBeConstant(constraints)) + return false; + return PropertyTypeIncludes(alloc, property, value, implicitType); +} + +bool +jit::PropertyWriteNeedsTypeBarrier(TempAllocator& alloc, CompilerConstraintList* constraints, + MBasicBlock* current, MDefinition** pobj, + PropertyName* name, MDefinition** pvalue, + bool canModify, MIRType implicitType) +{ + // If any value being written is not reflected in the type information for + // objects which obj could represent, a type barrier is needed when writing + // the value. As for propertyReadNeedsTypeBarrier, this only applies for + // properties that are accounted for by type information, i.e. normal data + // properties and elements. + + TemporaryTypeSet* types = (*pobj)->resultTypeSet(); + if (!types || types->unknownObject()) + return true; + + // If all of the objects being written to have property types which already + // reflect the value, no barrier at all is needed. Additionally, if all + // objects being written to have the same types for the property, and those + // types do *not* reflect the value, add a type barrier for the value. + + bool success = true; + for (size_t i = 0; i < types->getObjectCount(); i++) { + TypeSet::ObjectKey* key = types->getObject(i); + if (!key || key->unknownProperties()) + continue; + + // TI doesn't track TypedArray indexes and should never insert a type + // barrier for them. + if (!name && IsTypedArrayClass(key->clasp())) + continue; + + jsid id = name ? NameToId(name) : JSID_VOID; + HeapTypeSetKey property = key->property(id); + if (!CanWriteProperty(alloc, constraints, property, *pvalue, implicitType)) { + // Either pobj or pvalue needs to be modified to filter out the + // types which the value could have but are not in the property, + // or a VM call is required. A VM call is always required if pobj + // and pvalue cannot be modified. + if (!canModify) + return true; + success = TryAddTypeBarrierForWrite(alloc, constraints, current, types, name, pvalue, + implicitType); + break; + } + } + + // Perform additional filtering to make sure that any unboxed property + // being written can accommodate the value. + for (size_t i = 0; i < types->getObjectCount(); i++) { + TypeSet::ObjectKey* key = types->getObject(i); + if (key && key->isGroup() && key->group()->maybeUnboxedLayout()) { + const UnboxedLayout& layout = key->group()->unboxedLayout(); + if (name) { + const UnboxedLayout::Property* property = layout.lookup(name); + if (property && !CanStoreUnboxedType(alloc, property->type, *pvalue)) + return true; + } else { + if (layout.isArray() && !CanStoreUnboxedType(alloc, layout.elementType(), *pvalue)) + return true; + } + } + } + + if (success) + return false; + + // If all of the objects except one have property types which reflect the + // value, and the remaining object has no types at all for the property, + // add a guard that the object does not have that remaining object's type. + + if (types->getObjectCount() <= 1) + return true; + + TypeSet::ObjectKey* excluded = nullptr; + for (size_t i = 0; i < types->getObjectCount(); i++) { + TypeSet::ObjectKey* key = types->getObject(i); + if (!key || key->unknownProperties()) + continue; + if (!name && IsTypedArrayClass(key->clasp())) + continue; + + jsid id = name ? NameToId(name) : JSID_VOID; + HeapTypeSetKey property = key->property(id); + if (CanWriteProperty(alloc, constraints, property, *pvalue, implicitType)) + continue; + + if ((property.maybeTypes() && !property.maybeTypes()->empty()) || excluded) + return true; + excluded = key; + } + + MOZ_ASSERT(excluded); + + // If the excluded object is a group with an unboxed layout, make sure it + // does not have a corresponding native group. Objects with the native + // group might appear even though they are not in the type set. + if (excluded->isGroup()) { + if (UnboxedLayout* layout = excluded->group()->maybeUnboxedLayout()) { + if (layout->nativeGroup()) + return true; + excluded->watchStateChangeForUnboxedConvertedToNative(constraints); + } + } + + *pobj = AddGroupGuard(alloc, current, *pobj, excluded, /* bailOnEquality = */ true); + return false; +} |