/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jit/MIR.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Maybe.h"
#include "mozilla/ScopeExit.h"
#include <array>
#include <utility>
#include "jslibmath.h"
#include "jsmath.h"
#include "jsnum.h"
#include "builtin/RegExp.h"
#include "jit/AtomicOperations.h"
#include "jit/CompileInfo.h"
#include "jit/KnownClass.h"
#include "jit/MIR-wasm.h"
#include "jit/MIRGraph.h"
#include "jit/RangeAnalysis.h"
#include "jit/VMFunctions.h"
#include "jit/WarpBuilderShared.h"
#include "js/Conversions.h"
#include "js/experimental/JitInfo.h" // JSJitInfo, JSTypedMethodJitInfo
#include "js/ScalarType.h" // js::Scalar::Type
#include "util/Text.h"
#include "util/Unicode.h"
#include "vm/BigIntType.h"
#include "vm/Float16.h"
#include "vm/Iteration.h" // js::NativeIterator
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/Uint8Clamped.h"
#include "vm/BytecodeUtil-inl.h"
#include "vm/JSAtomUtils-inl.h" // TypeName
using namespace js;
using namespace js::jit;
using JS::ToInt32;
using mozilla::IsFloat32Representable;
using mozilla::IsPowerOfTwo;
using mozilla::NumbersAreIdentical;
NON_GC_POINTER_TYPE_ASSERTIONS_GENERATED
#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);
}
template <size_t Arity, size_t Index>
static void ConvertOperandToDouble(MAryInstruction<Arity>* def,
TempAllocator& alloc) {
static_assert(Index < Arity);
auto* operand = def->getOperand(Index);
if (operand->type() == MIRType::Float32) {
ConvertDefinitionToDouble<Index>(alloc, operand, def);
}
}
template <size_t Arity, size_t... ISeq>
static void ConvertOperandsToDouble(MAryInstruction<Arity>* def,
TempAllocator& alloc,
std::index_sequence<ISeq...>) {
(ConvertOperandToDouble<Arity, ISeq>(def, alloc), ...);
}
template <size_t Arity>
static void ConvertOperandsToDouble(MAryInstruction<Arity>* def,
TempAllocator& alloc) {
ConvertOperandsToDouble<Arity>(def, alloc, std::make_index_sequence<Arity>{});
}
template <size_t Arity, size_t... ISeq>
static bool AllOperandsCanProduceFloat32(MAryInstruction<Arity>* def,
std::index_sequence<ISeq...>) {
return (def->getOperand(ISeq)->canProduceFloat32() && ...);
}
template <size_t Arity>
static bool AllOperandsCanProduceFloat32(MAryInstruction<Arity>* def) {
return AllOperandsCanProduceFloat32<Arity>(def,
std::make_index_sequence<Arity>{});
}
static bool CheckUsesAreFloat32Consumers(
const MInstruction* ins) {
if (ins->isImplicitlyUsed()) {
return false;
}
bool allConsumerUses =
true;
for (MUseDefIterator use(ins); allConsumerUses && use; use++) {
allConsumerUses &= use.def()->canConsumeFloat32(use.use());
}
return allConsumerUses;
}
#ifdef JS_JITSPEW
static const char* OpcodeName(MDefinition::Opcode op) {
static const char*
const names[] = {
# define NAME(x)
#x,
MIR_OPCODE_LIST(NAME)
# undef NAME
};
return names[
unsigned(op)];
}
void MDefinition::PrintOpcodeName(GenericPrinter& out, Opcode op) {
const char* name = OpcodeName(op);
size_t len = strlen(name);
for (size_t i = 0; i < len; i++) {
out.printf(
"%c", unicode::ToLowerCase(name[i]));
}
}
uint32_t js::jit::GetMBasicBlockId(
const MBasicBlock* block) {
return block->id();
}
#endif
static MConstant* EvaluateInt64ConstantOperands(TempAllocator& alloc,
MBinaryInstruction* ins) {
MDefinition* left = ins->getOperand(0);
MDefinition* right = ins->getOperand(1);
if (!left->isConstant() || !right->isConstant()) {
return nullptr;
}
MOZ_ASSERT(left->type() == MIRType::Int64);
MOZ_ASSERT(right->type() == MIRType::Int64);
int64_t lhs = left->toConstant()->toInt64();
int64_t rhs = right->toConstant()->toInt64();
int64_t ret;
switch (ins->op()) {
case MDefinition::Opcode::
BitAnd:
ret = lhs & rhs;
break;
case MDefinition::Opcode::
BitOr:
ret = lhs | rhs;
break;
case MDefinition::Opcode::BitXor:
ret = lhs ^ rhs;
break;
case MDefinition::Opcode::Lsh:
ret = lhs << (rhs & 0x3F);
break;
case MDefinition::Opcode::Rsh:
ret = lhs >> (rhs & 0x3F);
break;
case MDefinition::Opcode::Ursh:
ret = uint64_t(lhs) >> (uint64_t(rhs) & 0x3F);
break;
case MDefinition::Opcode::Add:
ret = lhs + rhs;
break;
case MDefinition::Opcode::Sub:
ret = lhs - rhs;
break;
case MDefinition::Opcode::Mul:
ret = lhs * rhs;
break;
case MDefinition::Opcode::Div:
if (rhs == 0) {
// Division by zero will trap at runtime.
return nullptr;
}
if (ins->toDiv()->isUnsigned()) {
ret = int64_t(uint64_t(lhs) / uint64_t(rhs));
}
else if (lhs == INT64_MIN || rhs == -1) {
// Overflow will trap at runtime.
return nullptr;
}
else {
ret = lhs / rhs;
}
break;
case MDefinition::Opcode::Mod:
if (rhs == 0) {
// Division by zero will trap at runtime.
return nullptr;
}
if (!ins->toMod()->isUnsigned() && (lhs < 0 || rhs < 0)) {
// Handle all negative values at runtime, for simplicity.
return nullptr;
}
ret = int64_t(uint64_t(lhs) % uint64_t(rhs));
break;
default:
MOZ_CRASH(
"NYI");
}
return MConstant::NewInt64(alloc, ret);
}
static MConstant* EvaluateConstantOperands(TempAllocator& alloc,
MBinaryInstruction* ins) {
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::Opcode::
BitAnd:
ret =
double(lhs->toInt32() & rhs->toInt32());
break;
case MDefinition::Opcode::
BitOr:
ret =
double(lhs->toInt32() | rhs->toInt32());
break;
case MDefinition::Opcode::BitXor:
ret =
double(lhs->toInt32() ^ rhs->toInt32());
break;
case MDefinition::Opcode::Lsh:
ret =
double(uint32_t(lhs->toInt32()) << (rhs->toInt32() & 0x1F));
break;
case MDefinition::Opcode::Rsh:
ret =
double(lhs->toInt32() >> (rhs->toInt32() & 0x1F));
break;
case MDefinition::Opcode::Ursh:
ret =
double(uint32_t(lhs->toInt32()) >> (rhs->toInt32() & 0x1F));
break;
case MDefinition::Opcode::Add:
ret = lhs->numberToDouble() + rhs->numberToDouble();
break;
case MDefinition::Opcode::Sub:
ret = lhs->numberToDouble() - rhs->numberToDouble();
break;
case MDefinition::Opcode::Mul:
ret = lhs->numberToDouble() * rhs->numberToDouble();
break;
case MDefinition::Opcode::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::Opcode::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");
}
if (ins->type() == MIRType::Float32) {
return MConstant::NewFloat32(alloc,
float(ret));
}
if (ins->type() == MIRType::
Double) {
return MConstant::
New(alloc, DoubleValue(ret));
}
MOZ_ASSERT(ins->type() == MIRType::Int32);
// If the result isn't an int32 (for example, a division where the numerator
// isn't evenly divisible by the denominator), decline folding.
int32_t intRet;
if (!mozilla::NumberIsInt32(ret, &intRet)) {
return nullptr;
}
return MConstant::
New(alloc, Int32Value(intRet));
}
static MConstant* EvaluateConstantNaNOperand(MBinaryInstruction* ins) {
auto* left = ins->lhs();
auto* right = ins->rhs();
MOZ_ASSERT(IsTypeRepresentableAsDouble(left->type()));
MOZ_ASSERT(IsTypeRepresentableAsDouble(right->type()));
MOZ_ASSERT(left->type() == ins->type());
MOZ_ASSERT(right->type() == ins->type());
// Don't fold NaN if we can't return a floating point type.
if (!IsFloatingPointType(ins->type())) {
return nullptr;
}
MOZ_ASSERT(!left->isConstant() || !right->isConstant(),
"EvaluateConstantOperands should have handled this case");
// One operand must be a constant NaN.
MConstant* cst;
if (left->isConstant()) {
cst = left->toConstant();
}
else if (right->isConstant()) {
cst = right->toConstant();
}
else {
return nullptr;
}
if (!std::isnan(cst->numberToDouble())) {
return nullptr;
}
// Fold to constant NaN.
return cst;
}
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->setMustPreserveNaN(ins->mustPreserveNaN());
return mul;
}
#ifdef JS_JITSPEW
const char* MDefinition::opName()
const {
return OpcodeName(op()); }
void MDefinition::printName(GenericPrinter& out)
const {
PrintOpcodeName(out, op());
out.printf(
"%u", id());
}
#endif
HashNumber MDefinition::valueHash()
const {
HashNumber out = HashNumber(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;
}
HashNumber MNullaryInstruction::valueHash()
const {
HashNumber hash = HashNumber(op());
if (MDefinition* dep = dependency()) {
hash = addU32ToHash(hash, dep->id());
}
MOZ_ASSERT(hash == MDefinition::valueHash());
return hash;
}
HashNumber MUnaryInstruction::valueHash()
const {
HashNumber hash = HashNumber(op());
hash = addU32ToHash(hash, getOperand(0)->id());
if (MDefinition* dep = dependency()) {
hash = addU32ToHash(hash, dep->id());
}
MOZ_ASSERT(hash == MDefinition::valueHash());
return hash;
}
HashNumber MBinaryInstruction::valueHash()
const {
HashNumber hash = HashNumber(op());
hash = addU32ToHash(hash, getOperand(0)->id());
hash = addU32ToHash(hash, getOperand(1)->id());
if (MDefinition* dep = dependency()) {
hash = addU32ToHash(hash, dep->id());
}
MOZ_ASSERT(hash == MDefinition::valueHash());
return hash;
}
HashNumber MTernaryInstruction::valueHash()
const {
HashNumber hash = HashNumber(op());
hash = addU32ToHash(hash, getOperand(0)->id());
hash = addU32ToHash(hash, getOperand(1)->id());
hash = addU32ToHash(hash, getOperand(2)->id());
if (MDefinition* dep = dependency()) {
hash = addU32ToHash(hash, dep->id());
}
MOZ_ASSERT(hash == MDefinition::valueHash());
return hash;
}
HashNumber MQuaternaryInstruction::valueHash()
const {
HashNumber hash = HashNumber(op());
hash = addU32ToHash(hash, getOperand(0)->id());
hash = addU32ToHash(hash, getOperand(1)->id());
hash = addU32ToHash(hash, getOperand(2)->id());
hash = addU32ToHash(hash, getOperand(3)->id());
if (MDefinition* dep = dependency()) {
hash = addU32ToHash(hash, dep->id());
}
MOZ_ASSERT(hash == MDefinition::valueHash());
return hash;
}
const MDefinition* MDefinition::skipObjectGuards()
const {
const MDefinition* result =
this;
// These instructions don't modify the object and just guard specific
// properties.
while (
true) {
if (result->isGuardShape()) {
result = result->toGuardShape()->object();
continue;
}
if (result->isGuardNullProto()) {
result = result->toGuardNullProto()->object();
continue;
}
if (result->isGuardProto()) {
result = result->toGuardProto()->object();
continue;
}
break;
}
return result;
}
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 true;
}
bool MDefinition::definitelyType(std::initializer_list<MIRType> types)
const {
#ifdef DEBUG
// Only support specialized, non-magic types.
auto isSpecializedNonMagic = [](MIRType type) {
return type <= MIRType::Object;
};
#endif
MOZ_ASSERT(types.size() > 0);
MOZ_ASSERT(std::all_of(types.begin(), types.end(), isSpecializedNonMagic));
if (type() == MIRType::Value) {
return false;
}
return std::find(types.begin(), types.end(), type()) != types.end();
}
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 Opcode::StoreFixedSlot:
value = store->toStoreFixedSlot()->value();
break;
case Opcode::StoreDynamicSlot:
value = store->toStoreDynamicSlot()->value();
break;
case Opcode::StoreElement:
value = store->toStoreElement()->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;
}
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* other) {
MResumePoint* resumePoint = other->resumePoint_;
other->resumePoint_ = nullptr;
resumePoint->resetInstruction();
setResumePoint(resumePoint);
}
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;
}
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());
default:
break;
}
return nullptr;
}
class UsesIterator {
MDefinition* def_;
public:
explicit UsesIterator(MDefinition* def) : def_(def) {}
auto begin()
const {
return def_->usesBegin(); }
auto end()
const {
return def_->usesEnd(); }
};
static bool AllInstructionsDeadIfUnused(MBasicBlock* block) {
for (
auto* ins : *block) {
// Skip trivial instructions.
if (ins->isNop() || ins->isGoto()) {
continue;
}
// All uses must be within the current block.
for (
auto* use : UsesIterator(ins)) {
if (use->consumer()->block() != block) {
return false;
}
}
// All instructions within this block must be dead if unused.
if (!DeadIfUnused(ins)) {
return false;
}
}
return true;
}
MDefinition* MTest::foldsNeedlessControlFlow(TempAllocator& alloc) {
// All instructions within both successors need be dead if unused.
if (!AllInstructionsDeadIfUnused(ifTrue()) ||
!AllInstructionsDeadIfUnused(ifFalse())) {
return nullptr;
}
// Both successors must have the same target successor.
if (ifTrue()->numSuccessors() != 1 || ifFalse()->numSuccessors() != 1) {
return nullptr;
}
if (ifTrue()->getSuccessor(0) != ifFalse()->getSuccessor(0)) {
return nullptr;
}
// The target successor's phis must be redundant. Redundant phis should have
// been removed in an earlier pass, so only check if any phis are present,
// which is a stronger condition.
if (ifTrue()->successorWithPhis()) {
return nullptr;
}
return MGoto::
New(alloc, ifTrue());
}
// If a test is dominated by either the true or false path of a previous test of
// the same condition, then the test is redundant and can be converted into a
// goto true or goto false, respectively.
MDefinition* MTest::foldsRedundantTest(TempAllocator& alloc) {
MBasicBlock* myBlock = this->block();
MDefinition* originalInput = getOperand(0);
// Handle single and double negatives. This ensures that we do not miss a
// folding opportunity due to a condition being inverted.
MDefinition* newInput = input();
bool inverted =
false;
if (originalInput->isNot()) {
newInput = originalInput->toNot()->input();
inverted =
true;
if (originalInput->toNot()->input()->isNot()) {
newInput = originalInput->toNot()->input()->toNot()->input();
inverted =
false;
}
}
// The specific order of traversal does not matter. If there are multiple
// dominating redundant tests, they will either agree on direction (in which
// case we will prune the same way regardless of order), or they will
// disagree, in which case we will eventually be marked entirely dead by the
// folding of the redundant parent.
for (MUseIterator i(newInput->usesBegin
()), e(newInput->usesEnd()); i != e;
++i) {
if (!i->consumer()->isDefinition()) {
continue;
}
if (!i->consumer()->toDefinition()->isTest()) {
continue;
}
MTest* otherTest = i->consumer()->toDefinition()->toTest();
if (otherTest ==
this) {
continue;
}
if (otherTest->ifFalse()->dominates(myBlock)) {
// This test cannot be true, so fold to a goto false.
return MGoto::
New(alloc, inverted ? ifTrue() : ifFalse());
}
if (otherTest->ifTrue()->dominates(myBlock)) {
// This test cannot be false, so fold to a goto true.
return MGoto::
New(alloc, inverted ? ifFalse() : ifTrue());
}
}
return nullptr;
}
MDefinition* MTest::foldsTo(TempAllocator& alloc) {
if (MDefinition* def = foldsRedundantTest(alloc)) {
return def;
}
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;
}
AliasSet MThrow::getAliasSet()
const {
return AliasSet::Store(AliasSet::ExceptionState);
}
AliasSet MThrowWithStack::getAliasSet()
const {
return AliasSet::Store(AliasSet::ExceptionState);
}
AliasSet MNewArrayDynamicLength::getAliasSet()
const {
return AliasSet::Store(AliasSet::ExceptionState);
}
AliasSet MNewTypedArrayDynamicLength::getAliasSet()
const {
return AliasSet::Store(AliasSet::ExceptionState);
}
#ifdef JS_JITSPEW
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);
out.printf(
":%s", StringFromMIRType(getOperand(j)->type()));
}
else {
out.printf(
"(null)");
}
}
}
void MDefinition::dump(GenericPrinter& out)
const {
printName(out);
out.printf(
":%s", StringFromMIRType(type()));
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:%u\n", linkWord, script->filename(), lineno);
rp = rp->caller();
linkWord =
"in";
}
}
void MDefinition::dumpLocation()
const {
Fprinter out(stderr);
dumpLocation(out);
out.finish();
}
#endif
#
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::hasOneLiveDefUse()
const {
bool hasOneDefUse =
false;
for (MUseIterator i(uses_.begin()); i != uses_.end(); i++) {
if (!(*i)->consumer()->isDefinition()) {
continue;
}
MDefinition* def = (*i)->consumer()->toDefinition();
if (def->isRecoveredOnBailout()) {
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;
}
MDefinition* MDefinition::maybeSingleDefUse()
const {
MUseDefIterator use(
this);
if (!use) {
// No def-uses.
return nullptr;
}
MDefinition* useDef = use.def();
use++;
if (use) {
// More than one def-use.
return nullptr;
}
return useDef;
}
MDefinition* MDefinition::maybeMostRecentlyAddedDefUse()
const {
MUseDefIterator use(
this);
if (!use) {
// No def-uses.
return nullptr;
}
MDefinition* mostRecentUse = use.def();
#ifdef DEBUG
// This function relies on addUse adding new uses to the front of the list.
// Check this invariant by asserting the next few uses are 'older'. Skip this
// for phis because setBackedge can add a new use for a loop phi even if the
// loop body has a use with an id greater than the loop phi's id.
if (!mostRecentUse->isPhi()) {
static constexpr size_t NumUsesToCheck = 3;
use++;
for (size_t i = 0; use && i < NumUsesToCheck; i++, use++) {
MOZ_ASSERT(use.def()->id() <= mostRecentUse->id());
}
}
#endif
return mostRecentUse;
}
void MDefinition::replaceAllUsesWith(MDefinition* dom) {
for (size_t i = 0, e = numOperands(); i < e; ++i) {
getOperand(i)->setImplicitlyUsedUnchecked();
}
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 (isImplicitlyUsed()) {
dom->setImplicitlyUsedUnchecked();
}
for (MUseIterator i(usesBegin()), e(usesEnd()); i != e; ++i) {
i->setProducerUnchecked(dom);
}
dom->uses_.takeElements(uses_);
}
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);
}
}
MConstant* MConstant::
New(TempAllocator& alloc,
const Value& v) {
return new (alloc) MConstant(alloc, v);
}
MConstant* MConstant::
New(TempAllocator::Fallible alloc,
const Value& v) {
return new (alloc) MConstant(alloc.alloc, v);
}
MConstant* MConstant::NewFloat32(TempAllocator& alloc,
double d) {
MOZ_ASSERT(std::isnan(d) || d ==
double(
float(d)));
return new (alloc) MConstant(
float(d));
}
MConstant* MConstant::NewInt64(TempAllocator& alloc, int64_t i) {
return new (alloc) MConstant(MIRType::Int64, i);
}
MConstant* MConstant::NewIntPtr(TempAllocator& alloc, intptr_t i) {
return new (alloc) MConstant(MIRType::IntPtr, 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::NewObject(TempAllocator& alloc, JSObject* v) {
return new (alloc) MConstant(v);
}
MConstant* MConstant::NewShape(TempAllocator& alloc, Shape* s) {
return new (alloc) MConstant(s);
}
static MIRType MIRTypeFromValue(
const js::Value& vp) {
if (vp.isDouble()) {
return MIRType::
Double;
}
if (vp.isMagic()) {
switch (vp.whyMagic()) {
case JS_OPTIMIZED_OUT:
return MIRType::MagicOptimizedOut;
case JS_ELEMENTS_HOLE:
return MIRType::MagicHole;
case JS_IS_CONSTRUCTING:
return MIRType::MagicIsConstructing;
case JS_UNINITIALIZED_LEXICAL:
return MIRType::MagicUninitializedLexical;
default:
MOZ_ASSERT_UNREACHABLE(
"Unexpected magic constant");
}
}
return MIRTypeFromValueType(vp.extractNonDoubleType());
}
MConstant::MConstant(TempAllocator& alloc,
const js::Value& vp)
: MNullaryInstruction(classOpcode) {
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: {
JSString* str = vp.toString();
if (str->isAtomRef()) {
str = str->atom();
}
MOZ_ASSERT(!IsInsideNursery(str));
MOZ_ASSERT(str->isAtom());
payload_.str = vp.toString();
break;
}
case MIRType::Symbol:
payload_.sym = vp.toSymbol();
break;
case MIRType::BigInt:
MOZ_ASSERT(!IsInsideNursery(vp.toBigInt()));
payload_.bi = vp.toBigInt();
break;
case MIRType::Object:
MOZ_ASSERT(!IsInsideNursery(&vp.toObject()));
payload_.obj = &vp.toObject();
break;
case MIRType::MagicOptimizedOut:
case MIRType::MagicHole:
case MIRType::MagicIsConstructing:
case MIRType::MagicUninitializedLexical:
break;
default:
MOZ_CRASH(
"Unexpected type");
}
setMovable();
}
MConstant::MConstant(JSObject* obj) : MNullaryInstruction(classOpcode) {
MOZ_ASSERT(!IsInsideNursery(obj));
setResultType(MIRType::Object);
payload_.obj = obj;
setMovable();
}
MConstant::MConstant(Shape* shape) : MNullaryInstruction(classOpcode) {
setResultType(MIRType::Shape);
payload_.shape = shape;
setMovable();
}
MConstant::MConstant(
float f) : MNullaryInstruction(classOpcode) {
setResultType(MIRType::Float32);
payload_.f = f;
setMovable();
}
MConstant::MConstant(MIRType type, int64_t i)
: MNullaryInstruction(classOpcode) {
MOZ_ASSERT(type == MIRType::Int64 || type == MIRType::IntPtr);
setResultType(type);
if (type == MIRType::Int64) {
payload_.i64 = i;
}
else {
payload_.iptr = 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:
#
if MOZ_LITTLE_ENDIAN()
MOZ_ASSERT((payload_.asBits >> 32) == 0);
#
else
MOZ_ASSERT((payload_.asBits << 32) == 0);
# endif
break;
case MIRType::
Boolean:
#
if MOZ_LITTLE_ENDIAN()
MOZ_ASSERT((payload_.asBits >> 1) == 0);
#
else
MOZ_ASSERT((payload_.asBits & ~(1ULL << 56)) == 0);
# endif
break;
case MIRType::
Double:
case MIRType::Int64:
break;
case MIRType::String:
case MIRType::Object:
case MIRType::Symbol:
case MIRType::BigInt:
case MIRType::IntPtr:
case MIRType::Shape:
#
if MOZ_LITTLE_ENDIAN()
MOZ_ASSERT_IF(JS_BITS_PER_WORD == 32, (payload_.asBits >> 32) == 0);
#
else
MOZ_ASSERT_IF(JS_BITS_PER_WORD == 32, (payload_.asBits << 32) == 0);
# endif
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();
return ConstantValueHash(type(), payload_.asBits);
}
HashNumber MConstantProto::valueHash()
const {
HashNumber hash = protoObject()->valueHash();
const MDefinition* receiverObject = getReceiverObject();
if (receiverObject) {
hash = addU32ToHash(hash, receiverObject->id());
}
return hash;
}
bool MConstant::congruentTo(
const MDefinition* ins)
const {
return ins->isConstant() && equals(ins->toConstant());
}
#ifdef JS_JITSPEW
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", uint32_t(toInt32()));
break;
case MIRType::Int64:
out.printf(
"0x%" PRIx64, uint64_t(toInt64()));
break;
case MIRType::IntPtr:
out.printf(
"0x%" PRIxPTR, uintptr_t(toIntPtr()));
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->maybePartialDisplayAtom()) {
out.put(
"function ");
EscapedStringPrinter(out, fun->maybePartialDisplayAtom(), 0);
}
else {
out.put(
"unnamed function");
}
if (fun->hasBaseScript()) {
BaseScript* script = fun->baseScript();
out.printf(
" (%s:%u)", 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::BigInt:
out.printf(
"BigInt at %p", (
void*)toBigInt());
break;
case MIRType::String:
out.printf(
"string %p", (
void*)toString());
break;
case MIRType::Shape:
out.printf(
"shape at %p", (
void*)toShape());
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");
}
}
#endif
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::BigInt:
return BigIntValue(toBigInt());
case MIRType::Object:
return ObjectValue(toObject());
case MIRType::Shape:
return PrivateGCThingValue(toShape());
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::IntPtr:
*res = toIntPtr() != 0;
return true;
case MIRType::
Double:
*res = !std::isnan(toDouble()) && toDouble() != 0.0;
return true;
case MIRType::Float32:
*res = !std::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::BigInt:
*res = !toBigInt()->isZero();
return true;
case MIRType::String:
*res = toString()->length() != 0;
return true;
case MIRType::Object:
// TODO(Warp): Lazy groups have been removed.
// We have to call EmulatesUndefined but that reads obj->group->clasp
// and so it's racy when the object has a lazy group. The main callers
// of this (MTest, MNot) already know how to fold the object case, so
// just give up.
return false;
default:
MOZ_ASSERT(IsMagicType(type()));
return false;
}
}
#ifdef JS_JITSPEW
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 MTypeOfIs::printOpcode(GenericPrinter& out)
const {
MDefinition::printOpcode(out);
out.printf(
" %s", CodeName(jsop()));
const char* name =
"";
switch (jstype()) {
case JSTYPE_UNDEFINED:
name =
"undefined";
break;
case JSTYPE_OBJECT:
name =
"object";
break;
case JSTYPE_FUNCTION:
name =
"function";
break;
case JSTYPE_STRING:
name =
"string";
break;
case JSTYPE_NUMBER:
name =
"number";
break;
case JSTYPE_BOOLEAN:
name =
"boolean";
break;
case JSTYPE_SYMBOL:
name =
"symbol";
break;
case JSTYPE_BIGINT:
name =
"bigint";
break;
# ifdef ENABLE_RECORD_TUPLE
case JSTYPE_RECORD:
case JSTYPE_TUPLE:
# endif
case JSTYPE_LIMIT:
MOZ_CRASH(
"Unexpected type");
}
out.printf(
" '%s'", name);
}
void MLoadUnboxedScalar::printOpcode(GenericPrinter& out)
const {
MDefinition::printOpcode(out);
out.printf(
" %s", Scalar::name(storageType()));
}
void MLoadDataViewElement::printOpcode(GenericPrinter& out)
const {
MDefinition::printOpcode(out);
out.printf(
" %s", Scalar::name(storageType()));
}
void MAssertRange::printOpcode(GenericPrinter& out)
const {
MDefinition::printOpcode(out);
out.put(
" ");
assertedRange()->dump(out);
}
void MNearbyInt::printOpcode(GenericPrinter& out)
const {
MDefinition::printOpcode(out);
const char* roundingModeStr = nullptr;
switch (roundingMode_) {
case RoundingMode::Up:
roundingModeStr =
"(up)";
break;
case RoundingMode::Down:
roundingModeStr =
"(down)";
break;
case RoundingMode::NearestTiesToEven:
roundingModeStr =
"(nearest ties even)";
break;
case RoundingMode::TowardsZero:
roundingModeStr =
"(towards zero)";
break;
}
out.printf(
" %s", roundingModeStr);
}
#endif
AliasSet MRandom::getAliasSet()
const {
return AliasSet::Store(AliasSet::RNG); }
MDefinition* MSign::foldsTo(TempAllocator& alloc) {
MDefinition* input = getOperand(0);
if (!input->isConstant() ||
!input->toConstant()->isTypeRepresentableAsDouble()) {
return this;
}
double in = input->toConstant()->numberToDouble();
double out = js::math_sign_impl(in);
if (type() == MIRType::Int32) {
// Decline folding if this is an int32 operation, but the result type
// isn't an int32.
Value outValue = NumberValue(out);
if (!outValue.isInt32()) {
return this;
}
return MConstant::
New(alloc, outValue);
}
return MConstant::
New(alloc, DoubleValue(out));
}
const char* MMathFunction::FunctionName(UnaryMathFunction
function) {
return GetUnaryMathFunctionName(
function);
}
#ifdef JS_JITSPEW
void MMathFunction::printOpcode(GenericPrinter& out)
const {
MDefinition::printOpcode(out);
out.printf(
" %s", FunctionName(
function()));
}
#endif
MDefinition* MMathFunction::foldsTo(TempAllocator& alloc) {
MDefinition* input = getOperand(0);
if (!input->isConstant() ||
!input->toConstant()->isTypeRepresentableAsDouble()) {
return this;
}
UnaryMathFunctionType funPtr = GetUnaryMathFunctionPtr(
function());
double in = input->toConstant()->numberToDouble();
// The function pointer call can't GC.
JS::AutoSuppressGCAnalysis nogc;
double out = funPtr(in);
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::isLockfreeJS(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;
#ifdef JS_JITSPEW
void MParameter::printOpcode(GenericPrinter& out)
const {
PrintOpcodeName(out, op());
if (index() == THIS_SLOT) {
out.printf(
" THIS_SLOT");
}
else {
out.printf(
" %d", index());
}
}
#endif
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* nativeFun, uint16_t nargs,
FunctionFlags flags)
: nativeFun_(nativeFun), nargs_(nargs), flags_(flags) {
MOZ_ASSERT_IF(nativeFun, isNativeWithoutJitEntry());
#ifdef DEBUG
// If we are not running off-main thread we can assert that the
// metadata is consistent.
if (!CanUseExtraThreads() && nativeFun) {
MOZ_ASSERT(nativeFun->nargs() == nargs);
MOZ_ASSERT(nativeFun->isNativeWithoutJitEntry() ==
isNativeWithoutJitEntry());
MOZ_ASSERT(nativeFun->hasJitEntry() == hasJitEntry());
MOZ_ASSERT(nativeFun->isConstructor() == isConstructor());
MOZ_ASSERT(nativeFun->isClassConstructor() == isClassConstructor());
}
#endif
}
MCall* MCall::
New(TempAllocator& alloc, WrappedFunction* target, size_t maxArgc,
size_t numActualArgs, bool construct, bool ignoresReturnValue,
bool isDOMCall, mozilla::Maybe<DOMObjectKind> objectKind,
mozilla::Maybe<gc::Heap> initialHeap) {
MOZ_ASSERT(isDOMCall == objectKind.isSome());
MOZ_ASSERT(isDOMCall == initialHeap.isSome());
MOZ_ASSERT(maxArgc >= numActualArgs);
MCall* ins;
if (isDOMCall) {
MOZ_ASSERT(!construct);
ins =
new (alloc)
MCallDOMNative(target, numActualArgs, *objectKind, *initialHeap);
}
else {
ins =
new (alloc) MCall(target, numActualArgs, construct, ignoresReturnValue);
}
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 (!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()->hasJitInfo());
return getSingleTarget()->jitInfo();
}
MCallClassHook* MCallClassHook::
New(TempAllocator& alloc, JSNative target,
uint32_t argc, bool constructing) {
auto* ins =
new (alloc) MCallClassHook(target, constructing);
// Add callee + |this| + (if constructing) newTarget.
uint32_t numOperands = 2 + argc + constructing;
if (!ins->init(alloc, numOperands)) {
return nullptr;
}
return ins;
}
MDefinition* MStringLength::foldsTo(TempAllocator& alloc) {
if (string()->isConstant()) {
JSString* str = string()->toConstant()->toString();
return MConstant::
New(alloc, Int32Value(str->length()));
}
// MFromCharCode returns a one-element string.
if (string()->isFromCharCode()) {
return MConstant::
New(alloc, Int32Value(1));
}
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;
}
MDefinition* MStringConvertCase::foldsTo(TempAllocator& alloc) {
MDefinition* string = this->string();
// Handle the pattern |str[idx].toUpperCase()| and simplify it from
// |StringConvertCase(FromCharCode(CharCodeAt(str, idx)))| to just
// |CharCodeConvertCase(CharCodeAt(str, idx))|.
if (string->isFromCharCode()) {
auto* charCode = string->toFromCharCode()->code();
auto mode = mode_ == Mode::LowerCase ? MCharCodeConvertCase::LowerCase
: MCharCodeConvertCase::UpperCase;
return MCharCodeConvertCase::
New(alloc, charCode, mode);
}
// Handle the pattern |num.toString(base).toUpperCase()| and simplify it to
// directly return the string representation in the correct case.
if (string->isInt32ToStringWithBase()) {
auto* toString = string->toInt32ToStringWithBase();
bool lowerCase = mode_ == Mode::LowerCase;
if (toString->lowerCase() == lowerCase) {
return toString;
}
return MInt32ToStringWithBase::
New(alloc, toString->input(),
toString->base(), lowerCase);
}
return this;
}
static bool IsSubstrTo(MSubstr* substr, int32_t len) {
// We want to match this pattern:
//
// Substr(string, Constant(0), Min(Constant(length), StringLength(string)))
//
// which is generated for the self-hosted `String.p.{substring,slice,substr}`
// functions when called with constants `start` and `end` parameters.
auto isConstantZero = [](auto* def) {
return def->isConstant() && def->toConstant()->isInt32(0);
};
if (!isConstantZero(substr->begin())) {
return false;
}
auto* length = substr->length();
if (length->isBitOr()) {
// Unnecessary bit-ops haven't yet been removed.
auto* bitOr = length->toBitOr();
if (isConstantZero(bitOr->lhs())) {
length = bitOr->rhs();
}
else if (isConstantZero(bitOr->rhs())) {
length = bitOr->lhs();
}
}
if (!length->isMinMax() || length->toMinMax()->isMax()) {
return false;
}
auto* min = length->toMinMax();
if (!min->lhs()->isConstant() && !min->rhs()->isConstant()) {
return false;
}
auto* minConstant = min->lhs()->isConstant() ? min->lhs()->toConstant()
: min->rhs()->toConstant();
auto* minOperand = min->lhs()->isConstant() ? min->rhs() : min->lhs();
if (!minOperand->isStringLength() ||
minOperand->toStringLength()->string() != substr->string()) {
return false;
}
// Ensure |len| matches the substring's length.
return minConstant->isInt32(len);
}
MDefinition* MSubstr::foldsTo(TempAllocator& alloc) {
// Fold |str.substring(0, 1)| to |str.charAt(0)|.
if (!IsSubstrTo(
this, 1)) {
return this;
}
auto* charCode = MCharCodeAtOrNegative::
New(alloc, string(), begin());
block()->insertBefore(
this, charCode);
return MFromCharCodeEmptyIfNegative::
New(alloc, charCode);
}
MDefinition* MCharCodeAt::foldsTo(TempAllocator& alloc) {
MDefinition* string = this->string();
if (!string->isConstant() && !string->isFromCharCode()) {
return this;
}
MDefinition* index = this->index();
if (index->isSpectreMaskIndex()) {
index = index->toSpectreMaskIndex()->index();
}
if (!index->isConstant()) {
return this;
}
int32_t idx = index->toConstant()->toInt32();
// Handle the pattern |s[idx].charCodeAt(0)|.
if (string->isFromCharCode()) {
if (idx != 0) {
return this;
}
// Simplify |CharCodeAt(FromCharCode(CharCodeAt(s, idx)), 0)| to just
// |CharCodeAt(s, idx)|.
auto* charCode = string->toFromCharCode()->code();
if (!charCode->isCharCodeAt()) {
return this;
}
return charCode;
}
JSLinearString* str = &string->toConstant()->toString()->asLinear();
if (idx < 0 || uint32_t(idx) >= str->length()) {
return this;
}
char16_t ch = str->latin1OrTwoByteChar(idx);
return MConstant::
New(alloc, Int32Value(ch));
}
MDefinition* MCodePointAt::foldsTo(TempAllocator& alloc) {
MDefinition* string = this->string();
if (!string->isConstant() && !string->isFromCharCode()) {
return this;
}
MDefinition* index = this->index();
if (index->isSpectreMaskIndex()) {
index = index->toSpectreMaskIndex()->index();
}
if (!index->isConstant()) {
return this;
}
int32_t idx = index->toConstant()->toInt32();
// Handle the pattern |s[idx].codePointAt(0)|.
if (string->isFromCharCode()) {
if (idx != 0) {
return this;
}
// Simplify |CodePointAt(FromCharCode(CharCodeAt(s, idx)), 0)| to just
// |CharCodeAt(s, idx)|.
auto* charCode = string->toFromCharCode()->code();
if (!charCode->isCharCodeAt()) {
return this;
}
return charCode;
}
JSLinearString* str = &string->toConstant()->toString()->asLinear();
if (idx < 0 || uint32_t(idx) >= str->length()) {
return this;
}
char32_t first = str->latin1OrTwoByteChar(idx);
if (unicode::IsLeadSurrogate(first) && uint32_t(idx) + 1 < str->length()) {
char32_t second = str->latin1OrTwoByteChar(idx + 1);
if (unicode::IsTrailSurrogate(second)) {
first = unicode::UTF16Decode(first, second);
}
}
return MConstant::
New(alloc, Int32Value(first));
}
MDefinition* MToRelativeStringIndex::foldsTo(TempAllocator& alloc) {
MDefinition* index = this->index();
MDefinition* length = this->length();
if (!index->isConstant()) {
return this;
}
if (!length->isStringLength() && !length->isConstant()) {
return this;
}
MOZ_ASSERT_IF(length->isConstant(), length->toConstant()->toInt32() >= 0);
int32_t relativeIndex = index->toConstant()->toInt32();
if (relativeIndex >= 0) {
return index;
}
// Safe to truncate because |length| is never negative.
return MAdd::
New(alloc, index, length, TruncateKind::Truncate);
}
template <size_t Arity>
[[nodiscard]]
static bool EnsureFloatInputOrConvert(
MAryInstruction<Arity>* owner, TempAllocator& alloc) {
MOZ_ASSERT(!IsFloatingPointType(owner->type()),
"Floating point types must check consumers");
if (AllOperandsCanProduceFloat32(owner)) {
return true;
}
ConvertOperandsToDouble(owner, alloc);
return false;
}
template <size_t Arity>
[[nodiscard]]
static bool EnsureFloatConsumersAndInputOrConvert(
MAryInstruction<Arity>* owner, TempAllocator& alloc) {
MOZ_ASSERT(IsFloatingPointType(owner->type()),
"Integer types don't need to check consumers");
if (AllOperandsCanProduceFloat32(owner) &&
CheckUsesAreFloat32Consumers(owner)) {
return true;
}
ConvertOperandsToDouble(owner, alloc);
return false;
}
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;
}
}
void MTrunc::trySpecializeFloat32(TempAllocator& alloc) {
MOZ_ASSERT(type() == MIRType::Int32);
if (EnsureFloatInputOrConvert(
this, alloc)) {
specialization_ = MIRType::Float32;
}
}
void MNearbyInt::trySpecializeFloat32(TempAllocator& alloc) {
if (EnsureFloatConsumersAndInputOrConvert(
this, alloc)) {
specialization_ = MIRType::Float32;
setResultType(MIRType::Float32);
}
}
MGoto* MGoto::
New(TempAllocator& alloc, MBasicBlock* 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); }
MDefinition* MBox::foldsTo(TempAllocator& alloc) {
if (input()->isUnbox()) {
return input()->toUnbox()->input();
}
return this;
}
#ifdef JS_JITSPEW
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::BigInt:
out.printf(
"to BigInt");
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;
default:
break;
}
}
#endif
MDefinition* MUnbox::foldsTo(TempAllocator& alloc) {
if (input()->isBox()) {
MDefinition* unboxed = input()->toBox()->input();
// Fold MUnbox(MBox(x)) => x if types match.
if (unboxed->type() == type()) {
if (fallible()) {
unboxed->setImplicitlyUsedUnchecked();
}
return unboxed;
}
// Fold MUnbox(MBox(x)) => MToDouble(x) if possible.
if (type() == MIRType::
Double &&
IsTypeRepresentableAsDouble(unboxed->type())) {
if (unboxed->isConstant()) {
return MConstant::
New(
alloc, DoubleValue(unboxed->toConstant()->numberToDouble()));
}
return MToDouble::
New(alloc, unboxed);
}
// MUnbox<Int32>(MBox<Double>(x)) will always fail, even if x can be
// represented as an Int32. Fold to avoid unnecessary bailouts.
if (type() == MIRType::Int32 && unboxed->type() == MIRType::
Double) {
auto* folded = MToNumberInt32::
New(alloc, unboxed,
IntConversionInputKind::NumbersOnly);
folded->setGuard();
return folded;
}
}
return this;
}
#ifdef DEBUG
void MPhi::assertLoopPhi()
const {
// getLoopPredecessorOperand and getLoopBackedgeOperand rely on these
// predecessors being at known indices.
if (block()->numPredecessors() == 2) {
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);
}
else {
// After we remove fake loop predecessors for loop headers that
// are only reachable via OSR, the only predecessor is the
// loop backedge.
MOZ_ASSERT(block()->numPredecessors() == 1);
MOZ_ASSERT(block()->graph().osrBlock());
MOZ_ASSERT(!block()->graph().canBuildDominators());
MBasicBlock* back = block()->getPredecessor(0);
MOZ_ASSERT(back == block()->backedge());
MOZ_ASSERT(back->successorWithPhis() == block());
MOZ_ASSERT(back->positionInPhiSuccessor() == 0);
}
}
#endif
MDefinition* MPhi::getLoopPredecessorOperand()
const {
// This should not be called after removing fake loop predecessors.
MOZ_ASSERT(block()->numPredecessors() == 2);
assertLoopPhi();
return getOperand(0);
}
MDefinition* MPhi::getLoopBackedgeOperand()
const {
assertLoopPhi();
uint32_t idx = block()->numPredecessors() == 2 ? 1 : 0;
return getOperand(idx);
}
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.
--> --------------------
--> maximum size reached
--> --------------------