Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/js/src/jit/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 220 kB image not shown  

Quelle  MIR.cpp   Sprache: C

 
/* -*- 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 charconst 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

--> --------------------

Messung V0.5
C=87 H=93 G=89

¤ Dauer der Verarbeitung: 0.24 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.