backend/arm64/reg_alloc: Implement ReadWrite mode

This commit is contained in:
Merry 2022-08-02 00:37:40 +01:00 committed by merry
parent 208b19b89a
commit 0288540155
2 changed files with 147 additions and 51 deletions

View file

@ -93,7 +93,6 @@ bool HostLocInfo::Contains(const IR::Inst* value) const {
void HostLocInfo::SetupScratchLocation() { void HostLocInfo::SetupScratchLocation() {
ASSERT(IsCompletelyEmpty()); ASSERT(IsCompletelyEmpty());
locked++;
realized = true; realized = true;
} }
@ -101,7 +100,6 @@ void HostLocInfo::SetupLocation(const IR::Inst* value) {
ASSERT(IsCompletelyEmpty()); ASSERT(IsCompletelyEmpty());
values.clear(); values.clear();
values.emplace_back(value); values.emplace_back(value);
locked++;
realized = true; realized = true;
uses_this_inst = 0; uses_this_inst = 0;
accumulated_uses = 0; accumulated_uses = 0;
@ -112,8 +110,8 @@ bool HostLocInfo::IsCompletelyEmpty() const {
return values.empty() && !locked && !realized && !accumulated_uses && !expected_uses && !uses_this_inst; return values.empty() && !locked && !realized && !accumulated_uses && !expected_uses && !uses_this_inst;
} }
bool HostLocInfo::IsImmediatelyAllocatable() const { bool HostLocInfo::MaybeAllocatable() const {
return values.empty() && !locked; return !locked && !realized;
} }
bool HostLocInfo::IsOneRemainingUse() const { bool HostLocInfo::IsOneRemainingUse() const {
@ -169,6 +167,7 @@ void RegAlloc::PrepareForCall(IR::Inst* result, std::optional<Argument::copyable
const std::array<std::optional<Argument::copyable_reference>, 4> args{arg0, arg1, arg2, arg3}; const std::array<std::optional<Argument::copyable_reference>, 4> args{arg0, arg1, arg2, arg3};
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (args[i]) { if (args[i]) {
ASSERT(gprs[i].IsCompletelyEmpty());
LoadCopyInto(args[i]->get().value, oaknut::XReg{i}); LoadCopyInto(args[i]->get().value, oaknut::XReg{i});
} }
} }
@ -339,21 +338,43 @@ int RegAlloc::RealizeWriteImpl(const IR::Inst* value) {
} }
} }
template<HostLoc::Kind kind>
int RegAlloc::RealizeReadWriteImpl(const IR::Value& read_value, const IR::Inst* write_value) {
// TODO: Move elimination
const int write_loc = RealizeWriteImpl<kind>(write_value);
if constexpr (kind == HostLoc::Kind::Gpr) {
LoadCopyInto(read_value, oaknut::XReg{write_loc});
return write_loc;
} else if constexpr (kind == HostLoc::Kind::Fpr) {
LoadCopyInto(read_value, oaknut::QReg{write_loc});
return write_loc;
} else if constexpr (kind == HostLoc::Kind::Flags) {
ASSERT_FALSE("Incorrect function for ReadWrite of flags");
} else {
static_assert(kind == HostLoc::Kind::Fpr || kind == HostLoc::Kind::Gpr || kind == HostLoc::Kind::Flags);
}
}
template int RegAlloc::RealizeReadImpl<HostLoc::Kind::Gpr>(const IR::Value& value); template int RegAlloc::RealizeReadImpl<HostLoc::Kind::Gpr>(const IR::Value& value);
template int RegAlloc::RealizeReadImpl<HostLoc::Kind::Fpr>(const IR::Value& value); template int RegAlloc::RealizeReadImpl<HostLoc::Kind::Fpr>(const IR::Value& value);
template int RegAlloc::RealizeReadImpl<HostLoc::Kind::Flags>(const IR::Value& value); template int RegAlloc::RealizeReadImpl<HostLoc::Kind::Flags>(const IR::Value& value);
template int RegAlloc::RealizeWriteImpl<HostLoc::Kind::Gpr>(const IR::Inst* value); template int RegAlloc::RealizeWriteImpl<HostLoc::Kind::Gpr>(const IR::Inst* value);
template int RegAlloc::RealizeWriteImpl<HostLoc::Kind::Fpr>(const IR::Inst* value); template int RegAlloc::RealizeWriteImpl<HostLoc::Kind::Fpr>(const IR::Inst* value);
template int RegAlloc::RealizeWriteImpl<HostLoc::Kind::Flags>(const IR::Inst* value); template int RegAlloc::RealizeWriteImpl<HostLoc::Kind::Flags>(const IR::Inst* value);
template int RegAlloc::RealizeReadWriteImpl<HostLoc::Kind::Gpr>(const IR::Value&, const IR::Inst*);
template int RegAlloc::RealizeReadWriteImpl<HostLoc::Kind::Fpr>(const IR::Value&, const IR::Inst*);
template int RegAlloc::RealizeReadWriteImpl<HostLoc::Kind::Flags>(const IR::Value&, const IR::Inst*);
int RegAlloc::AllocateRegister(const std::array<HostLocInfo, 32>& regs, const std::vector<int>& order) const { int RegAlloc::AllocateRegister(const std::array<HostLocInfo, 32>& regs, const std::vector<int>& order) const {
const auto empty = std::find_if(order.begin(), order.end(), [&](int i) { return regs[i].IsImmediatelyAllocatable(); }); const auto empty = std::find_if(order.begin(), order.end(), [&](int i) { return regs[i].IsCompletelyEmpty(); });
if (empty != order.end()) { if (empty != order.end()) {
return *empty; return *empty;
} }
std::vector<int> candidates; std::vector<int> candidates;
std::copy_if(order.begin(), order.end(), std::back_inserter(candidates), [&](int i) { return !regs[i].locked; }); std::copy_if(order.begin(), order.end(), std::back_inserter(candidates), [&](int i) { return regs[i].MaybeAllocatable(); });
// TODO: LRU // TODO: LRU
std::uniform_int_distribution<size_t> dis{0, candidates.size() - 1}; std::uniform_int_distribution<size_t> dis{0, candidates.size() - 1};
@ -405,7 +426,6 @@ void RegAlloc::ReadWriteFlags(Argument& read, IR::Inst* write) {
if (write) { if (write) {
flags.SetupLocation(write); flags.SetupLocation(write);
flags.locked--;
flags.realized = false; flags.realized = false;
} }
} }
@ -435,7 +455,6 @@ void RegAlloc::LoadCopyInto(const IR::Value& value, oaknut::XReg reg) {
const auto current_location = ValueLocation(value.GetInst()); const auto current_location = ValueLocation(value.GetInst());
ASSERT(current_location); ASSERT(current_location);
ASSERT(gprs[reg.index()].IsCompletelyEmpty());
switch (current_location->kind) { switch (current_location->kind) {
case HostLoc::Kind::Gpr: case HostLoc::Kind::Gpr:
code.MOV(reg, oaknut::XReg{current_location->index}); code.MOV(reg, oaknut::XReg{current_location->index});
@ -453,6 +472,32 @@ void RegAlloc::LoadCopyInto(const IR::Value& value, oaknut::XReg reg) {
} }
} }
void RegAlloc::LoadCopyInto(const IR::Value& value, oaknut::QReg reg) {
if (value.IsImmediate()) {
code.MOV(Xscratch0, value.GetImmediateAsU64());
code.FMOV(reg.toD(), Xscratch0);
return;
}
const auto current_location = ValueLocation(value.GetInst());
ASSERT(current_location);
switch (current_location->kind) {
case HostLoc::Kind::Gpr:
code.FMOV(reg.toD(), oaknut::XReg{current_location->index});
break;
case HostLoc::Kind::Fpr:
code.MOV(reg.B16(), oaknut::QReg{current_location->index}.B16());
break;
case HostLoc::Kind::Spill:
// TODO: Minimize move size to max value width
code.LDR(reg, SP, spill_offset + current_location->index * spill_slot_size);
break;
case HostLoc::Kind::Flags:
ASSERT_FALSE("Moving from flags into fprs is not currently supported");
break;
}
}
std::optional<HostLoc> RegAlloc::ValueLocation(const IR::Inst* value) const { std::optional<HostLoc> RegAlloc::ValueLocation(const IR::Inst* value) const {
const auto contains_value = [value](const HostLocInfo& info) { return info.Contains(value); }; const auto contains_value = [value](const HostLocInfo& info) { return info.Contains(value); };

View file

@ -26,7 +26,7 @@ namespace Dynarmic::Backend::Arm64 {
class FpsrManager; class FpsrManager;
class RegAlloc; class RegAlloc;
struct HostLoc { struct HostLoc final {
enum class Kind { enum class Kind {
Gpr, Gpr,
Fpr, Fpr,
@ -36,7 +36,13 @@ struct HostLoc {
int index; int index;
}; };
struct Argument { enum RWType {
Read,
Write,
ReadWrite,
};
struct Argument final {
public: public:
using copyable_reference = std::reference_wrapper<Argument>; using copyable_reference = std::reference_wrapper<Argument>;
@ -68,7 +74,7 @@ private:
IR::Value value; IR::Value value;
}; };
struct FlagsTag { struct FlagsTag final {
private: private:
template<typename> template<typename>
friend struct RAReg; friend struct RAReg;
@ -78,7 +84,7 @@ private:
}; };
template<typename T> template<typename T>
struct RAReg { struct RAReg final {
public: public:
static constexpr HostLoc::Kind kind = !std::is_same_v<FlagsTag, T> static constexpr HostLoc::Kind kind = !std::is_same_v<FlagsTag, T>
? std::is_base_of_v<oaknut::VReg, T> ? std::is_base_of_v<oaknut::VReg, T>
@ -103,7 +109,7 @@ public:
private: private:
friend class RegAlloc; friend class RegAlloc;
explicit RAReg(RegAlloc& reg_alloc, bool write, const IR::Value& value); explicit RAReg(RegAlloc& reg_alloc, RWType rw, const IR::Value& read_value, const IR::Inst* write_value);
RAReg(const RAReg&) = delete; RAReg(const RAReg&) = delete;
RAReg& operator=(const RAReg&) = delete; RAReg& operator=(const RAReg&) = delete;
@ -113,12 +119,13 @@ private:
void Realize(); void Realize();
RegAlloc& reg_alloc; RegAlloc& reg_alloc;
bool write; RWType rw;
const IR::Value value; const IR::Value read_value;
const IR::Inst* write_value;
std::optional<T> reg; std::optional<T> reg;
}; };
struct HostLocInfo { struct HostLocInfo final {
std::vector<const IR::Inst*> values; std::vector<const IR::Inst*> values;
size_t locked = 0; size_t locked = 0;
bool realized = false; bool realized = false;
@ -130,12 +137,12 @@ struct HostLocInfo {
void SetupScratchLocation(); void SetupScratchLocation();
void SetupLocation(const IR::Inst*); void SetupLocation(const IR::Inst*);
bool IsCompletelyEmpty() const; bool IsCompletelyEmpty() const;
bool IsImmediatelyAllocatable() const; bool MaybeAllocatable() const;
bool IsOneRemainingUse() const; bool IsOneRemainingUse() const;
void UpdateUses(); void UpdateUses();
}; };
class RegAlloc { class RegAlloc final {
public: public:
using ArgumentInfo = std::array<Argument, IR::max_arg_count>; using ArgumentInfo = std::array<Argument, IR::max_arg_count>;
@ -145,14 +152,14 @@ public:
ArgumentInfo GetArgumentInfo(IR::Inst* inst); ArgumentInfo GetArgumentInfo(IR::Inst* inst);
bool IsValueLive(IR::Inst* inst) const; bool IsValueLive(IR::Inst* inst) const;
auto ReadX(Argument& arg) { return RAReg<oaknut::XReg>{*this, false, arg.value}; } auto ReadX(Argument& arg) { return RAReg<oaknut::XReg>{*this, RWType::Read, arg.value, nullptr}; }
auto ReadW(Argument& arg) { return RAReg<oaknut::WReg>{*this, false, arg.value}; } auto ReadW(Argument& arg) { return RAReg<oaknut::WReg>{*this, RWType::Read, arg.value, nullptr}; }
auto ReadQ(Argument& arg) { return RAReg<oaknut::QReg>{*this, false, arg.value}; } auto ReadQ(Argument& arg) { return RAReg<oaknut::QReg>{*this, RWType::Read, arg.value, nullptr}; }
auto ReadD(Argument& arg) { return RAReg<oaknut::DReg>{*this, false, arg.value}; } auto ReadD(Argument& arg) { return RAReg<oaknut::DReg>{*this, RWType::Read, arg.value, nullptr}; }
auto ReadS(Argument& arg) { return RAReg<oaknut::SReg>{*this, false, arg.value}; } auto ReadS(Argument& arg) { return RAReg<oaknut::SReg>{*this, RWType::Read, arg.value, nullptr}; }
auto ReadH(Argument& arg) { return RAReg<oaknut::HReg>{*this, false, arg.value}; } auto ReadH(Argument& arg) { return RAReg<oaknut::HReg>{*this, RWType::Read, arg.value, nullptr}; }
auto ReadB(Argument& arg) { return RAReg<oaknut::BReg>{*this, false, arg.value}; } auto ReadB(Argument& arg) { return RAReg<oaknut::BReg>{*this, RWType::Read, arg.value, nullptr}; }
template<size_t size> template<size_t size>
auto ReadReg(Argument& arg) { auto ReadReg(Argument& arg) {
@ -182,16 +189,16 @@ public:
} }
} }
auto WriteX(IR::Inst* inst) { return RAReg<oaknut::XReg>{*this, true, IR::Value{inst}}; } auto WriteX(IR::Inst* inst) { return RAReg<oaknut::XReg>{*this, RWType::Write, {}, inst}; }
auto WriteW(IR::Inst* inst) { return RAReg<oaknut::WReg>{*this, true, IR::Value{inst}}; } auto WriteW(IR::Inst* inst) { return RAReg<oaknut::WReg>{*this, RWType::Write, {}, inst}; }
auto WriteQ(IR::Inst* inst) { return RAReg<oaknut::QReg>{*this, true, IR::Value{inst}}; } auto WriteQ(IR::Inst* inst) { return RAReg<oaknut::QReg>{*this, RWType::Write, {}, inst}; }
auto WriteD(IR::Inst* inst) { return RAReg<oaknut::DReg>{*this, true, IR::Value{inst}}; } auto WriteD(IR::Inst* inst) { return RAReg<oaknut::DReg>{*this, RWType::Write, {}, inst}; }
auto WriteS(IR::Inst* inst) { return RAReg<oaknut::SReg>{*this, true, IR::Value{inst}}; } auto WriteS(IR::Inst* inst) { return RAReg<oaknut::SReg>{*this, RWType::Write, {}, inst}; }
auto WriteH(IR::Inst* inst) { return RAReg<oaknut::HReg>{*this, true, IR::Value{inst}}; } auto WriteH(IR::Inst* inst) { return RAReg<oaknut::HReg>{*this, RWType::Write, {}, inst}; }
auto WriteB(IR::Inst* inst) { return RAReg<oaknut::BReg>{*this, true, IR::Value{inst}}; } auto WriteB(IR::Inst* inst) { return RAReg<oaknut::BReg>{*this, RWType::Write, {}, inst}; }
auto WriteFlags(IR::Inst* inst) { return RAReg<FlagsTag>{*this, true, IR::Value{inst}}; } auto WriteFlags(IR::Inst* inst) { return RAReg<FlagsTag>{*this, RWType::Write, {}, inst}; }
template<size_t size> template<size_t size>
auto WriteReg(IR::Inst* inst) { auto WriteReg(IR::Inst* inst) {
@ -221,6 +228,43 @@ public:
} }
} }
auto ReadWriteX(Argument& arg, const IR::Inst* inst) { return RAReg<oaknut::XReg>{*this, RWType::ReadWrite, arg.value, inst}; }
auto ReadWriteW(Argument& arg, const IR::Inst* inst) { return RAReg<oaknut::WReg>{*this, RWType::ReadWrite, arg.value, inst}; }
auto ReadWriteQ(Argument& arg, const IR::Inst* inst) { return RAReg<oaknut::QReg>{*this, RWType::ReadWrite, arg.value, inst}; }
auto ReadWriteD(Argument& arg, const IR::Inst* inst) { return RAReg<oaknut::DReg>{*this, RWType::ReadWrite, arg.value, inst}; }
auto ReadWriteS(Argument& arg, const IR::Inst* inst) { return RAReg<oaknut::SReg>{*this, RWType::ReadWrite, arg.value, inst}; }
auto ReadWriteH(Argument& arg, const IR::Inst* inst) { return RAReg<oaknut::HReg>{*this, RWType::ReadWrite, arg.value, inst}; }
auto ReadWriteB(Argument& arg, const IR::Inst* inst) { return RAReg<oaknut::BReg>{*this, RWType::ReadWrite, arg.value, inst}; }
template<size_t size>
auto ReadWriteReg(Argument& arg, const IR::Inst* inst) {
if constexpr (size == 64) {
return ReadWriteX(arg, inst);
} else if constexpr (size == 32) {
return ReadWriteW(arg, inst);
} else {
ASSERT_FALSE("Invalid size to ReadWriteReg {}", size);
}
}
template<size_t size>
auto ReadWriteVec(Argument& arg, const IR::Inst* inst) {
if constexpr (size == 128) {
return ReadWriteQ(arg, inst);
} else if constexpr (size == 64) {
return ReadWriteD(arg, inst);
} else if constexpr (size == 32) {
return ReadWriteS(arg, inst);
} else if constexpr (size == 16) {
return ReadWriteH(arg, inst);
} else if constexpr (size == 8) {
return ReadWriteB(arg, inst);
} else {
ASSERT_FALSE("Invalid size to ReadWriteVec {}", size);
}
}
void PrepareForCall(IR::Inst* result = nullptr, void PrepareForCall(IR::Inst* result = nullptr,
std::optional<Argument::copyable_reference> arg0 = {}, std::optional<Argument::copyable_reference> arg0 = {},
std::optional<Argument::copyable_reference> arg1 = {}, std::optional<Argument::copyable_reference> arg1 = {},
@ -254,6 +298,8 @@ private:
int RealizeReadImpl(const IR::Value& value); int RealizeReadImpl(const IR::Value& value);
template<HostLoc::Kind kind> template<HostLoc::Kind kind>
int RealizeWriteImpl(const IR::Inst* value); int RealizeWriteImpl(const IR::Inst* value);
template<HostLoc::Kind kind>
int RealizeReadWriteImpl(const IR::Value& read_value, const IR::Inst* write_value);
int AllocateRegister(const std::array<HostLocInfo, 32>& regs, const std::vector<int>& order) const; int AllocateRegister(const std::array<HostLocInfo, 32>& regs, const std::vector<int>& order) const;
void SpillGpr(int index); void SpillGpr(int index);
@ -261,6 +307,7 @@ private:
int FindFreeSpill() const; int FindFreeSpill() const;
void LoadCopyInto(const IR::Value& value, oaknut::XReg reg); void LoadCopyInto(const IR::Value& value, oaknut::XReg reg);
void LoadCopyInto(const IR::Value& value, oaknut::QReg reg);
std::optional<HostLoc> ValueLocation(const IR::Inst* value) const; std::optional<HostLoc> ValueLocation(const IR::Inst* value) const;
HostLocInfo& ValueInfo(HostLoc host_loc); HostLocInfo& ValueInfo(HostLoc host_loc);
@ -280,34 +327,38 @@ private:
}; };
template<typename T> template<typename T>
RAReg<T>::RAReg(RegAlloc& reg_alloc, bool write, const IR::Value& value) RAReg<T>::RAReg(RegAlloc& reg_alloc, RWType rw, const IR::Value& read_value, const IR::Inst* write_value)
: reg_alloc{reg_alloc}, write{write}, value{value} { : reg_alloc{reg_alloc}, rw{rw}, read_value{read_value}, write_value{write_value} {
if (!write && !value.IsImmediate()) { if (rw != RWType::Write && !read_value.IsImmediate()) {
reg_alloc.ValueInfo(value.GetInst()).locked++; reg_alloc.ValueInfo(read_value.GetInst()).locked++;
} }
} }
template<typename T> template<typename T>
RAReg<T>::~RAReg() { RAReg<T>::~RAReg() {
if (value.IsImmediate()) { if (rw != RWType::Write && !read_value.IsImmediate()) {
if (reg) { reg_alloc.ValueInfo(read_value.GetInst()).locked--;
// Immediate in scratch register
HostLocInfo& info = reg_alloc.ValueInfo(HostLoc{kind, reg->index()});
info.locked--;
info.realized = false;
} }
} else {
HostLocInfo& info = reg_alloc.ValueInfo(value.GetInst());
info.locked--;
if (reg) { if (reg) {
reg_alloc.ValueInfo(HostLoc{kind, reg->index()}).realized = false; reg_alloc.ValueInfo(HostLoc{kind, reg->index()}).realized = false;
} }
} }
}
template<typename T> template<typename T>
void RAReg<T>::Realize() { void RAReg<T>::Realize() {
reg = T{write ? reg_alloc.RealizeWriteImpl<kind>(value.GetInst()) : reg_alloc.RealizeReadImpl<kind>(value)}; switch (rw) {
case RWType::Read:
reg = T{reg_alloc.RealizeReadImpl<kind>(read_value)};
break;
case RWType::Write:
reg = T{reg_alloc.RealizeWriteImpl<kind>(write_value)};
break;
case RWType::ReadWrite:
reg = T{reg_alloc.RealizeReadWriteImpl<kind>(read_value, write_value)};
break;
default:
ASSERT_FALSE("Invalid RWType");
}
} }
} // namespace Dynarmic::Backend::Arm64 } // namespace Dynarmic::Backend::Arm64