// Copyright 2016, VIXL authors // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of ARM Limited nor the names of its contributors may be // used to endorse or promote products derived from this software without // specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Construct an empty CPURegList with the specified size and type. If `size` // is CPURegister::kUnknownSize and the register type requires a size, a valid // but unspecified default will be picked. static CPURegList Empty(CPURegister::RegisterType type, unsigned size = CPURegister::kUnknownSize) { return CPURegList(type, GetDefaultSizeFor(type, size), 0);
}
// Construct a CPURegList with all possible registers with the specified size // and type. If `size` is CPURegister::kUnknownSize and the register type // requires a size, a valid but unspecified default will be picked. static CPURegList All(CPURegister::RegisterType type, unsigned size = CPURegister::kUnknownSize) { unsigned number_of_registers = (CPURegister::GetMaxCodeFor(type) + 1);
RegList list = (static_cast<RegList>(1) << number_of_registers) - 1; if (type == CPURegister::kRegister) { // GetMaxCodeFor(kRegister) ignores SP, so explicitly include it.
list |= (static_cast<RegList>(1) << kSPRegInternalCode);
} return CPURegList(type, GetDefaultSizeFor(type, size), list);
}
// Combine another CPURegList into this one. Registers that already exist in // this list are left unchanged. The type and size of the registers in the // 'other' list must match those in this list. void Combine(const CPURegList& other) {
VIXL_ASSERT(IsValid());
VIXL_ASSERT(other.GetType() == type_);
VIXL_ASSERT(other.GetRegisterSizeInBits() == size_);
list_ |= other.GetList();
}
// Remove every register in the other CPURegList from this one. Registers that // do not exist in this list are ignored. The type and size of the registers // in the 'other' list must match those in this list. void Remove(const CPURegList& other) {
VIXL_ASSERT(IsValid());
VIXL_ASSERT(other.GetType() == type_);
VIXL_ASSERT(other.GetRegisterSizeInBits() == size_);
list_ &= ~other.GetList();
}
// Variants of Combine and Remove which take a single register. void Combine(const CPURegister& other) {
VIXL_ASSERT(other.GetType() == type_);
VIXL_ASSERT(other.GetSizeInBits() == size_);
Combine(other.GetCode());
}
// Variants of Combine and Remove which take a single register by its code; // the type and size of the register is inferred from this list. void Combine(int code) {
VIXL_ASSERT(IsValid());
VIXL_ASSERT(CPURegister(code, size_, type_).IsValid());
list_ |= (UINT64_C(1) << code);
}
// Remove all callee-saved registers from the list. This can be useful when // preparing registers for an AAPCS64 function call, for example. void RemoveCalleeSaved();
// Find the register in this list that appears in `mask` with the lowest or // highest code, remove it from the list and return it as a CPURegister. If // the list is empty, leave it unchanged and return NoCPUReg.
CPURegister PopLowestIndex(RegList mask = ~static_cast<RegList>(0));
CPURegister PopHighestIndex(RegList mask = ~static_cast<RegList>(0));
// AAPCS64 caller-saved registers. Note that this includes lr. // TODO(all): Determine how we handle d8-d15 being callee-saved, but the top // 64-bits being caller-saved. static CPURegList GetCallerSaved(unsigned size = kXRegSize); static CPURegList GetCallerSavedV(unsigned size = kDRegSize);
private: // If `size` is CPURegister::kUnknownSize and the type requires a known size, // then return an arbitrary-but-valid size. // // Otherwise, the size is checked for validity and returned unchanged. staticunsigned GetDefaultSizeFor(CPURegister::RegisterType type, unsigned size) { if (size == CPURegister::kUnknownSize) { if (type == CPURegister::kRegister) size = kXRegSize; if (type == CPURegister::kVRegister) size = kQRegSize; // All other types require kUnknownSize.
}
VIXL_ASSERT(CPURegister(0, size, type).IsValid()); return size;
}
RegList list_; int size_;
CPURegister::RegisterType type_;
// AAPCS64 caller-saved registers. Note that this includes lr. externconst CPURegList kCallerSaved; externconst CPURegList kCallerSavedV;
class IntegerOperand;
// Operand. class Operand {
public: // #<immediate> // where <immediate> is int64_t. // This is allowed to be an implicit constructor because Operand is // a wrapper class that doesn't normally perform any type conversion.
Operand(int64_t immediate); // NOLINT(runtime/explicit)
// rm, {<shift> #<shift_amount>} // where <shift> is one of {LSL, LSR, ASR, ROR}. // <shift_amount> is uint6_t. // This is allowed to be an implicit constructor because Operand is // a wrapper class that doesn't normally perform any type conversion.
Operand(Register reg,
Shift shift = LSL, unsigned shift_amount = 0); // NOLINT(runtime/explicit)
// rm, {<extend> {#<shift_amount>}} // where <extend> is one of {UXTB, UXTH, UXTW, UXTX, SXTB, SXTH, SXTW, SXTX}. // <shift_amount> is uint2_t. explicit Operand(Register reg, Extend extend, unsigned shift_amount = 0);
// This returns an LSL shift (<= 4) operand as an equivalent extend operand, // which helps in the encoding of instructions that use the stack pointer.
Operand ToExtendedRegister() const;
// If the MemOperand has a register offset, return it. (This also applies to // pre- and post-index modes.) Otherwise, return NoReg. constRegister& GetRegisterOffset() const { return regoffset_; } constRegister& regoffset() const { return regoffset_; }
// If the MemOperand has an immediate offset, return it. (This also applies to // pre- and post-index modes.) Otherwise, return 0.
int64_t GetOffset() const { return offset_; }
int64_t offset() const { return offset_; }
unsigned GetShiftAmount() const { // Extend modes can also encode a shift for some instructions.
VIXL_ASSERT((GetShift() != NO_SHIFT) || (GetExtend() != NO_EXTEND)); return shift_amount_;
} unsigned shift_amount() const { return shift_amount_; }
// True for MemOperands which represent something like [x0]. // Currently, this will also return true for [x0, #0], because MemOperand has // no way to distinguish the two. bool IsPlainRegister() const;
// True for MemOperands which represent something like [x0], or for compound // MemOperands which are functionally equivalent, such as [x0, #0], [x0, xzr] // or [x0, wzr, UXTW #3]. bool IsEquivalentToPlainRegister() const;
// True for immediate-offset (but not indexed) MemOperands. bool IsImmediateOffset() const; // True for register-offset (but not indexed) MemOperands. bool IsRegisterOffset() const; // True for immediate or register pre-indexed MemOperands. bool IsPreIndex() const; // True for immediate or register post-indexed MemOperands. bool IsPostIndex() const; // True for immediate pre-indexed MemOperands, [reg, #imm]! bool IsImmediatePreIndex() const; // True for immediate post-indexed MemOperands, [reg], #imm bool IsImmediatePostIndex() const;
// SVE supports memory operands which don't make sense to the core ISA, such as // scatter-gather forms, in which either the base or offset registers are // vectors. This class exists to avoid complicating core-ISA code with // SVE-specific behaviour. // // Note that SVE does not support any pre- or post-index modes. class SVEMemOperand {
public: // "vector-plus-immediate", like [z0.s, #21] explicit SVEMemOperand(ZRegister base, uint64_t offset = 0)
: base_(base),
regoffset_(NoReg),
offset_(RawbitsToInt64(offset)),
mod_(NO_SVE_OFFSET_MODIFIER),
shift_amount_(0) {
VIXL_ASSERT(IsVectorPlusImmediate());
VIXL_ASSERT(IsValid());
}
// "scalar-plus-immediate", like [x0], [x0, #42] or [x0, #42, MUL_VL] // The only supported modifiers are NO_SVE_OFFSET_MODIFIER or SVE_MUL_VL. // // Note that VIXL cannot currently distinguish between `SVEMemOperand(x0)` and // `SVEMemOperand(x0, 0)`. This is only significant in scalar-plus-scalar // instructions where xm defaults to xzr. However, users should not rely on // `SVEMemOperand(x0, 0)` being accepted in such cases. explicit SVEMemOperand(Register base,
uint64_t offset = 0,
SVEOffsetModifier mod = NO_SVE_OFFSET_MODIFIER)
: base_(base),
regoffset_(NoReg),
offset_(RawbitsToInt64(offset)),
mod_(mod),
shift_amount_(0) {
VIXL_ASSERT(IsScalarPlusImmediate());
VIXL_ASSERT(IsValid());
}
// "scalar-plus-scalar", like [x0, x1] // "scalar-plus-vector", like [x0, z1.d]
SVEMemOperand(Register base, CPURegister offset)
: base_(base),
regoffset_(offset),
offset_(0),
mod_(NO_SVE_OFFSET_MODIFIER),
shift_amount_(0) {
VIXL_ASSERT(IsScalarPlusScalar() || IsScalarPlusVector()); if (offset.IsZero()) VIXL_ASSERT(IsEquivalentToScalar());
VIXL_ASSERT(IsValid());
}
// "scalar-plus-vector", like [x0, z1.d, UXTW] // The type of `mod` can be any `SVEOffsetModifier` (other than LSL), or a // corresponding `Extend` value. template <typename M>
SVEMemOperand(Register base, ZRegister offset, M mod)
: base_(base),
regoffset_(offset),
offset_(0),
mod_(GetSVEOffsetModifierFor(mod)),
shift_amount_(0) {
VIXL_ASSERT(mod_ != SVE_LSL); // LSL requires an explicit shift amount.
VIXL_ASSERT(IsScalarPlusVector());
VIXL_ASSERT(IsValid());
}
// "scalar-plus-scalar", like [x0, x1, LSL #1] // "scalar-plus-vector", like [x0, z1.d, LSL #2] // The type of `mod` can be any `SVEOffsetModifier`, or a corresponding // `Shift` or `Extend` value. template <typename M>
SVEMemOperand(Register base, CPURegister offset, M mod, unsigned shift_amount)
: base_(base),
regoffset_(offset),
offset_(0),
mod_(GetSVEOffsetModifierFor(mod)),
shift_amount_(shift_amount) {
VIXL_ASSERT(IsValid());
}
// "vector-plus-vector", like [z0.d, z1.d, UXTW] template <typename M = SVEOffsetModifier>
SVEMemOperand(ZRegister base,
ZRegister offset,
M mod = NO_SVE_OFFSET_MODIFIER, unsigned shift_amount = 0)
: base_(base),
regoffset_(offset),
offset_(0),
mod_(GetSVEOffsetModifierFor(mod)),
shift_amount_(shift_amount) {
VIXL_ASSERT(IsValid());
VIXL_ASSERT(IsVectorPlusVector());
}
// True for SVEMemOperands which represent something like [x0]. // This will also return true for [x0, #0], because there is no way // to distinguish the two. bool IsPlainScalar() const { return IsScalarPlusImmediate() && (offset_ == 0);
}
// True for SVEMemOperands which represent something like [x0], or for // compound SVEMemOperands which are functionally equivalent, such as // [x0, #0], [x0, xzr] or [x0, wzr, UXTW #3]. bool IsEquivalentToScalar() const;
// True for SVEMemOperands like [x0], [x0, #0], false for [x0, xzr] and // similar. bool IsPlainRegister() const;
bool IsScalarPlusScalar() const { // SVE offers no extend modes for scalar-plus-scalar, so both registers must // be X registers. return base_.IsX() && regoffset_.IsX() &&
((mod_ == NO_SVE_OFFSET_MODIFIER) || (mod_ == SVE_LSL));
}
bool IsScalarPlusVector() const { // The modifier can be LSL or an an extend mode (UXTW or SXTW) here. Unlike // in the core ISA, these extend modes do not imply an S-sized lane, so the // modifier is independent from the lane size. The architecture describes // [US]XTW with a D-sized lane as an "unpacked" offset. return base_.IsX() && regoffset_.IsZRegister() &&
(regoffset_.IsLaneSizeS() || regoffset_.IsLaneSizeD()) && !IsMulVl();
}
private: // Allow standard `Shift` and `Extend` arguments to be used.
SVEOffsetModifier GetSVEOffsetModifierFor(Shift shift) { if (shift == LSL) return SVE_LSL; if (shift == NO_SHIFT) return NO_SVE_OFFSET_MODIFIER; // SVE does not accept any other shift.
VIXL_UNIMPLEMENTED(); return NO_SVE_OFFSET_MODIFIER;
}
SVEOffsetModifier GetSVEOffsetModifierFor(Extend extend = NO_EXTEND) { if (extend == UXTW) return SVE_UXTW; if (extend == SXTW) return SVE_SXTW; if (extend == NO_EXTEND) return NO_SVE_OFFSET_MODIFIER; // SVE does not accept any other extend mode.
VIXL_UNIMPLEMENTED(); return NO_SVE_OFFSET_MODIFIER;
}
// Represent a signed or unsigned integer operand. // // This is designed to make instructions which naturally accept a _signed_ // immediate easier to implement and use, when we also want users to be able to // specify raw-bits values (such as with hexadecimal constants). The advantage // of this class over a simple uint64_t (with implicit C++ sign-extension) is // that this class can strictly check the range of allowed values. With a simple // uint64_t, it is impossible to distinguish -1 from UINT64_MAX. // // For example, these instructions are equivalent: // // __ Insr(z0.VnB(), -1); // __ Insr(z0.VnB(), 0xff); // // ... as are these: // // __ Insr(z0.VnD(), -1); // __ Insr(z0.VnD(), 0xffffffffffffffff); // // ... but this is invalid: // // __ Insr(z0.VnB(), 0xffffffffffffffff); // Too big for B-sized lanes. class IntegerOperand {
public: #define VIXL_INT_TYPES(V) \
V(char) V(short) V(int) V(long) V(longlong) // NOLINT(google-runtime-int) #define VIXL_DECL_INT_OVERLOADS(T) \ /* These are allowed to be implicit constructors because this is a */ \ /* wrapper class that doesn't normally perform any type conversion. */ \
IntegerOperand(signed T immediate) /* NOLINT(runtime/explicit) */ \
: raw_bits_(immediate), /* Allow implicit sign-extension. */ \
is_negative_(immediate < 0) {} \
IntegerOperand(unsigned T immediate) /* NOLINT(runtime/explicit) */ \
: raw_bits_(immediate), is_negative_(false) {}
VIXL_INT_TYPES(VIXL_DECL_INT_OVERLOADS) #undef VIXL_DECL_INT_OVERLOADS #undef VIXL_INT_TYPES
// TODO: `Operand` can currently only hold an int64_t, so some large, unsigned // values will be misrepresented here. explicit IntegerOperand(const Operand& operand)
: raw_bits_(operand.GetEquivalentImmediate()),
is_negative_(operand.GetEquivalentImmediate() < 0) {}
// Cast a value in the range [INT<n>_MIN, UINT<n>_MAX] to an unsigned integer // in the range [0, UINT<n>_MAX] (using two's complement mapping).
uint64_t AsUintN(unsigned n) const {
VIXL_ASSERT(FitsInBits(n)); return raw_bits_ & GetUintMask(n);
}
// Cast a value in the range [INT<n>_MIN, UINT<n>_MAX] to a signed integer in // the range [INT<n>_MIN, INT<n>_MAX] (using two's complement mapping).
int64_t AsIntN(unsigned n) const {
VIXL_ASSERT(FitsInBits(n)); return ExtractSignedBitfield64(n - 1, 0, raw_bits_);
}
// Several instructions encode a signed int<N>_t, which is then (optionally) // left-shifted and sign-extended to a Z register lane with a size which may // be larger than N. This helper tries to find an int<N>_t such that the // IntegerOperand's arithmetic value is reproduced in each lane. // // This is the mechanism that allows `Insr(z0.VnB(), 0xff)` to be treated as // `Insr(z0.VnB(), -1)`. template <unsigned N, unsigned kShift, typename T> bool TryEncodeAsShiftedIntNForLane(const CPURegister& zd, T* imm) const {
VIXL_STATIC_ASSERT(std::numeric_limits<T>::digits > N);
VIXL_ASSERT(FitsInLane(zd)); if ((raw_bits_ & GetUintMask(kShift)) != 0) returnfalse;
// Reverse the specified left-shift.
IntegerOperand unshifted(*this);
unshifted.ArithmeticShiftRight(kShift);
if (unshifted.IsIntN(N)) { // This is trivial, since sign-extension produces the same arithmetic // value irrespective of the destination size.
*imm = static_cast<T>(unshifted.AsIntN(N)); return true;
}
// Otherwise, we might be able to use the sign-extension to produce the // desired bit pattern. We can only do this for values in the range // [INT<N>_MAX + 1, UINT<N>_MAX], where the highest set bit is the sign bit. // // The lane size has to be adjusted to compensate for `kShift`, since the // high bits will be dropped when the encoded value is left-shifted. if (unshifted.IsUintN(zd.GetLaneSizeInBits() - kShift)) {
int64_t encoded = unshifted.AsIntN(zd.GetLaneSizeInBits() - kShift); if (vixl::IsIntN(N, encoded)) {
*imm = static_cast<T>(encoded); return true;
}
} returnfalse;
}
// As above, but `kShift` is written to the `*shift` parameter on success, so // that it is easy to chain calls like this: // // if (imm.TryEncodeAsShiftedIntNForLane<8, 0>(zd, &imm8, &shift) || // imm.TryEncodeAsShiftedIntNForLane<8, 8>(zd, &imm8, &shift)) { // insn(zd, imm8, shift) // } template <unsigned N, unsigned kShift, typename T, typename S> bool TryEncodeAsShiftedIntNForLane(const CPURegister& zd,
T* imm,
S* shift) const { if (TryEncodeAsShiftedIntNForLane<N, kShift>(zd, imm)) {
*shift = kShift; return true;
} returnfalse;
}
// As above, but assume that `kShift` is 0. template <unsigned N, typename T> bool TryEncodeAsIntNForLane(const CPURegister& zd, T* imm) const { return TryEncodeAsShiftedIntNForLane<N, 0>(zd, imm);
}
// As above, but for unsigned fields. This is usually a simple operation, but // is provided for symmetry. template <unsigned N, unsigned kShift, typename T> bool TryEncodeAsShiftedUintNForLane(const CPURegister& zd, T* imm) const {
VIXL_STATIC_ASSERT(std::numeric_limits<T>::digits > N);
VIXL_ASSERT(FitsInLane(zd));
// TODO: Should we convert -1 to 0xff here? if (is_negative_) returnfalse;
USE(zd);
if ((raw_bits_ & GetUintMask(kShift)) != 0) returnfalse;
private: // Shift the arithmetic value right, with sign extension if is_negative_. void ArithmeticShiftRight(int shift) {
VIXL_ASSERT((shift >= 0) && (shift < 64)); if (shift == 0) return; if (is_negative_) {
raw_bits_ = ExtractSignedBitfield64(63, shift, raw_bits_);
} else {
raw_bits_ >>= shift;
}
}
uint64_t raw_bits_; bool is_negative_;
};
// This an abstraction that can represent a register or memory location. The // `MacroAssembler` provides helpers to move data between generic operands. class GenericOperand {
public:
GenericOperand() { VIXL_ASSERT(!IsValid()); }
GenericOperand(const CPURegister& reg); // NOLINT(runtime/explicit)
GenericOperand(const MemOperand& mem_op,
size_t mem_op_size = 0); // NOLINT(runtime/explicit)
private:
CPURegister cpu_register_;
MemOperand mem_op_; // The size of the memory region pointed to, in bytes. // We only support sizes up to X/D register sizes.
size_t mem_op_size_;
};
} // namespace vixl
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.