Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#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,
bool* ptypeChange = nullptr) {
MDefinition* left = ins->getOperand(0);
MDefinition* right = ins->getOperand(1);
MOZ_ASSERT(IsTypeRepresentableAsDouble(left->type()));
MOZ_ASSERT(IsTypeRepresentableAsDouble(right->type()));
if (!left->isConstant() || !right->isConstant()) {
return nullptr;
}
MConstant* lhs = left->toConstant();
MConstant* rhs = right->toConstant();
double ret = JS::GenericNaN();
switch (ins->op()) {
case MDefinition::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));
}
Value retVal;
retVal.setNumber(JS::CanonicalizeNaN(ret));
// If this was an int32 operation but the result isn't an int32 (for
// example, a division where the numerator isn't evenly divisible by the
// denominator), decline folding.
MOZ_ASSERT(ins->type() == MIRType::Int32);
if (!retVal.isInt32()) {
if (ptypeChange) {
*ptypeChange = true;
}
return nullptr;
}
return MConstant::New(alloc, retVal);
}
static 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()) {