ssa register allocator and more ssa stuff
This commit is contained in:
parent
bd30752b86
commit
623d70d710
|
@ -0,0 +1,368 @@
|
|||
/*
|
||||
Created on: Jun 5, 2019
|
||||
|
||||
Copyright 2019 flyinghead
|
||||
|
||||
This file is part of reicast.
|
||||
|
||||
reicast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
reicast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with reicast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "blockmanager.h"
|
||||
#include "ssa.h"
|
||||
|
||||
#define SHIL_MODE 2
|
||||
#include "shil_canonical.h"
|
||||
|
||||
void SSAOptimizer::InsertMov32Op(const shil_param& rd, const shil_param& rs)
|
||||
{
|
||||
shil_opcode op2(block->oplist[opnum]);
|
||||
op2.op = shop_mov32;
|
||||
op2.rd = rd;
|
||||
op2.rd2 = shil_param();
|
||||
op2.rs1 = rs;
|
||||
op2.rs2 = shil_param();
|
||||
op2.rs3 = shil_param();
|
||||
|
||||
block->oplist.insert(block->oplist.begin() + opnum + 1, op2);
|
||||
opnum++;
|
||||
|
||||
|
||||
}
|
||||
bool SSAOptimizer::ExecuteConstOp(shil_opcode& op)
|
||||
{
|
||||
if (!op.rs1.is_reg() && !op.rs2.is_reg() && !op.rs3.is_reg()
|
||||
&& (op.rs1.is_imm() || op.rs2.is_imm() || op.rs3.is_imm())
|
||||
&& op.rd.is_reg())
|
||||
{
|
||||
// Only immediate operands -> execute the op at compile time
|
||||
|
||||
u32 rs1 = op.rs1.is_imm() ? op.rs1.imm_value() : 0;
|
||||
u32 rs2 = op.rs2.is_imm() ? op.rs2.imm_value() : 0;
|
||||
u32 rs3 = op.rs3.is_imm() ? op.rs3.imm_value() : 0;
|
||||
u32 rd;
|
||||
u32 rd2;
|
||||
|
||||
switch (op.op)
|
||||
{
|
||||
case shop_mov32:
|
||||
rd = rs1;
|
||||
break;
|
||||
case shop_add:
|
||||
rd = rs1 + rs2;
|
||||
break;
|
||||
case shop_sub:
|
||||
rd = rs1 - rs2;
|
||||
break;
|
||||
case shop_adc:
|
||||
case shop_sbc:
|
||||
case shop_negc:
|
||||
case shop_rocl:
|
||||
case shop_rocr:
|
||||
{
|
||||
u64 v;
|
||||
if (op.op == shop_adc)
|
||||
v = shil_opcl_adc::f1::impl(rs1, rs2, rs3);
|
||||
else if (op.op == shop_sbc)
|
||||
v = shil_opcl_sbc::f1::impl(rs1, rs2, rs3);
|
||||
else if (op.op == shop_rocl)
|
||||
v = shil_opcl_rocl::f1::impl(rs1, rs2);
|
||||
else if (op.op == shop_rocr)
|
||||
v = shil_opcl_rocr::f1::impl(rs1, rs2);
|
||||
else
|
||||
v = shil_opcl_negc::f1::impl(rs1, rs2);
|
||||
rd = (u32)v;
|
||||
rd2 = (u32)(v >> 32);
|
||||
|
||||
shil_param op2_rd = shil_param(op.rd2._reg);
|
||||
op2_rd.version[0] = op.rd2.version[0];
|
||||
InsertMov32Op(op2_rd, shil_param(FMT_IMM, (u32)(v >> 32)));
|
||||
|
||||
op.rd2 = shil_param();
|
||||
}
|
||||
break;
|
||||
case shop_shl:
|
||||
rd = rs1 << rs2;
|
||||
break;
|
||||
case shop_shr:
|
||||
rd = rs1 >> rs2;
|
||||
break;
|
||||
case shop_sar:
|
||||
rd = (s32) rs1 >> rs2;
|
||||
break;
|
||||
case shop_ror:
|
||||
rd = (rs1 >> rs2)
|
||||
| (rs1 << (32 - rs2));
|
||||
break;
|
||||
case shop_shld:
|
||||
rd = shil_opcl_shld::f1::impl(rs1, rs2);
|
||||
break;
|
||||
case shop_shad:
|
||||
rd = shil_opcl_shad::f1::impl(rs1, rs2);
|
||||
break;
|
||||
case shop_or:
|
||||
rd = rs1 | rs2;
|
||||
break;
|
||||
case shop_and:
|
||||
rd = rs1 & rs2;
|
||||
break;
|
||||
case shop_xor:
|
||||
rd = rs1 ^ rs2;
|
||||
break;
|
||||
case shop_not:
|
||||
rd = ~rs1;
|
||||
break;
|
||||
case shop_ext_s16:
|
||||
rd = (s32)(s16)rs1;
|
||||
break;
|
||||
case shop_ext_s8:
|
||||
rd = (s32)(s8)rs1;
|
||||
break;
|
||||
case shop_mul_i32:
|
||||
rd = rs1 * rs2;
|
||||
break;
|
||||
case shop_mul_u16:
|
||||
rd = (u16)(rs1 * rs2);
|
||||
break;
|
||||
case shop_mul_s16:
|
||||
rd = (s16)(rs1 * rs2);
|
||||
break;
|
||||
case shop_mul_u64:
|
||||
case shop_mul_s64:
|
||||
{
|
||||
u64 v;
|
||||
if (op.op == shop_mul_u64)
|
||||
v = shil_opcl_mul_u64::f1::impl(rs1, rs2);
|
||||
else
|
||||
v = shil_opcl_mul_s64::f1::impl(rs1, rs2);
|
||||
rd = (u32)v;
|
||||
rd2 = (u32)(v >> 32);
|
||||
|
||||
shil_param op2_rd = shil_param(op.rd2._reg);
|
||||
op2_rd.version[0] = op.rd2.version[0];
|
||||
InsertMov32Op(op2_rd, shil_param(FMT_IMM, rd2));
|
||||
|
||||
op.rd2 = shil_param();
|
||||
}
|
||||
break;
|
||||
case shop_test:
|
||||
rd = (rs1 & rs2) == 0;
|
||||
break;
|
||||
case shop_neg:
|
||||
rd = -rs1;
|
||||
break;
|
||||
case shop_swaplb:
|
||||
rd = shil_opcl_swaplb::f1::impl(rs1);
|
||||
break;
|
||||
case shop_swap:
|
||||
rd = shil_opcl_swap::f1::impl(rs1);
|
||||
break;
|
||||
case shop_seteq:
|
||||
case shop_setgt:
|
||||
case shop_setge:
|
||||
case shop_setab:
|
||||
case shop_setae:
|
||||
{
|
||||
switch (op.op)
|
||||
{
|
||||
case shop_seteq:
|
||||
rd = rs1 == rs2;
|
||||
break;
|
||||
case shop_setge:
|
||||
rd = (s32)rs1 >= (s32)rs2;
|
||||
break;
|
||||
case shop_setgt:
|
||||
rd = (s32)rs1 > (s32)rs2;
|
||||
break;
|
||||
case shop_setab:
|
||||
rd = rs1 > rs2;
|
||||
break;
|
||||
case shop_setae:
|
||||
rd = rs1 >= rs2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case shop_setpeq:
|
||||
rd = shil_opcl_setpeq::f1::impl(rs1, rs2);
|
||||
break;
|
||||
case shop_xtrct:
|
||||
rd = shil_opcl_xtrct::f1::impl(rs1, rs2);
|
||||
break;
|
||||
|
||||
case shop_div32u:
|
||||
case shop_div32s:
|
||||
{
|
||||
u64 res = op.op == shop_div32u ? shil_opcl_div32u::f1::impl(rs1, rs2) : shil_opcl_div32s::f1::impl(rs1, rs2);
|
||||
rd = (u32)res;
|
||||
constprop_values[RegValue(op.rd, 1)] = res >> 32;
|
||||
|
||||
shil_param op2_rd = shil_param((Sh4RegType)(op.rd._reg + 1));
|
||||
op2_rd.version[0] = op.rd.version[1];
|
||||
InsertMov32Op(op2_rd, shil_param(FMT_IMM, res >> 32));
|
||||
}
|
||||
break;
|
||||
case shop_div32p2:
|
||||
rd = shil_opcl_div32p2::f1::impl(rs1, rs2, rs3);
|
||||
break;
|
||||
|
||||
case shop_jdyn:
|
||||
{
|
||||
verify(BET_GET_CLS(block->BlockType) == BET_CLS_Dynamic);
|
||||
switch ((block->BlockType >> 1) & 3)
|
||||
{
|
||||
case BET_SCL_Jump:
|
||||
case BET_SCL_Ret:
|
||||
block->BlockType = BET_StaticJump;
|
||||
block->BranchBlock = rs1;
|
||||
break;
|
||||
case BET_SCL_Call:
|
||||
block->BlockType = BET_StaticCall;
|
||||
block->BranchBlock = rs1;
|
||||
break;
|
||||
case BET_SCL_Intr:
|
||||
block->BlockType = BET_StaticIntr;
|
||||
block->NextBlock = rs1;
|
||||
break;
|
||||
default:
|
||||
die("Unexpected block end type\n")
|
||||
;
|
||||
}
|
||||
// rd (that is jdyn) won't be updated but since it's not a real register
|
||||
// it shouldn't be a problem
|
||||
block->oplist.erase(block->oplist.begin() + opnum);
|
||||
opnum--;
|
||||
stats.dyn_to_stat_blocks++;
|
||||
|
||||
return true;
|
||||
}
|
||||
case shop_jcond:
|
||||
{
|
||||
if (rs1 != (block->BlockType & 1))
|
||||
{
|
||||
block->BranchBlock = block->NextBlock;
|
||||
}
|
||||
block->BlockType = BET_StaticJump;
|
||||
block->NextBlock = 0xFFFFFFFF;
|
||||
block->has_jcond = false;
|
||||
// same remark regarding jdyn as in the previous case
|
||||
block->oplist.erase(block->oplist.begin() + opnum);
|
||||
opnum--;
|
||||
stats.dyn_to_stat_blocks++;
|
||||
|
||||
return true;
|
||||
}
|
||||
case shop_fneg:
|
||||
rd = rs1 ^ 0x80000000;
|
||||
break;
|
||||
case shop_fadd:
|
||||
{
|
||||
f32 frd = reinterpret_cast<f32&>(rs1) + reinterpret_cast<f32&>(rs2);
|
||||
rd = reinterpret_cast<u32&>(frd);
|
||||
}
|
||||
break;
|
||||
case shop_fsub:
|
||||
{
|
||||
f32 frd = reinterpret_cast<f32&>(rs1) - reinterpret_cast<f32&>(rs2);
|
||||
rd = reinterpret_cast<u32&>(frd);
|
||||
}
|
||||
break;
|
||||
case shop_fmul:
|
||||
{
|
||||
f32 frd = reinterpret_cast<f32&>(rs1) * reinterpret_cast<f32&>(rs2);
|
||||
rd = reinterpret_cast<u32&>(frd);
|
||||
}
|
||||
break;
|
||||
case shop_fdiv:
|
||||
{
|
||||
f32 frd = reinterpret_cast<f32&>(rs1) / reinterpret_cast<f32&>(rs2);
|
||||
rd = reinterpret_cast<u32&>(frd);
|
||||
}
|
||||
break;
|
||||
case shop_cvt_i2f_n:
|
||||
case shop_cvt_i2f_z:
|
||||
{
|
||||
f32 frd = (float)(s32) rs1;
|
||||
rd = reinterpret_cast<u32&>(frd);
|
||||
}
|
||||
break;
|
||||
case shop_fsqrt:
|
||||
{
|
||||
f32 frd = sqrtf(reinterpret_cast<f32&>(rs1));
|
||||
rd = reinterpret_cast<u32&>(frd);
|
||||
}
|
||||
break;
|
||||
case shop_cvt_f2i_t:
|
||||
{
|
||||
f32 r1 = reinterpret_cast<f32&>(rs1);
|
||||
rd = shil_opcl_cvt_f2i_t::f1::impl(r1);
|
||||
}
|
||||
break;
|
||||
case shop_fsrra:
|
||||
{
|
||||
f32 frd = shil_opcl_fsrra::f1::impl(reinterpret_cast<f32&>(rs1));
|
||||
rd = reinterpret_cast<u32&>(frd);
|
||||
}
|
||||
break;
|
||||
case shop_fsca:
|
||||
{
|
||||
f32 tmp[2];
|
||||
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;
|
||||
|
||||
shil_param op2_rd = shil_param((Sh4RegType)(op.rd._reg + 1));
|
||||
op2_rd.version[0] = op.rd.version[1];
|
||||
InsertMov32Op(op2_rd, shil_param(FMT_IMM, rd_1));
|
||||
|
||||
op.rd.type = FMT_F32;
|
||||
}
|
||||
break;
|
||||
case shop_fabs:
|
||||
{
|
||||
f32 frd = shil_opcl_fabs::f1::impl(reinterpret_cast<f32&>(rs1));
|
||||
rd = reinterpret_cast<u32&>(frd);
|
||||
}
|
||||
break;
|
||||
case shop_fmac:
|
||||
{
|
||||
f32 frd = shil_opcl_fmac::f1::impl(reinterpret_cast<f32&>(rs1), reinterpret_cast<f32&>(rs2), reinterpret_cast<f32&>(rs3));
|
||||
rd = reinterpret_cast<u32&>(frd);
|
||||
}
|
||||
break;
|
||||
case shop_fseteq:
|
||||
rd = shil_opcl_fseteq::f1::impl(reinterpret_cast<f32&>(rs1), reinterpret_cast<f32&>(rs2));
|
||||
break;
|
||||
case shop_fsetgt:
|
||||
rd = shil_opcl_fsetgt::f1::impl(reinterpret_cast<f32&>(rs1), reinterpret_cast<f32&>(rs2));
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("unhandled constant op %d\n", op.op);
|
||||
die("unhandled constant op");
|
||||
break;
|
||||
}
|
||||
|
||||
constprop_values[RegValue(op.rd)] = rd;
|
||||
if (op.rd2.is_r32())
|
||||
constprop_values[RegValue(op.rd2)] = rd2;
|
||||
ReplaceByMov32(op, rd);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -43,21 +43,37 @@ public:
|
|||
ConstPropPass();
|
||||
DeadCodeRemovalPass();
|
||||
ConstantExpressionsPass();
|
||||
//DeadRegisterPass();
|
||||
//DoRegAlloc();
|
||||
CombineShiftsPass();
|
||||
DeadRegisterPass();
|
||||
IdentityMovePass();
|
||||
|
||||
#if DEBUG
|
||||
if (stats.prop_constants > 0 || stats.dead_code_ops > 0 || stats.constant_ops_replaced > 0
|
||||
|| stats.constant_ops_removed || stats.dead_registers > 0)
|
||||
|| stats.dead_registers > 0 || stats.dyn_to_stat_blocks > 0)
|
||||
{
|
||||
printf("AFTER\n");
|
||||
PrintBlock();
|
||||
printf("STATS: constants %d constant ops replaced %d removed %d dead code %d dead regs %d\n\n", stats.prop_constants, stats.constant_ops_replaced,
|
||||
stats.constant_ops_removed, stats.dead_code_ops, stats.dead_registers);
|
||||
//printf("AFTER %08x\n", block->vaddr);
|
||||
//PrintBlock();
|
||||
printf("STATS: %08x ops %zd constants %d constops replaced %d dead code %d dead regs %d dyn2stat blks %d\n", block->vaddr, block->oplist.size(),
|
||||
stats.prop_constants, stats.constant_ops_replaced,
|
||||
stats.dead_code_ops, stats.dead_registers, stats.dyn_to_stat_blocks);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AddVersionPass()
|
||||
{
|
||||
memset(reg_versions, 0, sizeof(reg_versions));
|
||||
|
||||
for (shil_opcode& op : block->oplist)
|
||||
{
|
||||
AddVersionToOperand(op.rs1, false);
|
||||
AddVersionToOperand(op.rs2, false);
|
||||
AddVersionToOperand(op.rs3, false);
|
||||
AddVersionToOperand(op.rd, true);
|
||||
AddVersionToOperand(op.rd2, true);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// References a specific version of a register value
|
||||
class RegValue : public std::pair<Sh4RegType, u32>
|
||||
|
@ -69,18 +85,14 @@ private:
|
|||
verify(param.is_reg());
|
||||
verify(index >= 0 && index < param.count());
|
||||
}
|
||||
RegValue(Sh4RegType reg, u32 version)
|
||||
: std::pair<Sh4RegType, u32>(reg, version) { }
|
||||
RegValue() : std::pair<Sh4RegType, u32>() { }
|
||||
|
||||
Sh4RegType get_reg() const { return first; }
|
||||
u32 get_version() const { return second; }
|
||||
};
|
||||
|
||||
struct reg_alloc {
|
||||
u32 host_reg;
|
||||
u16 version;
|
||||
bool write_back;
|
||||
};
|
||||
|
||||
void PrintBlock()
|
||||
{
|
||||
for (const shil_opcode& op : block->oplist)
|
||||
|
@ -102,23 +114,10 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
void AddVersionPass()
|
||||
{
|
||||
memset(reg_versions, 0, sizeof(reg_versions));
|
||||
|
||||
for (shil_opcode& op : block->oplist)
|
||||
{
|
||||
AddVersionToOperand(op.rs1, false);
|
||||
AddVersionToOperand(op.rs2, false);
|
||||
AddVersionToOperand(op.rs3, false);
|
||||
AddVersionToOperand(op.rd, true);
|
||||
AddVersionToOperand(op.rd2, true);
|
||||
}
|
||||
}
|
||||
|
||||
// mov rd, #v
|
||||
void ReplaceByMov32(shil_opcode& op, u32 v)
|
||||
{
|
||||
verify(op.rd2.is_null());
|
||||
op.op = shop_mov32;
|
||||
op.rs1 = shil_param(FMT_IMM, v);
|
||||
op.rs2.type = FMT_NULL;
|
||||
|
@ -129,11 +128,15 @@ private:
|
|||
// mov rd, rs1
|
||||
void ReplaceByMov32(shil_opcode& op)
|
||||
{
|
||||
verify(op.rd2.is_null());
|
||||
op.op = shop_mov32;
|
||||
op.rs2.type = FMT_NULL;
|
||||
op.rs3.type = FMT_NULL;
|
||||
}
|
||||
|
||||
// mov rd, rs
|
||||
void InsertMov32Op(const shil_param& rd, const shil_param& rs);
|
||||
|
||||
void ConstPropOperand(shil_param& param)
|
||||
{
|
||||
if (param.is_r32())
|
||||
|
@ -148,9 +151,11 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
bool ExecuteConstOp(shil_opcode& op);
|
||||
|
||||
void ConstPropPass()
|
||||
{
|
||||
for (int opnum = 0; opnum < block->oplist.size(); opnum++)
|
||||
for (opnum = 0; opnum < block->oplist.size(); opnum++)
|
||||
{
|
||||
shil_opcode& op = block->oplist[opnum];
|
||||
|
||||
|
@ -181,259 +186,52 @@ private:
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (!op.rs1.is_reg() && !op.rs2.is_reg() && !op.rs3.is_reg()
|
||||
&& (op.rs1.is_imm() || op.rs2.is_imm() || op.rs3.is_imm())
|
||||
&& op.rd.is_r32())
|
||||
else if (ExecuteConstOp(op))
|
||||
{
|
||||
// Only immediate operands -> execute the op at compile time
|
||||
//printf("%08x IMM %s --> \n", block->vaddr + op.guest_offs, op.dissasm().c_str());
|
||||
const RegValue dest_reg(op.rd);
|
||||
|
||||
if (op.op == shop_mov32)
|
||||
}
|
||||
else if (op.op == shop_and || op.op == shop_or || op.op == shop_xor || op.op == shop_add || op.op == shop_mul_s16 || op.op == shop_mul_u16
|
||||
|| op.op == shop_mul_i32 || op.op == shop_test || op.op == shop_seteq || op.op == shop_fseteq || op.op == shop_fadd || op.op == shop_fmul
|
||||
|| op.op == shop_mul_u64 || op.op == shop_mul_s64 || op.op == shop_adc)
|
||||
{
|
||||
if (op.rs1.is_imm() && op.rs2.is_reg())
|
||||
{
|
||||
constprop_values[dest_reg] = op.rs1.imm_value();
|
||||
// Swap rs1 and rs2 so that rs1 is never an immediate operand
|
||||
shil_param t = op.rs1;
|
||||
op.rs1 = op.rs2;
|
||||
op.rs2 = t;
|
||||
}
|
||||
else if (op.op == shop_add)
|
||||
}
|
||||
else if ((op.op == shop_shld || op.op == shop_shad) && op.rs2.is_imm())
|
||||
{
|
||||
// Replace shld/shad with shl/shr/sar
|
||||
u32 r2 = op.rs2.imm_value();
|
||||
if ((r2 & 0x80000000) == 0)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= op.rs1.imm_value() + op.rs2.imm_value();
|
||||
ReplaceByMov32(op, v);
|
||||
// rd = r1 << (r2 & 0x1F)
|
||||
op.op = shop_shl;
|
||||
op.rs2._imm = r2 & 0x1F;
|
||||
stats.constant_ops_replaced++;
|
||||
}
|
||||
else if (op.op == shop_sub)
|
||||
else if ((r2 & 0x1F) == 0)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= op.rs1.imm_value() - op.rs2.imm_value();
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_shl)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= op.rs1.imm_value() << op.rs2.imm_value();
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_shr)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= op.rs1.imm_value() >> op.rs2.imm_value();
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_sar)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= (s32)op.rs1.imm_value() >> op.rs2.imm_value();
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_ror)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= (op.rs1.imm_value() >> op.rs2.imm_value())
|
||||
| (op.rs1.imm_value() << (32 - op.rs2.imm_value()));
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_shld)
|
||||
{
|
||||
u32 r1 = op.rs1.imm_value();
|
||||
u32 r2 = op.rs2.imm_value();
|
||||
u32 rd = shil_opcl_shld::f1::impl(r1, r2);
|
||||
|
||||
constprop_values[dest_reg] = rd;
|
||||
ReplaceByMov32(op, rd);
|
||||
}
|
||||
else if (op.op == shop_shad)
|
||||
{
|
||||
u32 r1 = op.rs1.imm_value();
|
||||
u32 r2 = op.rs2.imm_value();
|
||||
u32 rd = shil_opcl_shad::f1::impl(r1, r2);
|
||||
|
||||
constprop_values[dest_reg] = rd;
|
||||
ReplaceByMov32(op, rd);
|
||||
}
|
||||
else if (op.op == shop_or)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= op.rs1.imm_value() | op.rs2.imm_value();
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_and)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= op.rs1.imm_value() & op.rs2.imm_value();
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_xor)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= op.rs1.imm_value() ^ op.rs2.imm_value();
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_not)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= ~op.rs1.imm_value();
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_ext_s16)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= (s32)(s16)op.rs1.imm_value();
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_ext_s8)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= (s32)(s8)op.rs1.imm_value();
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_mul_i32)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= op.rs1.imm_value() * op.rs2.imm_value();
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_mul_u16)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= (u16)(op.rs1.imm_value() * op.rs2.imm_value());
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_mul_s16)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= (s16)(op.rs1.imm_value() * op.rs2.imm_value());
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_test)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= (op.rs1.imm_value() & op.rs2.imm_value()) == 0;
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_neg)
|
||||
{
|
||||
u32 v = constprop_values[dest_reg]
|
||||
= -op.rs1.imm_value();
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_swaplb)
|
||||
{
|
||||
u32 v = shil_opcl_swaplb::f1::impl(op.rs1.imm_value());
|
||||
constprop_values[dest_reg] = v;
|
||||
ReplaceByMov32(op, v);
|
||||
}
|
||||
else if (op.op == shop_seteq || op.op == shop_setgt || op.op == shop_setge || op.op == shop_setab || op.op == shop_setae)
|
||||
{
|
||||
u32 r1 = op.rs1.imm_value();
|
||||
u32 r2 = op.rs2.imm_value();
|
||||
u32 rd;
|
||||
switch (op.op)
|
||||
if (op.op == shop_shl)
|
||||
// rd = 0
|
||||
ReplaceByMov32(op, 0);
|
||||
else
|
||||
{
|
||||
case shop_seteq:
|
||||
rd = r1 == r2;
|
||||
break;
|
||||
case shop_setge:
|
||||
rd = (s32)r1 >= (s32)r2;
|
||||
break;
|
||||
case shop_setgt:
|
||||
rd = (s32)r1 > (s32)r2;
|
||||
break;
|
||||
case shop_setab:
|
||||
rd = r1 > r2;
|
||||
break;
|
||||
case shop_setae:
|
||||
rd = r1 >= r2;
|
||||
break;
|
||||
// rd = r1 >> 31;
|
||||
op.op = shop_sar;
|
||||
op.rs2._imm = 31;
|
||||
stats.constant_ops_replaced++;
|
||||
}
|
||||
constprop_values[dest_reg] = rd;
|
||||
ReplaceByMov32(op, rd);
|
||||
}
|
||||
else if (op.op == shop_jdyn)
|
||||
{
|
||||
// TODO check this
|
||||
verify(BET_GET_CLS(block->BlockType) == BET_CLS_Dynamic);
|
||||
switch ((block->BlockType >> 1) & 3)
|
||||
{
|
||||
case BET_SCL_Jump:
|
||||
case BET_SCL_Ret:
|
||||
block->BlockType = BET_StaticJump;
|
||||
block->BranchBlock = op.rs1.imm_value();
|
||||
break;
|
||||
case BET_SCL_Call:
|
||||
block->BlockType = BET_StaticCall;
|
||||
block->BranchBlock = op.rs1.imm_value();
|
||||
break;
|
||||
case BET_SCL_Intr:
|
||||
block->BlockType = BET_StaticIntr;
|
||||
block->NextBlock = op.rs1.imm_value();
|
||||
break;
|
||||
default:
|
||||
die("Unexpected block end type\n");
|
||||
}
|
||||
block->oplist.erase(block->oplist.begin() + opnum);
|
||||
opnum--;
|
||||
stats.constant_ops_removed++;
|
||||
//printf("DEAD\n");
|
||||
continue;
|
||||
}
|
||||
else if (op.op == shop_jcond)
|
||||
{
|
||||
if (op.rs1.imm_value() != (block->BlockType & 1))
|
||||
{
|
||||
block->BranchBlock = block->NextBlock;
|
||||
}
|
||||
block->BlockType = BET_StaticJump;
|
||||
block->NextBlock = 0xFFFFFFFF;
|
||||
block->has_jcond = false;
|
||||
block->oplist.erase(block->oplist.begin() + opnum);
|
||||
opnum--;
|
||||
stats.constant_ops_removed++;
|
||||
//printf("DEAD\n");
|
||||
continue;
|
||||
}
|
||||
else if (op.op == shop_fneg)
|
||||
{
|
||||
u32 rd = constprop_values[dest_reg] = op.rs1.imm_value() ^ 0x80000000;
|
||||
ReplaceByMov32(op, rd);
|
||||
}
|
||||
else if (op.op == shop_fadd)
|
||||
{
|
||||
f32 rd = reinterpret_cast<f32&>(op.rs1._imm) + reinterpret_cast<f32&>(op.rs2._imm);
|
||||
constprop_values[dest_reg] = reinterpret_cast<u32&>(rd);
|
||||
ReplaceByMov32(op, reinterpret_cast<u32&>(rd));
|
||||
}
|
||||
else if (op.op == shop_fmul)
|
||||
{
|
||||
f32 rd = reinterpret_cast<f32&>(op.rs1._imm) * reinterpret_cast<f32&>(op.rs2._imm);
|
||||
constprop_values[dest_reg] = reinterpret_cast<u32&>(rd);
|
||||
ReplaceByMov32(op, reinterpret_cast<u32&>(rd));
|
||||
}
|
||||
else if (op.op == shop_cvt_i2f_n || op.op == shop_cvt_i2f_z)
|
||||
{
|
||||
f32 rd = (float)(s32)op.rs1._imm;
|
||||
constprop_values[dest_reg] = reinterpret_cast<u32&>(rd);
|
||||
ReplaceByMov32(op, reinterpret_cast<u32&>(rd));
|
||||
}
|
||||
else if (op.op == shop_fsqrt)
|
||||
{
|
||||
f32 rd = sqrtf(reinterpret_cast<f32&>(op.rs1._imm));
|
||||
constprop_values[dest_reg] = reinterpret_cast<u32&>(rd);
|
||||
ReplaceByMov32(op, reinterpret_cast<u32&>(rd));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("unhandled constant op %d\n", op.op);
|
||||
die("RHHAAA");
|
||||
// rd = r1 >> ((~r2 & 0x1F) + 1)
|
||||
op.op = op.op == shop_shad ? shop_sar : shop_shr;
|
||||
op.rs2._imm = (~r2 & 0x1F) + 1;
|
||||
stats.constant_ops_replaced++;
|
||||
}
|
||||
|
||||
//printf("%s\n", op.dissasm().c_str());
|
||||
}
|
||||
else if ((op.op == shop_and || op.op == shop_or || op.op == shop_xor || op.op == shop_add || op.op == shop_mul_s16 || op.op == shop_mul_u16
|
||||
|| op.op == shop_mul_i32 || op.op == shop_test || op.op == shop_seteq || op.op == shop_fseteq || op.op == shop_fadd || op.op == shop_fmul)
|
||||
&& op.rs1.is_imm() && op.rs2.is_reg())
|
||||
{
|
||||
// swap rs1 and rs2 so that rs1 is never an immediate operand
|
||||
shil_param t = op.rs1;
|
||||
op.rs1 = op.rs2;
|
||||
op.rs2 = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -530,15 +328,14 @@ private:
|
|||
}
|
||||
dead_code = dead_code && unused_rd;
|
||||
}
|
||||
if (dead_code && op.op != shop_readm) // TODO Can we also remove dead readm?
|
||||
if (dead_code && op.op != shop_readm) // memory read on registers can have side effects
|
||||
{
|
||||
//printf("%08x DEAD %s\n", blk->vaddr + op.guest_offs, op.dissasm().c_str());
|
||||
//printf("%08x DEAD %s\n", block->vaddr + op.guest_offs, op.dissasm().c_str());
|
||||
block->oplist.erase(block->oplist.begin() + opnum);
|
||||
stats.dead_code_ops++;
|
||||
}
|
||||
else
|
||||
{
|
||||
//printf("%08x %s\n", blk->vaddr + op.guest_offs, op.dissasm().c_str());
|
||||
if (op.rs1.is_reg())
|
||||
{
|
||||
for (int i = 0; i < op.rs1.count(); i++)
|
||||
|
@ -554,17 +351,6 @@ private:
|
|||
for (int i = 0; i < op.rs3.count(); i++)
|
||||
uses.insert(RegValue(op.rs3, i));
|
||||
}
|
||||
if (op.op == shop_mov32 && op.rs1.is_reg())
|
||||
{
|
||||
RegValue dest_reg(op.rd);
|
||||
RegValue src_reg(op.rs1);
|
||||
auto it = aliases.find(src_reg);
|
||||
if (it != aliases.end())
|
||||
// use the final value if the dest is itself aliased
|
||||
aliases[dest_reg] = it->second;
|
||||
else
|
||||
aliases[dest_reg] = src_reg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -605,17 +391,7 @@ private:
|
|||
|| op.op == shop_fadd || op.op == shop_fsub)
|
||||
{
|
||||
//printf("%08x IDEN %s\n", block->vaddr + op.guest_offs, op.dissasm().c_str());
|
||||
if (op.rd._reg == op.rs1._reg)
|
||||
{
|
||||
block->oplist.erase(block->oplist.begin() + opnum);
|
||||
opnum--;
|
||||
stats.constant_ops_removed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReplaceByMov32(op);
|
||||
}
|
||||
continue;
|
||||
ReplaceByMov32(op);
|
||||
}
|
||||
}
|
||||
// a * 1 == a
|
||||
|
@ -623,35 +399,8 @@ private:
|
|||
&& (op.op == shop_mul_i32 || op.op == shop_mul_s16 || op.op == shop_mul_u16))
|
||||
{
|
||||
//printf("%08x IDEN %s\n", block->vaddr + op.guest_offs, op.dissasm().c_str());
|
||||
if (op.rd._reg == op.rs1._reg)
|
||||
{
|
||||
block->oplist.erase(block->oplist.begin() + opnum);
|
||||
opnum--;
|
||||
stats.constant_ops_removed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReplaceByMov32(op);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// TODO very rare
|
||||
// a * 1.0 == a
|
||||
// a / 1.0 == a
|
||||
else if (op.rs2.imm_value() == 0x3f800000 // 1.0
|
||||
&& (op.op == shop_fmul || op.op == shop_fdiv))
|
||||
{
|
||||
//printf("%08x IDEN %s\n", block->vaddr + op.guest_offs, op.dissasm().c_str());
|
||||
if (op.rd._reg == op.rs1._reg)
|
||||
{
|
||||
block->oplist.erase(block->oplist.begin() + opnum);
|
||||
opnum--;
|
||||
stats.constant_ops_removed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReplaceByMov32(op);
|
||||
}
|
||||
ReplaceByMov32(op);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -669,352 +418,189 @@ private:
|
|||
else if (op.op == shop_and || op.op == shop_or)
|
||||
{
|
||||
//printf("%08x IDEN %s\n", block->vaddr + op.guest_offs, op.dissasm().c_str());
|
||||
if (op.rd._reg == op.rs1._reg)
|
||||
{
|
||||
block->oplist.erase(block->oplist.begin() + opnum);
|
||||
opnum--;
|
||||
stats.constant_ops_removed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReplaceByMov32(op);
|
||||
}
|
||||
ReplaceByMov32(op);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DefinesHigherVersion(const shil_param& param, RegValue reg_ver)
|
||||
{
|
||||
return param.is_reg()
|
||||
&& reg_ver.get_reg() >= param._reg
|
||||
&& reg_ver.get_reg() < (Sh4RegType)(param._reg + param.count())
|
||||
&& param.version[reg_ver.get_reg() - param._reg] > reg_ver.get_version();
|
||||
}
|
||||
|
||||
bool UsesRegValue(const shil_param& param, RegValue reg_ver)
|
||||
{
|
||||
return param.is_reg()
|
||||
&& reg_ver.get_reg() >= param._reg
|
||||
&& reg_ver.get_reg() < (Sh4RegType)(param._reg + param.count())
|
||||
&& param.version[reg_ver.get_reg() - param._reg] == reg_ver.get_version();
|
||||
}
|
||||
|
||||
void ReplaceByAlias(shil_param& param, const RegValue& from, const RegValue& to)
|
||||
{
|
||||
if (param.is_r32() && param._reg == from.get_reg())
|
||||
{
|
||||
verify(param.version[0] == from.get_version());
|
||||
param._reg = to.get_reg();
|
||||
param.version[0] = to.get_version();
|
||||
//printf("DeadRegisterPass replacing %s.%d by %s.%d\n", name_reg(from.get_reg()).c_str(), from.get_version(),
|
||||
// name_reg(to.get_reg()).c_str(), to.get_version());
|
||||
}
|
||||
}
|
||||
|
||||
void DeadRegisterPass()
|
||||
{
|
||||
for (auto alias : aliases)
|
||||
std::map<RegValue, RegValue> aliases; // (dest reg, version) -> (source reg, version)
|
||||
|
||||
// Find aliases
|
||||
for (shil_opcode& op : block->oplist)
|
||||
{
|
||||
if (writeback_values.count(alias.first) == 0)
|
||||
// ignore moves from/to int regs to/from fpu regs
|
||||
if (op.op == shop_mov32 && op.rs1.is_reg() && op.rd.is_r32i() == op.rs1.is_r32i())
|
||||
{
|
||||
// Do a first pass to check that we can replace the reg
|
||||
size_t defnum = -1;
|
||||
size_t usenum = -1;
|
||||
size_t aliasdef = -1;
|
||||
for (int opnum = 0; opnum < block->oplist.size(); opnum++)
|
||||
{
|
||||
shil_opcode* op = &block->oplist[opnum];
|
||||
if (op->rd.is_r32() && RegValue(op->rd) == alias.first)
|
||||
defnum = opnum;
|
||||
else if (op->rd2.is_r32() && RegValue(op->rd2) == alias.first)
|
||||
defnum = opnum;
|
||||
if (defnum == -1)
|
||||
continue;
|
||||
if (op->rd.is_reg() && alias.second.get_reg() >= op->rd._reg && alias.second.get_reg() < (Sh4RegType)(op->rd._reg + op->rd.count())
|
||||
&& op->rd.version[alias.second.get_reg() - op->rd._reg] > alias.second.get_version() && aliasdef == -1)
|
||||
aliasdef = opnum;
|
||||
else if (op->rd2.is_reg() && alias.second.get_reg() >= op->rd2._reg && alias.second.get_reg() < (Sh4RegType)(op->rd2._reg + op->rd2.count())
|
||||
&& op->rd2.version[alias.second.get_reg() - op->rd2._reg] > alias.second.get_version() && aliasdef == -1)
|
||||
aliasdef = opnum;
|
||||
if (op->rs1.is_r32() && op->rs1._reg == alias.first.get_reg() && op->rs1.version[0] == alias.first.get_version())
|
||||
usenum = opnum;
|
||||
if (op->rs2.is_r32() && op->rs2._reg == alias.first.get_reg() && op->rs2.version[0] == alias.first.get_version())
|
||||
usenum = opnum;
|
||||
if (op->rs3.is_r32() && op->rs3._reg == alias.first.get_reg() && op->rs3.version[0] == alias.first.get_version())
|
||||
usenum = opnum;
|
||||
}
|
||||
verify(defnum != -1);
|
||||
// If the alias is redefined before any use we can't do it
|
||||
if (aliasdef != -1 && usenum != -1 && aliasdef < usenum)
|
||||
continue;
|
||||
if (alias.first.get_reg() <= reg_r15)
|
||||
continue;
|
||||
// if (alias.first.get_reg() >= reg_fr_0 && alias.first.get_reg() <= reg_xf_15)
|
||||
// continue;
|
||||
for (opnum = defnum + 1; opnum <= usenum && usenum != -1; opnum++)
|
||||
{
|
||||
shil_opcode* op = &block->oplist[opnum];
|
||||
if (op->rs1.is_reg() && op->rs1.count() == 1 && op->rs1._reg == alias.first.first)
|
||||
{
|
||||
op->rs1._reg = alias.second.first;
|
||||
op->rs1.version[0] = alias.second.second;
|
||||
printf("DeadRegisterAlias rs1 replacing %s.%d by %s.%d\n", name_reg(alias.first.first).c_str(), alias.first.second,
|
||||
name_reg(alias.second.first).c_str(), alias.second.second);
|
||||
}
|
||||
if (op->rs2.is_reg() && op->rs2.count() == 1 && op->rs2._reg == alias.first.first)
|
||||
{
|
||||
op->rs2._reg = alias.second.first;
|
||||
op->rs2.version[0] = alias.second.second;
|
||||
printf("DeadRegisterAlias rs2 replacing %s.%d by %s.%d\n", name_reg(alias.first.first).c_str(), alias.first.second,
|
||||
name_reg(alias.second.first).c_str(), alias.second.second);
|
||||
}
|
||||
if (op->rs3.is_reg() && op->rs3.count() == 1 && op->rs3._reg == alias.first.first)
|
||||
{
|
||||
op->rs3._reg = alias.second.first;
|
||||
op->rs3.version[0] = alias.second.second;
|
||||
printf("DeadRegisterAlias rs3 replacing %s.%d by %s.%d\n", name_reg(alias.first.first).c_str(), alias.first.second,
|
||||
name_reg(alias.second.first).c_str(), alias.second.second);
|
||||
}
|
||||
}
|
||||
stats.dead_registers++;
|
||||
block->oplist.erase(block->oplist.begin() + defnum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DoRegAlloc()
|
||||
{
|
||||
host_gregs.clear();
|
||||
for (int i = 0; i < 6; i++) // FIXME reg count
|
||||
host_gregs.push_front(i);
|
||||
host_fregs.clear();
|
||||
for (int i = 0; i < 4; i++) // FIXME reg count
|
||||
host_fregs.push_front(i);
|
||||
|
||||
printf("BLOCK\n");
|
||||
for (opnum = 0; opnum < block->oplist.size(); opnum++)
|
||||
{
|
||||
shil_opcode* op = &block->oplist[opnum];
|
||||
if (op->op == shop_ifb || (mmu_enabled() && (op->op == shop_readm || op->op == shop_writem || op->op == shop_pref)))
|
||||
FlushAllWritebacks();
|
||||
// Flush regs used by vector ops
|
||||
if (op->rs1.count() > 1 || op->rs2.count() > 1 || op->rs3.count() > 1)
|
||||
{
|
||||
for (int i = 0; i < op->rs1.count(); i++)
|
||||
{
|
||||
auto reg = reg_alloced.find((Sh4RegType)(op->rs1._reg + i));
|
||||
if (reg != reg_alloced.end() && reg->second.version == op->rs1.version[i])
|
||||
FlushReg((Sh4RegType)(op->rs1._reg + i), true);
|
||||
}
|
||||
for (int i = 0; i < op->rs2.count(); i++)
|
||||
{
|
||||
auto reg = reg_alloced.find((Sh4RegType)(op->rs2._reg + i));
|
||||
if (reg != reg_alloced.end() && reg->second.version == op->rs2.version[i])
|
||||
FlushReg((Sh4RegType)(op->rs2._reg + i), true);
|
||||
}
|
||||
for (int i = 0; i < op->rs3.count(); i++)
|
||||
{
|
||||
auto reg = reg_alloced.find((Sh4RegType)(op->rs3._reg + i));
|
||||
if (reg != reg_alloced.end() && reg->second.version == op->rs3.version[i])
|
||||
FlushReg((Sh4RegType)(op->rs3._reg + i), true);
|
||||
}
|
||||
}
|
||||
AllocSourceReg(op->rs1);
|
||||
AllocSourceReg(op->rs2);
|
||||
AllocSourceReg(op->rs3);
|
||||
AllocDestReg(op->rd);
|
||||
AllocDestReg(op->rd2);
|
||||
printf("%08x %s\n", block->vaddr + op->guest_offs, op->dissasm().c_str());
|
||||
}
|
||||
FlushAllWritebacks();
|
||||
}
|
||||
|
||||
void FlushReg(Sh4RegType reg_num, bool full)
|
||||
{
|
||||
auto reg = reg_alloced.find(reg_num);
|
||||
verify(reg != reg_alloced.end());
|
||||
if (reg->second.write_back)
|
||||
{
|
||||
printf("WB %s.%d\n", name_reg(reg_num).c_str(), reg->second.version);
|
||||
reg->second.write_back = false;
|
||||
}
|
||||
if (full)
|
||||
{
|
||||
u32 host_reg = reg->second.host_reg;
|
||||
if (reg_num >= reg_fr_0 && reg_num <= reg_xf_15)
|
||||
host_fregs.push_front(host_reg);
|
||||
else
|
||||
host_gregs.push_front(host_reg);
|
||||
reg_alloced.erase(reg);
|
||||
}
|
||||
}
|
||||
|
||||
void FlushAllWritebacks()
|
||||
{
|
||||
for (auto reg : reg_alloced)
|
||||
{
|
||||
FlushReg(reg.first, false);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocSourceReg(const shil_param& param)
|
||||
{
|
||||
if (param.is_reg() && param.count() == 1) // TODO EXPLODE_SPANS?
|
||||
{
|
||||
auto it = reg_alloced.find(param._reg);
|
||||
if (it == reg_alloced.end())
|
||||
{
|
||||
u32 host_reg;
|
||||
if (param.is_r32i())
|
||||
{
|
||||
if (host_gregs.empty())
|
||||
{
|
||||
SpillReg(false, true);
|
||||
verify(!host_gregs.empty());
|
||||
}
|
||||
host_reg = host_gregs.back();
|
||||
host_gregs.pop_back();
|
||||
}
|
||||
RegValue dest_reg(op.rd);
|
||||
RegValue src_reg(op.rs1);
|
||||
auto it = aliases.find(src_reg);
|
||||
if (it != aliases.end())
|
||||
// use the final value if the src is itself aliased
|
||||
aliases[dest_reg] = it->second;
|
||||
else
|
||||
{
|
||||
if (host_fregs.empty())
|
||||
{
|
||||
SpillReg(true, true);
|
||||
verify(!host_fregs.empty());
|
||||
}
|
||||
host_reg = host_fregs.back();
|
||||
host_fregs.pop_back();
|
||||
}
|
||||
reg_alloced[param._reg] = { host_reg, param.version[0], false };
|
||||
printf("PL %s -> %cx\n", name_reg(param._reg).c_str(), 'a' + host_reg);
|
||||
aliases[dest_reg] = src_reg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NeedsWriteBack(Sh4RegType reg, u32 version)
|
||||
{
|
||||
u32 last_version = -1;
|
||||
for (int i = opnum + 1; i < block->oplist.size(); i++)
|
||||
// Attempt to eliminate them
|
||||
for (auto& alias : aliases)
|
||||
{
|
||||
shil_opcode* op = &block->oplist[i];
|
||||
// if a subsequent op needs all regs flushed to mem
|
||||
if (op->op == shop_ifb || (mmu_enabled() && (op->op == shop_readm || op->op == shop_writem || op->op == shop_pref)))
|
||||
return true;
|
||||
// reg is used by a subsequent vector op that doesn't use reg allocation
|
||||
if (UsesReg(op, reg, version, true))
|
||||
return true;
|
||||
if (op->rd.is_reg() && reg >= op->rd._reg && reg < (Sh4RegType)(op->rd._reg + op->rd.count()))
|
||||
last_version = op->rd.version[reg - op->rd._reg];
|
||||
else if (op->rd2.is_reg() && reg >= op->rd2._reg && reg < (Sh4RegType)(op->rd2._reg + op->rd2.count()))
|
||||
last_version = op->rd2.version[reg - op->rd2._reg];
|
||||
}
|
||||
return last_version == -1 || version == last_version;
|
||||
}
|
||||
|
||||
void AllocDestReg(const shil_param& param)
|
||||
{
|
||||
if (param.is_reg() && param.count() == 1) // TODO EXPLODE_SPANS?
|
||||
{
|
||||
auto it = reg_alloced.find(param._reg);
|
||||
if (it == reg_alloced.end())
|
||||
{
|
||||
u32 host_reg;
|
||||
if (param.is_r32i())
|
||||
{
|
||||
if (host_gregs.empty())
|
||||
{
|
||||
SpillReg(false, false);
|
||||
verify(!host_gregs.empty());
|
||||
}
|
||||
host_reg = host_gregs.back();
|
||||
host_gregs.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (host_fregs.empty())
|
||||
{
|
||||
SpillReg(true, false);
|
||||
verify(!host_fregs.empty());
|
||||
}
|
||||
host_reg = host_fregs.back();
|
||||
host_fregs.pop_back();
|
||||
}
|
||||
reg_alloced[param._reg] = { host_reg, param.version[0], NeedsWriteBack(param._reg, param.version[0]) };
|
||||
printf(" %s -> %cx\n", name_reg(param._reg).c_str(), 'a' + host_reg);
|
||||
}
|
||||
else
|
||||
{
|
||||
reg_alloc& reg = reg_alloced[param._reg];
|
||||
reg.write_back = NeedsWriteBack(param._reg, param.version[0]);
|
||||
reg.version = param.version[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpillReg(bool freg, bool source)
|
||||
{
|
||||
Sh4RegType not_used_reg = NoReg;
|
||||
Sh4RegType best_reg = NoReg;
|
||||
int best_first_use = -1;
|
||||
|
||||
for (auto reg : reg_alloced)
|
||||
{
|
||||
if ((reg.first >= reg_fr_0 && reg.first <= reg_xf_15) != freg)
|
||||
if (writeback_values.count(alias.first) > 0)
|
||||
continue;
|
||||
|
||||
// Find the first use, but prefer no write back
|
||||
int first_use = -1;
|
||||
for (int i = opnum + (source ? 0 : 1); i < block->oplist.size() /* && (first_use == -1 || write_back) */; i++)
|
||||
// Do a first pass to check that we can replace the value
|
||||
size_t defnum = -1;
|
||||
size_t usenum = -1;
|
||||
size_t aliasdef = -1;
|
||||
for (int opnum = 0; opnum < block->oplist.size(); opnum++)
|
||||
{
|
||||
shil_opcode* op = &block->oplist[i];
|
||||
if (UsesReg(op, reg.first, reg.second.version))
|
||||
shil_opcode* op = &block->oplist[opnum];
|
||||
// find def
|
||||
if (op->rd.is_r32() && RegValue(op->rd) == alias.first)
|
||||
defnum = opnum;
|
||||
else if (op->rd2.is_r32() && RegValue(op->rd2) == alias.first)
|
||||
defnum = opnum;
|
||||
|
||||
// find alias redef
|
||||
if (DefinesHigherVersion(op->rd, alias.second) && aliasdef == -1)
|
||||
aliasdef = opnum;
|
||||
else if (DefinesHigherVersion(op->rd2, alias.second) && aliasdef == -1)
|
||||
aliasdef = opnum;
|
||||
|
||||
// find last use
|
||||
if (UsesRegValue(op->rs1, alias.first))
|
||||
{
|
||||
first_use = i;
|
||||
break;
|
||||
if (op->rs1.count() == 1)
|
||||
usenum = opnum;
|
||||
else
|
||||
{
|
||||
usenum = 0xFFFF; // Can't alias values used by vectors cuz they need adjacent regs
|
||||
aliasdef = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (UsesRegValue(op->rs2, alias.first))
|
||||
{
|
||||
if (op->rs2.count() == 1)
|
||||
usenum = opnum;
|
||||
else
|
||||
{
|
||||
usenum = 0xFFFF;
|
||||
aliasdef = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (UsesRegValue(op->rs3, alias.first))
|
||||
{
|
||||
if (op->rs3.count() == 1)
|
||||
usenum = opnum;
|
||||
else
|
||||
{
|
||||
usenum = 0xFFFF;
|
||||
aliasdef = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (first_use == -1)
|
||||
{
|
||||
not_used_reg = reg.first;
|
||||
break;
|
||||
}
|
||||
if (first_use > best_first_use && first_use > opnum)
|
||||
{
|
||||
best_first_use = first_use;
|
||||
best_reg = reg.first;
|
||||
}
|
||||
}
|
||||
Sh4RegType spilled_reg;
|
||||
if (not_used_reg != NoReg)
|
||||
spilled_reg = not_used_reg;
|
||||
else
|
||||
{
|
||||
printf("RegAlloc: non optimal alloc? reg %s used in op %d\n", name_reg(best_reg).c_str(), best_first_use);
|
||||
spilled_reg = best_reg;
|
||||
}
|
||||
verify(spilled_reg != NoReg);
|
||||
verify(defnum != -1);
|
||||
// If the alias is redefined before any use we can't use it
|
||||
if (aliasdef != -1 && usenum != -1 && aliasdef < usenum)
|
||||
continue;
|
||||
|
||||
if (reg_alloced[spilled_reg].write_back)
|
||||
{
|
||||
printf("WB %s.%d\n", name_reg(spilled_reg).c_str(), reg_alloced[spilled_reg].version);
|
||||
for (int opnum = defnum + 1; opnum <= usenum && usenum != -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);
|
||||
}
|
||||
stats.dead_registers++;
|
||||
//printf("%08x DREG %s\n", block->vaddr + block->oplist[defnum].guest_offs, block->oplist[defnum].dissasm().c_str());
|
||||
block->oplist.erase(block->oplist.begin() + defnum);
|
||||
}
|
||||
u32 host_reg = reg_alloced[spilled_reg].host_reg;
|
||||
reg_alloced.erase(spilled_reg);
|
||||
if (freg)
|
||||
host_fregs.push_front(host_reg);
|
||||
else
|
||||
host_gregs.push_front(host_reg);
|
||||
}
|
||||
|
||||
bool UsesReg(shil_opcode* op, Sh4RegType reg, u32 version, bool vector = false)
|
||||
void IdentityMovePass()
|
||||
{
|
||||
if (op->rs1.is_reg() && reg >= op->rs1._reg && reg < (Sh4RegType)(op->rs1._reg + op->rs1.count())
|
||||
&& version == op->rs1.version[reg - op->rs1._reg]
|
||||
&& (!vector || op->rs1.count() > 1))
|
||||
return true;
|
||||
if (op->rs2.is_reg() && reg >= op->rs2._reg && reg < (Sh4RegType)(op->rs2._reg + op->rs2.count())
|
||||
&& version == op->rs2.version[reg - op->rs2._reg]
|
||||
&& (!vector || op->rs2.count() > 1))
|
||||
return true;
|
||||
if (op->rs3.is_reg() && reg >= op->rs3._reg && reg < (Sh4RegType)(op->rs3._reg + op->rs3.count())
|
||||
&& version == op->rs3.version[reg - op->rs3._reg]
|
||||
&& (!vector || op->rs3.count() > 1))
|
||||
return true;
|
||||
return false;
|
||||
// This pass creates holes in reg versions and should be run last
|
||||
// The versioning pass must be re-run if needed
|
||||
for (int opnum = 0; opnum < block->oplist.size(); opnum++)
|
||||
{
|
||||
shil_opcode& op = block->oplist[opnum];
|
||||
if (op.op == shop_mov32 && op.rs1.is_reg() && op.rd._reg == op.rs1._reg)
|
||||
{
|
||||
//printf("%08x DIDN %s\n", block->vaddr + op.guest_offs, op.dissasm().c_str());
|
||||
block->oplist.erase(block->oplist.begin() + opnum);
|
||||
opnum--;
|
||||
stats.dead_code_ops++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CombineShiftsPass()
|
||||
{
|
||||
for (int opnum = 0; opnum < (int)block->oplist.size() - 1; opnum++)
|
||||
{
|
||||
shil_opcode& op = block->oplist[opnum];
|
||||
shil_opcode& next_op = block->oplist[opnum + 1];
|
||||
if (op.op == next_op.op && (op.op == shop_shl || op.op == shop_shr || op.op == shop_sar) && next_op.rs1.is_r32i() && op.rd._reg == next_op.rs1._reg)
|
||||
{
|
||||
if (next_op.rs2._imm + op.rs2._imm <= 31)
|
||||
{
|
||||
next_op.rs2._imm += op.rs2._imm;
|
||||
//printf("%08x SHFT %s -> %d\n", block->vaddr + op.guest_offs, op.dissasm().c_str(), next_op.rs2._imm);
|
||||
ReplaceByMov32(op);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RuntimeBlockInfo* block;
|
||||
std::set<RegValue> writeback_values;
|
||||
std::map<RegValue, RegValue> aliases; // (dest reg, version) -> (source reg, version)
|
||||
|
||||
struct {
|
||||
u32 prop_constants = 0;
|
||||
u32 constant_ops_replaced = 0;
|
||||
u32 constant_ops_removed = 0;
|
||||
u32 dead_code_ops = 0;
|
||||
u32 dead_registers = 0;
|
||||
u32 dyn_to_stat_blocks = 0;
|
||||
} stats;
|
||||
|
||||
// transient vars
|
||||
int opnum = 0;
|
||||
// add version pass
|
||||
u32 reg_versions[sh4_reg_count];
|
||||
// const prop pass
|
||||
std::map<RegValue, u32> constprop_values; // (reg num, version) -> value
|
||||
// reg alloc
|
||||
deque<u32> host_gregs;
|
||||
deque<u32> host_fregs;
|
||||
std::map<Sh4RegType, reg_alloc> reg_alloced;
|
||||
int opnum = 0;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,584 @@
|
|||
/*
|
||||
Created on: Jun 5, 2019
|
||||
|
||||
Copyright 2019 flyinghead
|
||||
|
||||
This file is part of reicast.
|
||||
|
||||
reicast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
reicast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
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"
|
||||
#include "ssa.h"
|
||||
|
||||
#define ssa_printf(...)
|
||||
|
||||
template<typename nreg_t, typename nregf_t, bool explode_spans = true>
|
||||
class RegAlloc
|
||||
{
|
||||
public:
|
||||
RegAlloc() {}
|
||||
virtual ~RegAlloc() {}
|
||||
|
||||
void DoAlloc(RuntimeBlockInfo* block, const nreg_t* regs_avail, const nregf_t* regsf_avail)
|
||||
{
|
||||
this->block = block;
|
||||
SSAOptimizer optim(block);
|
||||
optim.AddVersionPass();
|
||||
|
||||
while (*regs_avail != (nreg_t)-1)
|
||||
host_gregs.push_back(*regs_avail++);
|
||||
|
||||
while (*regsf_avail != (nregf_t)-1)
|
||||
host_fregs.push_back(*regsf_avail++);
|
||||
}
|
||||
|
||||
void OpBegin(shil_opcode* op, int opid)
|
||||
{
|
||||
opnum = opid;
|
||||
if (op->op == shop_ifb)
|
||||
{
|
||||
FlushAllRegs(true);
|
||||
}
|
||||
else if (mmu_enabled() && (op->op == shop_readm || op->op == shop_writem || op->op == shop_pref))
|
||||
{
|
||||
FlushAllRegs(false);
|
||||
}
|
||||
else if (op->op == shop_sync_sr)
|
||||
{
|
||||
//FlushReg(reg_sr_T, true);
|
||||
FlushReg(reg_sr_status, true);
|
||||
FlushReg(reg_old_sr_status, true);
|
||||
for (int i = reg_r0; i <= reg_r7; i++)
|
||||
FlushReg((Sh4RegType)i, true);
|
||||
for (int i = reg_r0_Bank; i <= reg_r7_Bank; i++)
|
||||
FlushReg((Sh4RegType)i, true);
|
||||
}
|
||||
else if (op->op == shop_sync_fpscr)
|
||||
{
|
||||
FlushReg(reg_fpscr, true);
|
||||
FlushReg(reg_old_fpscr, true);
|
||||
for (int i = reg_fr_0; i <= reg_xf_15; i++)
|
||||
FlushReg((Sh4RegType)i, true);
|
||||
}
|
||||
// Flush regs used by vector ops
|
||||
if (op->rs1.is_reg() && op->rs1.count() > 1)
|
||||
{
|
||||
for (int i = 0; i < op->rs1.count(); i++)
|
||||
FlushReg((Sh4RegType)(op->rs1._reg + i), false);
|
||||
}
|
||||
if (op->rs2.is_reg() && op->rs2.count() > 1)
|
||||
{
|
||||
for (int i = 0; i < op->rs2.count(); i++)
|
||||
FlushReg((Sh4RegType)(op->rs2._reg + i), false);
|
||||
}
|
||||
if (op->rs3.is_reg() && op->rs3.count() > 1)
|
||||
{
|
||||
for (int i = 0; i < op->rs3.count(); i++)
|
||||
FlushReg((Sh4RegType)(op->rs3._reg + i), false);
|
||||
}
|
||||
if (op->op != shop_ifb)
|
||||
{
|
||||
AllocSourceReg(op->rs1);
|
||||
AllocSourceReg(op->rs2);
|
||||
AllocSourceReg(op->rs3);
|
||||
// Hard flush vector ops destination regs
|
||||
// Note that this is incorrect if a reg is both src (scalar) and dest (vec). However such an op doesn't exist.
|
||||
if (op->rd.is_reg() && op->rd.count() > 1)
|
||||
{
|
||||
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);
|
||||
FlushReg((Sh4RegType)(op->rd._reg + i), true);
|
||||
}
|
||||
}
|
||||
if (op->rd2.is_reg() && op->rd2.count() > 1)
|
||||
{
|
||||
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);
|
||||
FlushReg((Sh4RegType)(op->rd2._reg + i), true);
|
||||
}
|
||||
}
|
||||
AllocDestReg(op->rd);
|
||||
AllocDestReg(op->rd2);
|
||||
}
|
||||
ssa_printf("%08x %s gregs %ld fregs %ld\n", block->vaddr + op->guest_offs, op->dissasm().c_str(), host_gregs.size(), host_fregs.size());
|
||||
}
|
||||
|
||||
void OpEnd(shil_opcode* op)
|
||||
{
|
||||
for (Sh4RegType reg : pending_flushes)
|
||||
{
|
||||
verify(!reg_alloced[reg].write_back);
|
||||
reg_alloced.erase(reg);
|
||||
}
|
||||
pending_flushes.clear();
|
||||
|
||||
// Flush normally
|
||||
for (auto const& reg : reg_alloced)
|
||||
{
|
||||
FlushReg(reg.first, false);
|
||||
}
|
||||
|
||||
// Hard flush all dirty regs. Useful for troubleshooting
|
||||
// while (!reg_alloced.empty())
|
||||
// {
|
||||
// auto it = reg_alloced.begin();
|
||||
//
|
||||
// if (it->second.dirty)
|
||||
// it->second.write_back = true;
|
||||
// FlushReg(it->first, true);
|
||||
// }
|
||||
|
||||
// Final writebacks
|
||||
if (op >= &block->oplist.back())
|
||||
{
|
||||
FlushAllRegs(false);
|
||||
final_opend = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetOpnum(int num)
|
||||
{
|
||||
fast_forwarding = true;
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
shil_opcode* op = &block->oplist[i];
|
||||
OpBegin(op, i);
|
||||
OpEnd(op);
|
||||
}
|
||||
OpBegin(&block->oplist[num], num);
|
||||
fast_forwarding = false;
|
||||
}
|
||||
|
||||
bool IsAllocAny(const shil_param& prm)
|
||||
{
|
||||
if (prm.is_reg())
|
||||
{
|
||||
bool rv = IsAllocAny(prm._reg);
|
||||
if (prm.count() != 1)
|
||||
{
|
||||
for (u32 i = 1;i < prm.count(); i++)
|
||||
verify(IsAllocAny((Sh4RegType)(prm._reg + i)) == rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsAllocg(const shil_param& prm)
|
||||
{
|
||||
if (prm.is_reg())
|
||||
{
|
||||
verify(prm.count() == 1);
|
||||
return IsAllocg(prm._reg);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsAllocf(const shil_param& prm)
|
||||
{
|
||||
if (prm.is_reg())
|
||||
{
|
||||
verify(prm.count() == 1);
|
||||
return IsAllocf(prm._reg);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
nreg_t mapg(const shil_param& prm)
|
||||
{
|
||||
verify(IsAllocg(prm));
|
||||
verify(prm.count() == 1);
|
||||
return mapg(prm._reg);
|
||||
}
|
||||
|
||||
nregf_t mapf(const shil_param& prm)
|
||||
{
|
||||
verify(IsAllocf(prm));
|
||||
verify(prm.count() == 1);
|
||||
return mapf(prm._reg);
|
||||
}
|
||||
|
||||
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))
|
||||
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))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void Cleanup() {
|
||||
verify(final_opend || block->oplist.size() == 0);
|
||||
}
|
||||
|
||||
virtual void Preload(u32 reg, nreg_t nreg) = 0;
|
||||
virtual void Writeback(u32 reg, nreg_t nreg) = 0;
|
||||
virtual void CheckReg(u32 reg, nreg_t nreg) = 0;
|
||||
|
||||
virtual void Preload_FPU(u32 reg, nregf_t nreg) = 0;
|
||||
virtual void Writeback_FPU(u32 reg, nregf_t nreg) = 0;
|
||||
virtual void CheckReg_FPU(u32 reg, nregf_t nreg) = 0;
|
||||
|
||||
private:
|
||||
struct reg_alloc {
|
||||
u32 host_reg;
|
||||
u16 version;
|
||||
bool write_back;
|
||||
bool dirty;
|
||||
};
|
||||
|
||||
bool IsFloat(Sh4RegType reg)
|
||||
{
|
||||
return reg >= reg_fr_0 && reg <= reg_xf_15;
|
||||
}
|
||||
|
||||
nreg_t mapg(Sh4RegType reg)
|
||||
{
|
||||
verify(reg_alloced.count(reg));
|
||||
return (nreg_t)reg_alloced[reg].host_reg;
|
||||
}
|
||||
|
||||
nregf_t mapf(Sh4RegType reg)
|
||||
{
|
||||
verify(reg_alloced.count(reg));
|
||||
return (nregf_t)reg_alloced[reg].host_reg;
|
||||
}
|
||||
|
||||
bool IsAllocf(Sh4RegType reg)
|
||||
{
|
||||
if (!IsFloat(reg))
|
||||
return false;
|
||||
return reg_alloced.find(reg) != reg_alloced.end();
|
||||
}
|
||||
|
||||
bool IsAllocg(Sh4RegType reg)
|
||||
{
|
||||
if (IsFloat(reg))
|
||||
return false;
|
||||
return reg_alloced.find(reg) != reg_alloced.end();
|
||||
}
|
||||
|
||||
bool IsAllocAny(Sh4RegType reg)
|
||||
{
|
||||
return IsAllocg(reg) || IsAllocf(reg);
|
||||
}
|
||||
|
||||
void WriteBackReg(Sh4RegType reg_num, struct reg_alloc& reg_alloc)
|
||||
{
|
||||
if (reg_alloc.write_back)
|
||||
{
|
||||
if (!fast_forwarding)
|
||||
{
|
||||
ssa_printf("WB %s.%d <- %cx\n", name_reg(reg_num).c_str(), reg_alloc.version, 'a' + reg_alloc.host_reg);
|
||||
if (IsFloat(reg_num))
|
||||
Writeback_FPU(reg_num, (nregf_t)reg_alloc.host_reg);
|
||||
else
|
||||
Writeback(reg_num, (nreg_t)reg_alloc.host_reg);
|
||||
}
|
||||
reg_alloc.write_back = false;
|
||||
reg_alloc.dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FlushReg(Sh4RegType reg_num, bool hard)
|
||||
{
|
||||
auto reg = reg_alloced.find(reg_num);
|
||||
if (reg != reg_alloced.end())
|
||||
{
|
||||
WriteBackReg(reg->first, reg->second);
|
||||
if (hard)
|
||||
{
|
||||
u32 host_reg = reg->second.host_reg;
|
||||
reg_alloced.erase(reg);
|
||||
if (IsFloat(reg_num))
|
||||
host_fregs.push_front((nregf_t)host_reg);
|
||||
else
|
||||
host_gregs.push_front((nreg_t)host_reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FlushAllRegs(bool hard)
|
||||
{
|
||||
if (hard)
|
||||
{
|
||||
while (!reg_alloced.empty())
|
||||
FlushReg(reg_alloced.begin()->first, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto const& reg : reg_alloced)
|
||||
FlushReg(reg.first, false);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocSourceReg(const shil_param& param)
|
||||
{
|
||||
if (param.is_reg() && param.count() == 1) // TODO EXPLODE_SPANS?
|
||||
{
|
||||
auto it = reg_alloced.find(param._reg);
|
||||
if (it == reg_alloced.end())
|
||||
{
|
||||
u32 host_reg;
|
||||
if (param.is_r32i())
|
||||
{
|
||||
if (host_gregs.empty())
|
||||
{
|
||||
SpillReg(false, true);
|
||||
verify(!host_gregs.empty());
|
||||
}
|
||||
host_reg = host_gregs.back();
|
||||
host_gregs.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (host_fregs.empty())
|
||||
{
|
||||
SpillReg(true, true);
|
||||
verify(!host_fregs.empty());
|
||||
}
|
||||
host_reg = host_fregs.back();
|
||||
host_fregs.pop_back();
|
||||
}
|
||||
reg_alloced[param._reg] = { host_reg, param.version[0], false, false };
|
||||
if (!fast_forwarding)
|
||||
{
|
||||
ssa_printf("PL %s.%d -> %cx\n", name_reg(param._reg).c_str(), param.version[0], 'a' + host_reg);
|
||||
if (IsFloat(param._reg))
|
||||
Preload_FPU(param._reg, (nregf_t)host_reg);
|
||||
else
|
||||
Preload(param._reg, (nreg_t)host_reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NeedsWriteBack(Sh4RegType reg, u32 version)
|
||||
{
|
||||
for (int i = opnum + 1; i < block->oplist.size(); i++)
|
||||
{
|
||||
shil_opcode* op = &block->oplist[i];
|
||||
// if a subsequent op needs all or some regs flushed to mem
|
||||
// TODO we could look at the ifb op to optimize what to flush
|
||||
if (op->op == shop_ifb || (mmu_enabled() && (op->op == shop_readm || op->op == shop_writem || op->op == shop_pref)))
|
||||
return true;
|
||||
if (op->op == shop_sync_sr && (/*reg == reg_sr_T ||*/ reg == reg_sr_status || reg == reg_old_sr_status || (reg >= reg_r0 && reg <= reg_r7)
|
||||
|| (reg >= reg_r0_Bank && reg <= reg_r7_Bank)))
|
||||
return true;
|
||||
if (op->op == shop_sync_fpscr && (reg == reg_fpscr || reg == reg_old_fpscr || (reg >= reg_fr_0 && reg <= reg_xf_15)))
|
||||
return true;
|
||||
// if reg is used by a subsequent vector op that doesn't use reg allocation
|
||||
if (UsesReg(op, reg, version, true))
|
||||
return true;
|
||||
// no writeback needed if redefined
|
||||
if (DefsReg(op, reg, true))
|
||||
return false;
|
||||
if (DefsReg(op, reg, false))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AllocDestReg(const shil_param& param)
|
||||
{
|
||||
if (param.is_reg() && param.count() == 1) // TODO EXPLODE_SPANS?
|
||||
{
|
||||
auto it = reg_alloced.find(param._reg);
|
||||
if (it == reg_alloced.end())
|
||||
{
|
||||
u32 host_reg;
|
||||
if (param.is_r32i())
|
||||
{
|
||||
if (host_gregs.empty())
|
||||
{
|
||||
SpillReg(false, false);
|
||||
verify(!host_gregs.empty());
|
||||
}
|
||||
host_reg = host_gregs.back();
|
||||
host_gregs.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (host_fregs.empty())
|
||||
{
|
||||
SpillReg(true, false);
|
||||
verify(!host_fregs.empty());
|
||||
}
|
||||
host_reg = host_fregs.back();
|
||||
host_fregs.pop_back();
|
||||
}
|
||||
reg_alloced[param._reg] = { host_reg, param.version[0], NeedsWriteBack(param._reg, param.version[0]), true };
|
||||
ssa_printf(" %s.%d -> %cx %s\n", name_reg(param._reg).c_str(), param.version[0], 'a' + host_reg, reg_alloced[param._reg].write_back ? "(wb)" : "");
|
||||
}
|
||||
else
|
||||
{
|
||||
reg_alloc& reg = reg_alloced[param._reg];
|
||||
verify(!reg.write_back);
|
||||
reg.write_back = NeedsWriteBack(param._reg, param.version[0]);
|
||||
reg.dirty = true;
|
||||
reg.version = param.version[0];
|
||||
}
|
||||
verify(reg_alloced[param._reg].dirty);
|
||||
}
|
||||
}
|
||||
|
||||
void SpillReg(bool freg, bool source)
|
||||
{
|
||||
Sh4RegType spilled_reg = Sh4RegType::NoReg;
|
||||
int latest_use = -1;
|
||||
|
||||
for (auto const& reg : reg_alloced)
|
||||
{
|
||||
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)
|
||||
{
|
||||
pending = true;
|
||||
break;
|
||||
}
|
||||
if (pending)
|
||||
continue;
|
||||
|
||||
// Don't spill current op scalar dest regs
|
||||
shil_opcode* op = &block->oplist[opnum];
|
||||
if (DefsReg(op, reg.first, false))
|
||||
continue;
|
||||
|
||||
// Find the first use, but ignore vec ops
|
||||
int first_use = -1;
|
||||
for (int i = opnum + (source ? 0 : 1); i < block->oplist.size(); i++)
|
||||
{
|
||||
op = &block->oplist[i];
|
||||
// Vector ops don't use reg alloc
|
||||
if (UsesReg(op, reg.first, reg.second.version, false))
|
||||
{
|
||||
first_use = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (first_use == -1)
|
||||
{
|
||||
latest_use = -1;
|
||||
spilled_reg = reg.first;
|
||||
break;
|
||||
}
|
||||
if (first_use > latest_use && first_use > opnum)
|
||||
{
|
||||
latest_use = first_use;
|
||||
spilled_reg = reg.first;
|
||||
}
|
||||
}
|
||||
if (latest_use != -1)
|
||||
{
|
||||
ssa_printf("RegAlloc: non optimal alloc? reg %s used in op %d\n", 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;
|
||||
}
|
||||
verify(spilled_reg != Sh4RegType::NoReg);
|
||||
|
||||
if (source)
|
||||
FlushReg(spilled_reg, true);
|
||||
else
|
||||
{
|
||||
// Delay the eviction of spilled regs from the map due to dest register allocation.
|
||||
// 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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsVectorOp(shil_opcode* op)
|
||||
{
|
||||
return op->rs1.count() > 1 || op->rs2.count() > 1 || op->rs3.count() > 1 || op->rd.count() > 1 || op->rd2.count() > 1;
|
||||
}
|
||||
|
||||
bool UsesReg(shil_opcode* op, Sh4RegType reg, u32 version, bool vector)
|
||||
{
|
||||
if (op->rs1.is_reg() && reg >= op->rs1._reg && reg < (Sh4RegType)(op->rs1._reg + op->rs1.count())
|
||||
&& version == op->rs1.version[reg - op->rs1._reg]
|
||||
&& vector == (op->rs1.count() > 1))
|
||||
return true;
|
||||
if (op->rs2.is_reg() && reg >= op->rs2._reg && reg < (Sh4RegType)(op->rs2._reg + op->rs2.count())
|
||||
&& version == op->rs2.version[reg - op->rs2._reg]
|
||||
&& vector == (op->rs2.count() > 1))
|
||||
return true;
|
||||
if (op->rs3.is_reg() && reg >= op->rs3._reg && reg < (Sh4RegType)(op->rs3._reg + op->rs3.count())
|
||||
&& version == op->rs3.version[reg - op->rs3._reg]
|
||||
&& vector == (op->rs3.count() > 1))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DefsReg(shil_opcode* op, Sh4RegType reg, bool vector)
|
||||
{
|
||||
if (op->rd.is_reg() && reg >= op->rd._reg && reg < (Sh4RegType)(op->rd._reg + op->rd.count())
|
||||
&& vector == (op->rd.count() > 1))
|
||||
return true;
|
||||
if (op->rd2.is_reg() && reg >= op->rd2._reg && reg < (Sh4RegType)(op->rd2._reg + op->rd2.count())
|
||||
&& vector == (op->rd2.count() > 1))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
RuntimeBlockInfo* block = NULL;
|
||||
deque<nreg_t> host_gregs;
|
||||
deque<nregf_t> host_fregs;
|
||||
vector<Sh4RegType> pending_flushes;
|
||||
std::map<Sh4RegType, reg_alloc> reg_alloced;
|
||||
int opnum = 0;
|
||||
|
||||
bool final_opend = false;
|
||||
bool fast_forwarding = false;
|
||||
public:
|
||||
u32 spills = 0;
|
||||
};
|
||||
|
||||
#undef printf
|
Loading…
Reference in New Issue