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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef jit_MoveResolver_h
#define jit_MoveResolver_h
#include <algorithm>
#include "jit/InlineList.h"
#include "jit/JitAllocPolicy.h"
#include "jit/Registers.h"
#include "jit/RegisterSets.h"
#include "jit/shared/Assembler-shared.h"
namespace js {
namespace jit {
class MacroAssembler;
// This is similar to Operand, but carries more information. We're also not
// guaranteed that Operand looks like this on all ISAs.
class MoveOperand {
public:
enum class Kind : uint8_t {
// A register in the "integer", aka "general purpose", class.
Reg,
#ifdef JS_CODEGEN_REGISTER_PAIR
// Two consecutive "integer" registers (aka "general purpose"). The even
// register contains the lower part, the odd register has the high bits
// of the content.
RegPair,
#endif
// A register in the "float" register class.
FloatReg,
// A memory region.
Memory,
// The address of a memory region.
EffectiveAddress
};
private:
Kind kind_;
uint8_t code_;
int32_t disp_;
static_assert(std::max(Registers::Total, FloatRegisters::Total) <= UINT8_MAX,
"Any register code must fit in code_");
public:
MoveOperand() = delete;
explicit MoveOperand(Register reg)
: kind_(Kind::Reg), code_(reg.code()), disp_(0) {}
explicit MoveOperand(FloatRegister reg)
: kind_(Kind::FloatReg), code_(reg.code()), disp_(0) {}
MoveOperand(Register reg, int32_t disp, Kind kind = Kind::Memory)
: kind_(kind), code_(reg.code()), disp_(disp) {
MOZ_ASSERT(isMemoryOrEffectiveAddress());
// With a zero offset, this is a plain reg-to-reg move.
if (disp == 0 && kind_ == Kind::EffectiveAddress) {
kind_ = Kind::Reg;
}
}
explicit MoveOperand(const Address& addr, Kind kind = Kind::Memory)
: MoveOperand(AsRegister(addr.base), addr.offset, kind) {}
MoveOperand(MacroAssembler& masm, const ABIArg& arg);
MoveOperand(const MoveOperand& other) = default;
bool isFloatReg() const { return kind_ == Kind::FloatReg; }
bool isGeneralReg() const { return kind_ == Kind::Reg; }
bool isGeneralRegPair() const {
#ifdef JS_CODEGEN_REGISTER_PAIR
return kind_ == Kind::RegPair;
#else
return false;
#endif
}
bool isMemory() const { return kind_ == Kind::Memory; }
bool isEffectiveAddress() const { return kind_ == Kind::EffectiveAddress; }
bool isMemoryOrEffectiveAddress() const {
return isMemory() || isEffectiveAddress();
}
Register reg() const {
MOZ_ASSERT(isGeneralReg());
return Register::FromCode(code_);
}
Register evenReg() const {
MOZ_ASSERT(isGeneralRegPair());
return Register::FromCode(code_);
}
Register oddReg() const {
MOZ_ASSERT(isGeneralRegPair());
return Register::FromCode(code_ + 1);
}
FloatRegister floatReg() const {
MOZ_ASSERT(isFloatReg());
return FloatRegister::FromCode(code_);
}
Register base() const {
MOZ_ASSERT(isMemoryOrEffectiveAddress());
return Register::FromCode(code_);
}
int32_t disp() const {
MOZ_ASSERT(isMemoryOrEffectiveAddress());
return disp_;
}
bool aliases(MoveOperand other) const {
// These are not handled presently, but Memory and EffectiveAddress
// only appear in controlled circumstances in the trampoline code
// which ensures these cases never come up.
MOZ_ASSERT_IF(isMemoryOrEffectiveAddress() && other.isGeneralReg(),
base() != other.reg());
MOZ_ASSERT_IF(other.isMemoryOrEffectiveAddress() && isGeneralReg(),
other.base() != reg());
// Check if one of the operand is a registe rpair, in which case, we
// have to check any other register, or register pair.
if (isGeneralRegPair() || other.isGeneralRegPair()) {
if (isGeneralRegPair() && other.isGeneralRegPair()) {
// Assume that register pairs are aligned on even registers.
MOZ_ASSERT(!evenReg().aliases(other.oddReg()));
MOZ_ASSERT(!oddReg().aliases(other.evenReg()));
// Pair of registers are composed of consecutive registers, thus
// if the first registers are aliased, then the second registers
// are aliased too.
MOZ_ASSERT(evenReg().aliases(other.evenReg()) ==
oddReg().aliases(other.oddReg()));
return evenReg().aliases(other.evenReg());
} else if (other.isGeneralReg()) {
MOZ_ASSERT(isGeneralRegPair());
return evenReg().aliases(other.reg()) || oddReg().aliases(other.reg());
} else if (isGeneralReg()) {
MOZ_ASSERT(other.isGeneralRegPair());
return other.evenReg().aliases(reg()) || other.oddReg().aliases(reg());
}
return false;
}
if (kind_ != other.kind_) {
return false;
}
if (kind_ == Kind::FloatReg) {
return floatReg().aliases(other.floatReg());
}
if (code_ != other.code_) {
return false;
}
if (isMemoryOrEffectiveAddress()) {
return disp_ == other.disp_;
}
return true;
}
bool operator==(const MoveOperand& other) const {
if (kind_ != other.kind_) {
return false;
}
if (code_ != other.code_) {
return false;
}
if (isMemoryOrEffectiveAddress()) {
return disp_ == other.disp_;
}
return true;
}
bool operator!=(const MoveOperand& other) const { return !operator==(other); }
};
// This represents a move operation.
class MoveOp {
protected:
MoveOperand from_;
MoveOperand to_;
int32_t cycleBeginSlot_ = -1;
int32_t cycleEndSlot_ = -1;
bool cycleBegin_ = false;
bool cycleEnd_ = false;
public:
enum Type : uint8_t { GENERAL, INT32, FLOAT32, DOUBLE, SIMD128 };
protected:
Type type_;
// If cycleBegin_ is true, endCycleType_ is the type of the move at the end
// of the cycle. For example, given these moves:
// INT32 move a -> b
// GENERAL move b -> a
// the move resolver starts by copying b into a temporary location, so that
// the last move can read it. This copy needs to use use type GENERAL.
Type endCycleType_;
public:
MoveOp() = delete;
MoveOp(const MoveOperand& from, const MoveOperand& to, Type type)
: from_(from),
to_(to),
type_(type),
endCycleType_(GENERAL) // initialize to silence UBSan warning
{}
bool isCycleBegin() const { return cycleBegin_; }
bool isCycleEnd() const { return cycleEnd_; }
uint32_t cycleBeginSlot() const {
MOZ_ASSERT(cycleBeginSlot_ != -1);
return cycleBeginSlot_;
}
uint32_t cycleEndSlot() const {
MOZ_ASSERT(cycleEndSlot_ != -1);
return cycleEndSlot_;
}
const MoveOperand& from() const { return from_; }
const MoveOperand& to() const { return to_; }
Type type() const { return type_; }
Type endCycleType() const {
MOZ_ASSERT(isCycleBegin());
return endCycleType_;
}
bool aliases(const MoveOperand& op) const {
return from().aliases(op) || to().aliases(op);
}
bool aliases(const MoveOp& other) const {
return aliases(other.from()) || aliases(other.to());
}
#ifdef JS_CODEGEN_ARM
void overwrite(MoveOperand& from, MoveOperand& to, Type type) {
from_ = from;
to_ = to;
type_ = type;
}
#endif
};
class MoveResolver {
private:
struct PendingMove : public MoveOp,
public TempObject,
public InlineListNode<PendingMove> {
PendingMove() = delete;
PendingMove(const MoveOperand& from, const MoveOperand& to, Type type)
: MoveOp(from, to, type) {}
void setCycleBegin(Type endCycleType, int cycleSlot) {
MOZ_ASSERT(!cycleBegin_);
cycleBegin_ = true;
cycleBeginSlot_ = cycleSlot;
endCycleType_ = endCycleType;
}
void setCycleEnd(int cycleSlot) {
MOZ_ASSERT(!cycleEnd_);
cycleEnd_ = true;
cycleEndSlot_ = cycleSlot;
}
};
using PendingMoveIterator = InlineList<MoveResolver::PendingMove>::iterator;
js::Vector<MoveOp, 16, SystemAllocPolicy> orderedMoves_;
int numCycles_;
int curCycles_;
TempObjectPool<PendingMove> movePool_;
InlineList<PendingMove> pending_;
PendingMove* findBlockingMove(const PendingMove* last);
PendingMove* findCycledMove(PendingMoveIterator* stack,
PendingMoveIterator end,
const PendingMove* first);
[[nodiscard]] bool addOrderedMove(const MoveOp& move);
void reorderMove(size_t from, size_t to);
// Internal reset function. Does not clear lists.
void resetState();
#ifdef JS_CODEGEN_ARM
bool isDoubleAliasedAsSingle(const MoveOperand& move);
#endif
public:
MoveResolver();
// Resolves a move group into two lists of ordered moves. These moves must
// be executed in the order provided. Some moves may indicate that they
// participate in a cycle. For every cycle there are two such moves, and it
// is guaranteed that cycles do not nest inside each other in the list.
//
// After calling addMove() for each parallel move, resolve() performs the
// cycle resolution algorithm. Calling addMove() again resets the resolver.
[[nodiscard]] bool addMove(const MoveOperand& from, const MoveOperand& to,
MoveOp::Type type);
[[nodiscard]] bool resolve();
void sortMemoryToMemoryMoves();
size_t numMoves() const { return orderedMoves_.length(); }
const MoveOp& getMove(size_t i) const { return orderedMoves_[i]; }
uint32_t numCycles() const { return numCycles_; }
bool hasNoPendingMoves() const { return pending_.empty(); }
void setAllocator(TempAllocator& alloc) { movePool_.setAllocator(alloc); }
};
} // namespace jit
} // namespace js
#endif /* jit_MoveResolver_h */