mirror of
https://github.com/libretro/parallel-dc.git
synced 2026-11-07 12:37:53 -10:00
Dynarec and texture locks: what a block compile and a texture lock use kept, not allocated
The SSA optimizer's sets and maps of (register, version) are sorted arrays in memory kept from one block to the next, in the order std::map had them; the register allocator keeps its guest registers in a table by register with a bit each, its free host registers in a ring and the spilled ones in an array. A block compiles to the same code, with no allocation in the optimizer or the allocator. A texture locked in video memory takes its block from those unlocked before, so a texture rendered to each frame allocates nothing, and each page's locks are a list that counts the live ones, so whether a page is to be protected is known without going through the list.
This commit is contained in:
1 parent
d41ad6c9e5
commit
8d8146a6d9
4 files changed
+352
-144
No files matched your search
@@ -197,7 +197,7 @@ bool SSAOptimizer::ExecuteConstOp(shil_opcode* op)
|
||||
{
|
||||
u64 res = op->op == shop_div32u ? shil_opcl_div32u::f1::impl(rs1, rs2, rs3) : shil_opcl_div32s::f1::impl(rs1, rs2, rs3);
|
||||
rd = (u32)res;
|
||||
constprop_values[RegValue(op->rd, 1)] = res >> 32;
|
||||
constprop_values.Set(RegValue(op->rd, 1).key()) = res >> 32;
|
||||
|
||||
shil_param op2_rd = shil_param((Sh4RegType)(op->rd._reg + 1));
|
||||
op2_rd.version[0] = op->rd.version[1];
|
||||
@@ -324,7 +324,7 @@ bool SSAOptimizer::ExecuteConstOp(shil_opcode* op)
|
||||
shil_opcl_fsca::fsca_table::impl(tmp, rs1);
|
||||
rd = reinterpret_cast<u32&>(tmp[0]);
|
||||
u32 rd_1 = reinterpret_cast<u32&>(tmp[1]);
|
||||
constprop_values[RegValue(op->rd, 1)] = rd_1;
|
||||
constprop_values.Set(RegValue(op->rd, 1).key()) = rd_1;
|
||||
|
||||
shil_param op2_rd = shil_param((Sh4RegType)(op->rd._reg + 1));
|
||||
op2_rd.version[0] = op->rd.version[1];
|
||||
@@ -360,9 +360,9 @@ bool SSAOptimizer::ExecuteConstOp(shil_opcode* op)
|
||||
break;
|
||||
}
|
||||
|
||||
constprop_values[RegValue(op->rd)] = rd;
|
||||
constprop_values.Set(RegValue(op->rd).key()) = rd;
|
||||
if (op->rd2.is_r32())
|
||||
constprop_values[RegValue(op->rd2)] = rd2;
|
||||
constprop_values.Set(RegValue(op->rd2).key()) = rd2;
|
||||
ReplaceByMov32(*op, rd);
|
||||
|
||||
return true;
|
||||
|
||||
+166
-59
@@ -19,14 +19,128 @@
|
||||
along with reicast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <deque>
|
||||
#include <math.h>
|
||||
#include "types.h"
|
||||
#include "decoder.h"
|
||||
#include "hw/sh4/modules/mmu.h"
|
||||
#include "hw/sh4/sh4_mem.h"
|
||||
#include "rend/grow_buf.h"
|
||||
|
||||
/* A map of (register, version) to a value, the keys in order, in memory
|
||||
* kept from one block compiled to the next (a few of these a thread, taken
|
||||
* and given back): a block has a few hundred operations at most, so a
|
||||
* sorted array, searched by halves. Iterated in the order std::map has. */
|
||||
class RegValueMap
|
||||
{
|
||||
public:
|
||||
RegValueMap()
|
||||
{
|
||||
Pool& pool = TakePool();
|
||||
if (pool.count != 0)
|
||||
buf = pool.bufs[--pool.count];
|
||||
}
|
||||
~RegValueMap()
|
||||
{
|
||||
Pool& pool = TakePool();
|
||||
if (pool.count < MaxPooled)
|
||||
pool.bufs[pool.count++] = buf;
|
||||
else
|
||||
grow_buf_free(&buf);
|
||||
}
|
||||
RegValueMap(const RegValueMap&) = delete;
|
||||
RegValueMap& operator=(const RegValueMap&) = delete;
|
||||
|
||||
static u64 Key(Sh4RegType reg, u32 version)
|
||||
{
|
||||
/* (the register signed, as the pair compares it) */
|
||||
return ((u64)((u32)reg ^ 0x80000000u) << 32) | version;
|
||||
}
|
||||
static Sh4RegType KeyReg(u64 key)
|
||||
{
|
||||
return (Sh4RegType)(s32)((u32)(key >> 32) ^ 0x80000000u);
|
||||
}
|
||||
static u32 KeyVersion(u64 key)
|
||||
{
|
||||
return (u32)key;
|
||||
}
|
||||
|
||||
u32 Count() const { return count; }
|
||||
u64 KeyAt(u32 i) const { return Entries()[i].key; }
|
||||
u64 ValueAt(u32 i) const { return Entries()[i].value; }
|
||||
void Clear() { count = 0; }
|
||||
|
||||
/* where @key is, or -1 */
|
||||
int Find(u64 key) const
|
||||
{
|
||||
const u32 i = LowerBound(key);
|
||||
return i < count && Entries()[i].key == key ? (int)i : -1;
|
||||
}
|
||||
bool Contains(u64 key) const { return Find(key) >= 0; }
|
||||
|
||||
/* @key's value, made (0) if it had none */
|
||||
u64& Set(u64 key)
|
||||
{
|
||||
const u32 i = LowerBound(key);
|
||||
if (i < count && Entries()[i].key == key)
|
||||
return Entries()[i].value;
|
||||
Entry *e = (Entry *)grow_buf_reserve_keep(&buf, (count + 1) * sizeof(Entry));
|
||||
if (e == nullptr)
|
||||
die("Out of memory");
|
||||
memmove(&e[i + 1], &e[i], (count - i) * sizeof(Entry));
|
||||
e[i].key = key;
|
||||
e[i].value = 0;
|
||||
count++;
|
||||
return e[i].value;
|
||||
}
|
||||
|
||||
/* those whose key @drop says to, gone; the others in their order */
|
||||
template<typename F>
|
||||
void EraseIf(F drop)
|
||||
{
|
||||
Entry *e = Entries();
|
||||
u32 kept = 0;
|
||||
for (u32 i = 0; i < count; i++)
|
||||
if (!drop(e[i].key))
|
||||
e[kept++] = e[i];
|
||||
count = kept;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Entry
|
||||
{
|
||||
u64 key;
|
||||
u64 value;
|
||||
};
|
||||
enum { MaxPooled = 8 };
|
||||
struct Pool
|
||||
{
|
||||
grow_buf_t bufs[MaxPooled];
|
||||
u32 count;
|
||||
};
|
||||
static Pool& TakePool()
|
||||
{
|
||||
static thread_local Pool pool;
|
||||
return pool;
|
||||
}
|
||||
Entry *Entries() const { return (Entry *)buf.data; }
|
||||
u32 LowerBound(u64 key) const
|
||||
{
|
||||
const Entry *e = Entries();
|
||||
u32 lo = 0, hi = count;
|
||||
while (lo < hi)
|
||||
{
|
||||
const u32 mid = (lo + hi) / 2;
|
||||
if (e[mid].key < key)
|
||||
lo = mid + 1;
|
||||
else
|
||||
hi = mid;
|
||||
}
|
||||
return lo;
|
||||
}
|
||||
|
||||
grow_buf_t buf = GROW_BUF_INIT;
|
||||
u32 count = 0;
|
||||
};
|
||||
|
||||
class SSAOptimizer
|
||||
{
|
||||
@@ -95,6 +209,8 @@ private:
|
||||
|
||||
Sh4RegType get_reg() const { return first; }
|
||||
u32 get_version() const { return second; }
|
||||
u64 key() const { return RegValueMap::Key(first, second); }
|
||||
static RegValue from_key(u64 k) { return RegValue(RegValueMap::KeyReg(k), RegValueMap::KeyVersion(k)); }
|
||||
};
|
||||
|
||||
void PrintBlock()
|
||||
@@ -145,11 +261,11 @@ private:
|
||||
{
|
||||
if (param.is_r32())
|
||||
{
|
||||
auto it = constprop_values.find(RegValue(param));
|
||||
if (it != constprop_values.end())
|
||||
const int it = constprop_values.Find(RegValue(param).key());
|
||||
if (it >= 0)
|
||||
{
|
||||
param.type = FMT_IMM;
|
||||
param._imm = it->second;
|
||||
param._imm = (u32)constprop_values.ValueAt((u32)it);
|
||||
stats.prop_constants++;
|
||||
}
|
||||
}
|
||||
@@ -174,41 +290,27 @@ private:
|
||||
|
||||
if (op.op == shop_ifb)
|
||||
{
|
||||
constprop_values.clear();
|
||||
constprop_values.Clear();
|
||||
}
|
||||
else if (op.op == shop_sync_sr)
|
||||
{
|
||||
for (auto it = constprop_values.begin(); it != constprop_values.end(); )
|
||||
{
|
||||
Sh4RegType reg = it->first.get_reg();
|
||||
if (reg == reg_sr_status || reg == reg_old_sr_status || (reg >= reg_r0 && reg <= reg_r7)
|
||||
|| (reg >= reg_r0_Bank && reg <= reg_r7_Bank))
|
||||
it = constprop_values.erase(it);
|
||||
else
|
||||
it++;
|
||||
}
|
||||
constprop_values.EraseIf([](u64 key) {
|
||||
const Sh4RegType reg = RegValueMap::KeyReg(key);
|
||||
return reg == reg_sr_status || reg == reg_old_sr_status || (reg >= reg_r0 && reg <= reg_r7)
|
||||
|| (reg >= reg_r0_Bank && reg <= reg_r7_Bank);
|
||||
});
|
||||
}
|
||||
else if (op.op == shop_div1)
|
||||
{
|
||||
// it changes the status register: what was known of it is not any more
|
||||
for (auto it = constprop_values.begin(); it != constprop_values.end(); )
|
||||
{
|
||||
if (it->first.get_reg() == reg_sr_status)
|
||||
it = constprop_values.erase(it);
|
||||
else
|
||||
it++;
|
||||
}
|
||||
constprop_values.EraseIf([](u64 key) { return RegValueMap::KeyReg(key) == reg_sr_status; });
|
||||
}
|
||||
else if (op.op == shop_sync_fpscr)
|
||||
{
|
||||
for (auto it = constprop_values.begin(); it != constprop_values.end(); )
|
||||
{
|
||||
Sh4RegType reg = it->first.get_reg();
|
||||
if (reg == reg_fpscr || reg == reg_old_fpscr || (reg >= reg_fr_0 && reg <= reg_xf_15))
|
||||
it = constprop_values.erase(it);
|
||||
else
|
||||
it++;
|
||||
}
|
||||
constprop_values.EraseIf([](u64 key) {
|
||||
const Sh4RegType reg = RegValueMap::KeyReg(key);
|
||||
return reg == reg_fpscr || reg == reg_old_fpscr || (reg >= reg_fr_0 && reg <= reg_xf_15);
|
||||
});
|
||||
}
|
||||
else if (op.op == shop_readm || op.op == shop_writem)
|
||||
{
|
||||
@@ -278,7 +380,7 @@ private:
|
||||
break;
|
||||
}
|
||||
ReplaceByMov32(op, v);
|
||||
constprop_values[RegValue(op.rd)] = v;
|
||||
constprop_values.Set(RegValue(op.rd).key()) = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,7 +442,7 @@ private:
|
||||
void DeadCodeRemovalPass()
|
||||
{
|
||||
u32 last_versions[sh4_reg_count];
|
||||
std::set<RegValue> uses;
|
||||
RegValueMap uses; /* (a set: the values unused) */
|
||||
|
||||
memset(last_versions, -1, sizeof(last_versions));
|
||||
for (int opnum = block->oplist.size() - 1; opnum >= 0; opnum--)
|
||||
@@ -397,12 +499,12 @@ private:
|
||||
{
|
||||
last_versions[op.rd._reg + i] = op.rd.version[i];
|
||||
unused_rd = false;
|
||||
writeback_values.insert(RegValue(op.rd, i));
|
||||
writeback_values.Set(RegValue(op.rd, i).key());
|
||||
}
|
||||
else
|
||||
{
|
||||
verify(op.rd.version[i] < last_versions[op.rd._reg + i]);
|
||||
if (uses.find(RegValue(op.rd, i)) != uses.end())
|
||||
if (uses.Contains(RegValue(op.rd, i).key()))
|
||||
{
|
||||
unused_rd = false;
|
||||
}
|
||||
@@ -419,12 +521,12 @@ private:
|
||||
{
|
||||
last_versions[op.rd2._reg + i] = op.rd2.version[i];
|
||||
unused_rd = false;
|
||||
writeback_values.insert(RegValue(op.rd2, i));
|
||||
writeback_values.Set(RegValue(op.rd2, i).key());
|
||||
}
|
||||
else
|
||||
{
|
||||
verify(op.rd2.version[i] < last_versions[op.rd2._reg + i]);
|
||||
if (uses.find(RegValue(op.rd2, i)) != uses.end())
|
||||
if (uses.Contains(RegValue(op.rd2, i).key()))
|
||||
{
|
||||
unused_rd = false;
|
||||
}
|
||||
@@ -443,17 +545,17 @@ private:
|
||||
if (op.rs1.is_reg())
|
||||
{
|
||||
for (int i = 0; i < op.rs1.count(); i++)
|
||||
uses.insert(RegValue(op.rs1, i));
|
||||
uses.Set(RegValue(op.rs1, i).key());
|
||||
}
|
||||
if (op.rs2.is_reg())
|
||||
{
|
||||
for (int i = 0; i < op.rs2.count(); i++)
|
||||
uses.insert(RegValue(op.rs2, i));
|
||||
uses.Set(RegValue(op.rs2, i).key());
|
||||
}
|
||||
if (op.rs3.is_reg())
|
||||
{
|
||||
for (int i = 0; i < op.rs3.count(); i++)
|
||||
uses.insert(RegValue(op.rs3, i));
|
||||
uses.Set(RegValue(op.rs3, i).key());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -575,7 +677,7 @@ private:
|
||||
|
||||
void DeadRegisterPass()
|
||||
{
|
||||
std::map<RegValue, RegValue> aliases; // (dest reg, version) -> (source reg, version)
|
||||
RegValueMap aliases; // (dest reg, version) -> (source reg, version), as keys
|
||||
|
||||
// Find aliases
|
||||
for (shil_opcode& op : block->oplist)
|
||||
@@ -585,19 +687,24 @@ private:
|
||||
{
|
||||
RegValue dest_reg(op.rd);
|
||||
RegValue src_reg(op.rs1);
|
||||
auto it = aliases.find(src_reg);
|
||||
if (it != aliases.end())
|
||||
const int it = aliases.Find(src_reg.key());
|
||||
if (it >= 0)
|
||||
{
|
||||
// use the final value if the src is itself aliased
|
||||
aliases[dest_reg] = it->second;
|
||||
const u64 final_src = aliases.ValueAt((u32)it);
|
||||
aliases.Set(dest_reg.key()) = final_src;
|
||||
}
|
||||
else
|
||||
aliases[dest_reg] = src_reg;
|
||||
aliases.Set(dest_reg.key()) = src_reg.key();
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to eliminate them
|
||||
for (auto& alias : aliases)
|
||||
for (u32 a = 0; a < aliases.Count(); a++)
|
||||
{
|
||||
if (writeback_values.count(alias.first) > 0)
|
||||
const RegValue alias_from = RegValue::from_key(aliases.KeyAt(a));
|
||||
const RegValue alias_to = RegValue::from_key(aliases.ValueAt(a));
|
||||
if (writeback_values.Contains(alias_from.key()))
|
||||
continue;
|
||||
|
||||
// Do a first pass to check that we can replace the value
|
||||
@@ -608,17 +715,17 @@ private:
|
||||
{
|
||||
shil_opcode* op = &block->oplist[opnum];
|
||||
// find def
|
||||
if (op->rd.is_r32() && RegValue(op->rd) == alias.first)
|
||||
if (op->rd.is_r32() && RegValue(op->rd) == alias_from)
|
||||
defnum = opnum;
|
||||
else if (op->rd2.is_r32() && RegValue(op->rd2) == alias.first)
|
||||
else if (op->rd2.is_r32() && RegValue(op->rd2) == alias_from)
|
||||
defnum = opnum;
|
||||
|
||||
// find alias redef
|
||||
if (aliasdef == (size_t)-1)
|
||||
{
|
||||
if (DefinesHigherVersion(op->rd, alias.second))
|
||||
if (DefinesHigherVersion(op->rd, alias_to))
|
||||
aliasdef = opnum;
|
||||
else if (DefinesHigherVersion(op->rd2, alias.second))
|
||||
else if (DefinesHigherVersion(op->rd2, alias_to))
|
||||
aliasdef = opnum;
|
||||
/* An instruction left to the interpreter can change any
|
||||
* register, and none of them is down as written by it:
|
||||
@@ -629,7 +736,7 @@ private:
|
||||
}
|
||||
|
||||
// find last use
|
||||
if (UsesRegValue(op->rs1, alias.first))
|
||||
if (UsesRegValue(op->rs1, alias_from))
|
||||
{
|
||||
if (op->rs1.count() == 1)
|
||||
usenum = opnum;
|
||||
@@ -640,7 +747,7 @@ private:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (UsesRegValue(op->rs2, alias.first))
|
||||
else if (UsesRegValue(op->rs2, alias_from))
|
||||
{
|
||||
if (op->rs2.count() == 1)
|
||||
usenum = opnum;
|
||||
@@ -651,7 +758,7 @@ private:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (UsesRegValue(op->rs3, alias.first))
|
||||
else if (UsesRegValue(op->rs3, alias_from))
|
||||
{
|
||||
if (op->rs3.count() == 1)
|
||||
usenum = opnum;
|
||||
@@ -671,9 +778,9 @@ private:
|
||||
for (size_t opnum = defnum + 1; opnum <= usenum && usenum != (size_t)-1; opnum++)
|
||||
{
|
||||
shil_opcode* op = &block->oplist[opnum];
|
||||
ReplaceByAlias(op->rs1, alias.first, alias.second);
|
||||
ReplaceByAlias(op->rs2, alias.first, alias.second);
|
||||
ReplaceByAlias(op->rs3, alias.first, alias.second);
|
||||
ReplaceByAlias(op->rs1, alias_from, alias_to);
|
||||
ReplaceByAlias(op->rs2, alias_from, alias_to);
|
||||
ReplaceByAlias(op->rs3, alias_from, alias_to);
|
||||
}
|
||||
stats.dead_registers++;
|
||||
//printf("%08x DREG %s\n", block->vaddr + block->oplist[defnum].guest_offs, block->oplist[defnum].dissasm().c_str());
|
||||
@@ -742,7 +849,7 @@ private:
|
||||
}
|
||||
|
||||
RuntimeBlockInfo* block;
|
||||
std::set<RegValue> writeback_values;
|
||||
RegValueMap writeback_values; /* (a set) */
|
||||
|
||||
struct {
|
||||
u32 prop_constants = 0;
|
||||
@@ -758,6 +865,6 @@ private:
|
||||
// add version pass
|
||||
u32 reg_versions[sh4_reg_count];
|
||||
// const prop pass
|
||||
std::map<RegValue, u32> constprop_values; // (reg num, version) -> value
|
||||
RegValueMap constprop_values; // (reg num, version) -> value
|
||||
int opnum = 0;
|
||||
};
|
||||
+119
-62
@@ -19,8 +19,6 @@
|
||||
along with reicast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <map>
|
||||
#include <deque>
|
||||
#include "types.h"
|
||||
#include "decoder.h"
|
||||
#include "hw/sh4/modules/mmu.h"
|
||||
@@ -115,7 +113,7 @@ public:
|
||||
{
|
||||
for (int i = 0; i < op->rd.count(); i++)
|
||||
{
|
||||
verify(reg_alloced.count((Sh4RegType)(op->rd._reg + i)) == 0 || !reg_alloced[(Sh4RegType)(op->rd._reg + i)].write_back);
|
||||
verify(!IsAlloced((Sh4RegType)(op->rd._reg + i)) || !alloced[op->rd._reg + i].write_back);
|
||||
FlushReg((Sh4RegType)(op->rd._reg + i), true);
|
||||
}
|
||||
}
|
||||
@@ -123,7 +121,7 @@ public:
|
||||
{
|
||||
for (int i = 0; i < op->rd2.count(); i++)
|
||||
{
|
||||
verify(reg_alloced.count((Sh4RegType)(op->rd2._reg + i)) == 0 || !reg_alloced[(Sh4RegType)(op->rd2._reg + i)].write_back);
|
||||
verify(!IsAlloced((Sh4RegType)(op->rd2._reg + i)) || !alloced[op->rd2._reg + i].write_back);
|
||||
FlushReg((Sh4RegType)(op->rd2._reg + i), true);
|
||||
}
|
||||
}
|
||||
@@ -135,27 +133,28 @@ public:
|
||||
|
||||
void OpEnd(shil_opcode* op)
|
||||
{
|
||||
for (Sh4RegType reg : pending_flushes)
|
||||
for (u32 pi = 0; pi < pending_count; pi++)
|
||||
{
|
||||
verify(!reg_alloced[reg].write_back);
|
||||
reg_alloced.erase(reg);
|
||||
const Sh4RegType reg = pending_flushes[pi];
|
||||
verify(!alloced[reg].write_back);
|
||||
Free(reg);
|
||||
}
|
||||
pending_flushes.clear();
|
||||
pending_count = 0;
|
||||
|
||||
// Flush normally
|
||||
for (auto const& reg : reg_alloced)
|
||||
for (int reg = NextAlloced(-1); reg >= 0; reg = NextAlloced(reg))
|
||||
{
|
||||
FlushReg(reg.first, false);
|
||||
FlushReg((Sh4RegType)reg, false);
|
||||
}
|
||||
|
||||
// Hard flush all dirty regs. Useful for troubleshooting
|
||||
// while (!reg_alloced.empty())
|
||||
// while (!NoneAlloced())
|
||||
// {
|
||||
// auto it = reg_alloced.begin();
|
||||
// const Sh4RegType it = (Sh4RegType)NextAlloced(-1);
|
||||
//
|
||||
// if (it->second.dirty)
|
||||
// it->second.write_back = true;
|
||||
// FlushReg(it->first, true);
|
||||
// if (alloced[it].dirty)
|
||||
// alloced[it].write_back = true;
|
||||
// FlushReg(it, true);
|
||||
// }
|
||||
|
||||
// Final writebacks
|
||||
@@ -242,16 +241,16 @@ public:
|
||||
|
||||
bool reg_used(nreg_t host_reg)
|
||||
{
|
||||
for (auto const& reg : reg_alloced)
|
||||
if ((nreg_t)reg.second.host_reg == host_reg && !IsFloat(reg.first))
|
||||
for (int reg = NextAlloced(-1); reg >= 0; reg = NextAlloced(reg))
|
||||
if ((nreg_t)alloced[reg].host_reg == host_reg && !IsFloat((Sh4RegType)reg))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool regf_used(nregf_t host_reg)
|
||||
{
|
||||
for (auto const& reg : reg_alloced)
|
||||
if ((nregf_t)reg.second.host_reg == host_reg && IsFloat(reg.first))
|
||||
for (int reg = NextAlloced(-1); reg >= 0; reg = NextAlloced(reg))
|
||||
if ((nregf_t)alloced[reg].host_reg == host_reg && IsFloat((Sh4RegType)reg))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@@ -260,8 +259,8 @@ public:
|
||||
verify(final_opend || block->oplist.empty());
|
||||
final_opend = false;
|
||||
FlushAllRegs(true);
|
||||
verify(reg_alloced.empty());
|
||||
verify(pending_flushes.empty());
|
||||
verify(NoneAlloced());
|
||||
verify(pending_count == 0);
|
||||
block = NULL;
|
||||
host_fregs.clear();
|
||||
host_gregs.clear();
|
||||
@@ -289,28 +288,28 @@ private:
|
||||
|
||||
nreg_t mapg(Sh4RegType reg)
|
||||
{
|
||||
verify(reg_alloced.count(reg));
|
||||
return (nreg_t)reg_alloced[reg].host_reg;
|
||||
verify(IsAlloced(reg));
|
||||
return (nreg_t)alloced[reg].host_reg;
|
||||
}
|
||||
|
||||
nregf_t mapf(Sh4RegType reg)
|
||||
{
|
||||
verify(reg_alloced.count(reg));
|
||||
return (nregf_t)reg_alloced[reg].host_reg;
|
||||
verify(IsAlloced(reg));
|
||||
return (nregf_t)alloced[reg].host_reg;
|
||||
}
|
||||
|
||||
bool IsAllocf(Sh4RegType reg)
|
||||
{
|
||||
if (!IsFloat(reg))
|
||||
return false;
|
||||
return reg_alloced.find(reg) != reg_alloced.end();
|
||||
return IsAlloced(reg);
|
||||
}
|
||||
|
||||
bool IsAllocg(Sh4RegType reg)
|
||||
{
|
||||
if (IsFloat(reg))
|
||||
return false;
|
||||
return reg_alloced.find(reg) != reg_alloced.end();
|
||||
return IsAlloced(reg);
|
||||
}
|
||||
|
||||
bool IsAllocAny(Sh4RegType reg)
|
||||
@@ -337,14 +336,13 @@ private:
|
||||
|
||||
void FlushReg(Sh4RegType reg_num, bool hard)
|
||||
{
|
||||
auto reg = reg_alloced.find(reg_num);
|
||||
if (reg != reg_alloced.end())
|
||||
if (IsAlloced(reg_num))
|
||||
{
|
||||
WriteBackReg(reg->first, reg->second);
|
||||
WriteBackReg(reg_num, alloced[reg_num]);
|
||||
if (hard)
|
||||
{
|
||||
u32 host_reg = reg->second.host_reg;
|
||||
reg_alloced.erase(reg);
|
||||
u32 host_reg = alloced[reg_num].host_reg;
|
||||
Free(reg_num);
|
||||
if (IsFloat(reg_num))
|
||||
host_fregs.push_front((nregf_t)host_reg);
|
||||
else
|
||||
@@ -357,13 +355,13 @@ private:
|
||||
{
|
||||
if (hard)
|
||||
{
|
||||
while (!reg_alloced.empty())
|
||||
FlushReg(reg_alloced.begin()->first, true);
|
||||
while (!NoneAlloced())
|
||||
FlushReg((Sh4RegType)NextAlloced(-1), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto const& reg : reg_alloced)
|
||||
FlushReg(reg.first, false);
|
||||
for (int reg = NextAlloced(-1); reg >= 0; reg = NextAlloced(reg))
|
||||
FlushReg((Sh4RegType)reg, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,8 +372,7 @@ private:
|
||||
for (u32 i = 0; i < param.count(); i++)
|
||||
{
|
||||
const Sh4RegType sh4reg = (Sh4RegType)(param._reg + i);
|
||||
auto it = reg_alloced.find(sh4reg);
|
||||
if (it == reg_alloced.end())
|
||||
if (!IsAlloced(sh4reg))
|
||||
{
|
||||
u32 host_reg;
|
||||
if (param.is_r32i())
|
||||
@@ -398,7 +395,7 @@ private:
|
||||
host_reg = host_fregs.back();
|
||||
host_fregs.pop_back();
|
||||
}
|
||||
reg_alloced[sh4reg] = { host_reg, param.version[i], false, false };
|
||||
Alloc(sh4reg, { host_reg, (u16)param.version[i], false, false });
|
||||
if (!fast_forwarding)
|
||||
{
|
||||
ssa_printf("PL %s.%d -> %cx", name_reg(sh4reg).c_str(), param.version[i], 'a' + host_reg);
|
||||
@@ -447,8 +444,7 @@ private:
|
||||
for (u32 i = 0; i < param.count(); i++)
|
||||
{
|
||||
const Sh4RegType sh4reg = (Sh4RegType)(param._reg + i);
|
||||
auto it = reg_alloced.find(sh4reg);
|
||||
if (it == reg_alloced.end())
|
||||
if (!IsAlloced(sh4reg))
|
||||
{
|
||||
u32 host_reg;
|
||||
if (param.is_r32i())
|
||||
@@ -471,18 +467,18 @@ private:
|
||||
host_reg = host_fregs.back();
|
||||
host_fregs.pop_back();
|
||||
}
|
||||
reg_alloced[sh4reg] = { host_reg, param.version[i], NeedsWriteBack(sh4reg, param.version[i]), true };
|
||||
ssa_printf(" %s.%d -> %cx %s", name_reg(sh4reg).c_str(), param.version[i], 'a' + host_reg, reg_alloced[sh4reg].write_back ? "(wb)" : "");
|
||||
Alloc(sh4reg, { host_reg, (u16)param.version[i], NeedsWriteBack(sh4reg, param.version[i]), true });
|
||||
ssa_printf(" %s.%d -> %cx %s", name_reg(sh4reg).c_str(), param.version[i], 'a' + host_reg, alloced[sh4reg].write_back ? "(wb)" : "");
|
||||
}
|
||||
else
|
||||
{
|
||||
reg_alloc& reg = it->second;
|
||||
reg_alloc& reg = alloced[sh4reg];
|
||||
verify(!reg.write_back);
|
||||
reg.write_back = NeedsWriteBack(sh4reg, param.version[i]);
|
||||
reg.dirty = true;
|
||||
reg.version = param.version[i];
|
||||
}
|
||||
verify(reg_alloced[sh4reg].dirty);
|
||||
verify(alloced[sh4reg].dirty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,14 +487,16 @@ private:
|
||||
Sh4RegType spilled_reg = Sh4RegType::NoReg;
|
||||
int latest_use = -1;
|
||||
|
||||
for (auto const& reg : reg_alloced)
|
||||
for (int ri = NextAlloced(-1); ri >= 0; ri = NextAlloced(ri))
|
||||
{
|
||||
if (IsFloat(reg.first) != freg)
|
||||
const Sh4RegType reg_first = (Sh4RegType)ri;
|
||||
const reg_alloc& reg_second = alloced[ri];
|
||||
if (IsFloat(reg_first) != freg)
|
||||
continue;
|
||||
// Don't spill already spilled regs
|
||||
bool pending = false;
|
||||
for (auto& pending_reg : pending_flushes)
|
||||
if (pending_reg == reg.first)
|
||||
for (u32 pi = 0; pi < pending_count; pi++)
|
||||
if (pending_flushes[pi] == reg_first)
|
||||
{
|
||||
pending = true;
|
||||
break;
|
||||
@@ -508,7 +506,7 @@ private:
|
||||
|
||||
// Don't spill current op scalar dest regs
|
||||
shil_opcode* op = &block->oplist[opnum];
|
||||
if (DefsReg(op, reg.first, false))
|
||||
if (DefsReg(op, reg_first, false))
|
||||
continue;
|
||||
|
||||
// Find the first use, but ignore vec ops
|
||||
@@ -517,7 +515,7 @@ private:
|
||||
{
|
||||
op = &block->oplist[i];
|
||||
// Vector ops don't use reg alloc
|
||||
if (UsesReg(op, reg.first, reg.second.version, false))
|
||||
if (UsesReg(op, reg_first, reg_second.version, false))
|
||||
{
|
||||
first_use = i;
|
||||
break;
|
||||
@@ -526,13 +524,13 @@ private:
|
||||
if (first_use == -1)
|
||||
{
|
||||
latest_use = -1;
|
||||
spilled_reg = reg.first;
|
||||
spilled_reg = reg_first;
|
||||
break;
|
||||
}
|
||||
if (first_use > latest_use && first_use > opnum)
|
||||
{
|
||||
latest_use = first_use;
|
||||
spilled_reg = reg.first;
|
||||
spilled_reg = reg_first;
|
||||
}
|
||||
}
|
||||
if (latest_use != -1)
|
||||
@@ -540,8 +538,8 @@ private:
|
||||
ssa_printf("RegAlloc: non optimal alloc? reg %s used in op %d", name_reg(spilled_reg).c_str(), latest_use);
|
||||
spills++;
|
||||
// need to write-back if dirty so reload works
|
||||
if (reg_alloced[spilled_reg].dirty)
|
||||
reg_alloced[spilled_reg].write_back = true;
|
||||
if (alloced[spilled_reg].dirty)
|
||||
alloced[spilled_reg].write_back = true;
|
||||
}
|
||||
verify(spilled_reg != Sh4RegType::NoReg);
|
||||
|
||||
@@ -553,13 +551,14 @@ private:
|
||||
// It's possible that the same host reg is allocated to a source operand
|
||||
// and to the (future) dest operand. In this case we want to keep both mappings
|
||||
// until the current op is done.
|
||||
WriteBackReg(spilled_reg, reg_alloced[spilled_reg]);
|
||||
u32 host_reg = reg_alloced[spilled_reg].host_reg;
|
||||
WriteBackReg(spilled_reg, alloced[spilled_reg]);
|
||||
u32 host_reg = alloced[spilled_reg].host_reg;
|
||||
if (IsFloat(spilled_reg))
|
||||
host_fregs.push_front((nregf_t)host_reg);
|
||||
else
|
||||
host_gregs.push_front((nreg_t)host_reg);
|
||||
pending_flushes.push_back(spilled_reg);
|
||||
verify(pending_count < sh4_reg_count);
|
||||
pending_flushes[pending_count++] = spilled_reg;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,10 +598,68 @@ private:
|
||||
}
|
||||
|
||||
RuntimeBlockInfo* block = NULL;
|
||||
std::deque<nreg_t> host_gregs;
|
||||
std::deque<nregf_t> host_fregs;
|
||||
std::vector<Sh4RegType> pending_flushes;
|
||||
std::map<Sh4RegType, reg_alloc> reg_alloced;
|
||||
/* The host registers free, the next taken from the back (a ring: one
|
||||
* given back goes to the front) */
|
||||
template<typename T>
|
||||
struct HostRegs
|
||||
{
|
||||
enum { Max = 64 };
|
||||
T regs[Max];
|
||||
u32 head = 0, n = 0;
|
||||
bool empty() const { return n == 0; }
|
||||
size_t size() const { return n; }
|
||||
void clear() { head = n = 0; }
|
||||
void push_back(T v) { verify(n < Max); regs[(head + n++) % Max] = v; }
|
||||
void push_front(T v) { verify(n < Max); head = (head + Max - 1) % Max; regs[head] = v; n++; }
|
||||
T back() const { return regs[(head + n - 1) % Max]; }
|
||||
void pop_back() { n--; }
|
||||
};
|
||||
HostRegs<nreg_t> host_gregs;
|
||||
HostRegs<nregf_t> host_fregs;
|
||||
/* Registers spilled for a destination, let go at the end of the op */
|
||||
Sh4RegType pending_flushes[sh4_reg_count];
|
||||
u32 pending_count = 0;
|
||||
/* The guest registers in host ones: a register's entry, and a bit each
|
||||
* for those that have one (in register order, as they are gone through) */
|
||||
static_assert(sh4_reg_count <= 128, "two words of bits");
|
||||
reg_alloc alloced[sh4_reg_count] = {};
|
||||
u64 alloced_bits[2] = {};
|
||||
bool IsAlloced(Sh4RegType reg) const
|
||||
{
|
||||
return reg >= 0 && reg < sh4_reg_count && ((alloced_bits[reg >> 6] >> (reg & 63)) & 1) != 0;
|
||||
}
|
||||
/* the first one with a host register after @after (-1: from the start); -1 if none */
|
||||
int NextAlloced(int after) const
|
||||
{
|
||||
for (int w = (after + 1) >> 6; w < 2; w++)
|
||||
{
|
||||
u64 bits = alloced_bits[w];
|
||||
if (w == (after + 1) >> 6)
|
||||
bits &= ~0ull << ((after + 1) & 63);
|
||||
if (bits != 0)
|
||||
{
|
||||
int b = 0;
|
||||
while ((bits & 1) == 0)
|
||||
{
|
||||
bits >>= 1;
|
||||
b++;
|
||||
}
|
||||
return w * 64 + b;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
bool NoneAlloced() const { return (alloced_bits[0] | alloced_bits[1]) == 0; }
|
||||
void Alloc(Sh4RegType reg, const reg_alloc& a)
|
||||
{
|
||||
alloced[reg] = a;
|
||||
alloced_bits[reg >> 6] |= 1ull << (reg & 63);
|
||||
}
|
||||
void Free(Sh4RegType reg)
|
||||
{
|
||||
alloced[reg] = reg_alloc();
|
||||
alloced_bits[reg >> 6] &= ~(1ull << (reg & 63));
|
||||
}
|
||||
int opnum = 0;
|
||||
|
||||
bool final_opend = false;
|
||||
|
||||
+63
-19
@@ -152,9 +152,31 @@ void palette_update()
|
||||
pal_hash_256[i] = XXH32(&PALETTE_RAM[i << 8], 256 * 4, 7);
|
||||
}
|
||||
|
||||
static std::vector<vram_block*> VramLocks[VRAM_SIZE_MAX / PAGE_SIZE];
|
||||
/* The locks on each page of video memory: a list that grows (nullptr
|
||||
* where a lock has gone, used again), and how many are live, the page
|
||||
* being protected while any is. Render thread only. */
|
||||
struct VramLockList
|
||||
{
|
||||
vram_block **locks;
|
||||
u32 count, size, live;
|
||||
};
|
||||
static VramLockList VramLocks[VRAM_SIZE_MAX / PAGE_SIZE];
|
||||
VArray2 vram; // vram 32-64b
|
||||
|
||||
/* Locks gone, kept for the next (linked by userdata): a texture rendered
|
||||
* to is locked again each frame, and takes one of these, not new memory */
|
||||
static vram_block *vramlock_free_blocks;
|
||||
|
||||
static vram_block *vramlock_block_new()
|
||||
{
|
||||
vram_block *block = vramlock_free_blocks;
|
||||
if (block != nullptr)
|
||||
vramlock_free_blocks = (vram_block *)block->userdata;
|
||||
else
|
||||
block = (vram_block *)malloc(sizeof(vram_block));
|
||||
return block;
|
||||
}
|
||||
|
||||
//List functions
|
||||
//
|
||||
void vramlock_list_remove(vram_block* block)
|
||||
@@ -164,11 +186,14 @@ void vramlock_list_remove(vram_block* block)
|
||||
|
||||
for (u32 i = base; i <= end; i++)
|
||||
{
|
||||
std::vector<vram_block*>& list = VramLocks[i];
|
||||
for (auto& lock : list)
|
||||
VramLockList& list = VramLocks[i];
|
||||
for (u32 k = 0; k < list.count; k++)
|
||||
{
|
||||
if (lock == block)
|
||||
lock = nullptr;
|
||||
if (list.locks[k] == block)
|
||||
{
|
||||
list.locks[k] = nullptr;
|
||||
list.live--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,15 +205,29 @@ void vramlock_list_add(vram_block* block)
|
||||
|
||||
for (u32 i = base; i <= end; i++)
|
||||
{
|
||||
std::vector<vram_block*>& list = VramLocks[i];
|
||||
// If the list is empty then we need to protect vram, otherwise it's already been done
|
||||
if (list.empty() || std::all_of(list.begin(), list.end(), [](vram_block *block) { return block == nullptr; }))
|
||||
VramLockList& list = VramLocks[i];
|
||||
// If the list has no lock then we need to protect vram, otherwise it's already been done
|
||||
if (list.live == 0)
|
||||
_vmem_protect_vram(i * PAGE_SIZE, PAGE_SIZE);
|
||||
auto it = std::find(list.begin(), list.end(), nullptr);
|
||||
if (it != list.end())
|
||||
*it = block;
|
||||
u32 k = 0;
|
||||
if (list.live < list.count)
|
||||
while (list.locks[k] != nullptr)
|
||||
k++;
|
||||
else
|
||||
list.push_back(block);
|
||||
{
|
||||
if (list.count == list.size)
|
||||
{
|
||||
const u32 size = list.size != 0 ? list.size * 2 : 4;
|
||||
vram_block **locks = (vram_block **)realloc(list.locks, size * sizeof(vram_block *));
|
||||
if (locks == nullptr)
|
||||
die("Out of memory");
|
||||
list.locks = locks;
|
||||
list.size = size;
|
||||
}
|
||||
k = list.count++;
|
||||
}
|
||||
list.locks[k] = block;
|
||||
list.live++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,24 +258,26 @@ static void libCore_vramlock_Unlock_block_wb(vram_block* block);
|
||||
static void vramlock_collect()
|
||||
{
|
||||
vram_writes.Collect([](unsigned page) {
|
||||
std::vector<vram_block *>& list = VramLocks[page];
|
||||
VramLockList& list = VramLocks[page];
|
||||
|
||||
vram_write_epoch++;
|
||||
|
||||
for (auto& lock : list)
|
||||
for (u32 k = 0; k < list.count; k++)
|
||||
{
|
||||
vram_block *lock = list.locks[k];
|
||||
if (lock != nullptr)
|
||||
{
|
||||
rend_text_invl(lock);
|
||||
rend_text_invl(lock);
|
||||
|
||||
if (lock != nullptr)
|
||||
if (list.locks[k] != nullptr)
|
||||
{
|
||||
ERROR_LOG(PVR, "Error : pvr is supposed to remove lock");
|
||||
die("Invalid state");
|
||||
}
|
||||
}
|
||||
}
|
||||
list.clear();
|
||||
list.count = 0;
|
||||
list.live = 0;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -259,7 +300,9 @@ void libCore_vramlock_Lock(u32 start_offset64, u32 end_offset64, BaseTextureCach
|
||||
|
||||
if (texture->lock_block == nullptr)
|
||||
{
|
||||
vram_block* block=(vram_block* )malloc(sizeof(vram_block));
|
||||
vram_block* block = vramlock_block_new();
|
||||
if (block == nullptr)
|
||||
return;
|
||||
|
||||
block->end=end_offset64;
|
||||
block->start=start_offset64;
|
||||
@@ -381,7 +424,8 @@ bool VramLockedWrite(u8* address)
|
||||
static void libCore_vramlock_Unlock_block_wb(vram_block* block)
|
||||
{
|
||||
vramlock_list_remove(block);
|
||||
free(block);
|
||||
block->userdata = vramlock_free_blocks;
|
||||
vramlock_free_blocks = block;
|
||||
}
|
||||
|
||||
#ifdef HAVE_TEXUPSCALE
|
||||
|
||||
Reference in new issue
Block a user