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:
libretroadmin committed 2026-11-01 18:43:53 +01:00
1 parent d41ad6c9e5
commit 8d8146a6d9
4 files changed
+352 -144

No files matched your search

+4 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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