Merge pull request #1531 from reicast/fh/smc-option

dynarec: add option to control smc code checks: faster, fast, full
This commit is contained in:
flyinghead 2019-03-30 13:27:45 +01:00 committed by GitHub
commit 26f02e22da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 321 additions and 148 deletions

View File

@ -304,6 +304,8 @@ void bm_Rebuild()
{
return;
die("this is broken in multiple levels, including compile options");
void RASDASD();
RASDASD();
@ -321,7 +323,7 @@ void bm_Rebuild()
//constprop(all_blocks[i]);
//#endif
}
ngen_Compile(all_blocks[i],false,false,all_blocks[i]->staging_runs>0,do_opts);
ngen_Compile(all_blocks[i],NoCheck,false,all_blocks[i]->staging_runs>0,do_opts);
blkmap.insert(all_blocks[i]);
verify(bm_GetBlock((RuntimeBlockInfo*)all_blocks[i]->code)==all_blocks[i]);

View File

@ -86,6 +86,15 @@ void recSh4_Run()
sh4_dyna_rcb=(u8*)&Sh4cntx + sizeof(Sh4cntx);
printf("cntx // fpcb offset: %td // pc offset: %td // pc %08X\n",(u8*)&sh4rcb.fpcb - sh4_dyna_rcb, (u8*)&sh4rcb.cntx.pc - sh4_dyna_rcb,sh4rcb.cntx.pc);
if (!settings.dynarec.safemode)
printf("Warning: Dynarec safe mode is off\n");
if (settings.dynarec.unstable_opt)
printf("Warning: Unstable optimizations is on\n");
if (settings.dynarec.SmcCheckLevel != FullCheck)
printf("Warning: SMC check mode is %d\n", settings.dynarec.SmcCheckLevel);
verify(rcb_noffs(&next_pc)==-184);
ngen_mainloop(sh4_dyna_rcb);
@ -119,34 +128,51 @@ u32 emit_FreeSpace()
}
bool DoCheck(u32 pc)
SmcCheckEnum DoCheck(u32 pc)
{
if (IsOnRam(pc))
{
if (!settings.dynarec.unstable_opt)
return true;
pc&=0xFFFFFF;
switch(pc)
{
//DOA2LE
case 0x3DAFC6:
case 0x3C83F8:
switch (settings.dynarec.SmcCheckLevel) {
//Shenmue 2
case 0x348000:
//Shenmue
case 0x41860e:
// Heuristic-elimintaed FastChecks
case NoCheck: {
if (IsOnRam(pc))
{
pc&=0xFFFFFF;
switch(pc)
{
//DOA2LE
case 0x3DAFC6:
case 0x3C83F8:
return true;
//Shenmue 2
case 0x348000:
//Shenmue
case 0x41860e:
default:
return false;
return FastCheck;
default:
return NoCheck;
}
}
return NoCheck;
}
break;
// Fast Check everything
case FastCheck:
return FastCheck;
// Full Check everything
case FullCheck:
return FullCheck;
default:
die("Unhandled settings.dynarec.SmcCheckLevel");
return FullCheck;
}
return false;
}
void AnalyseBlock(RuntimeBlockInfo* blk);

View File

@ -85,7 +85,7 @@ u32 DYNACALL rdv_DoInterrupts_pc(u32 pc);
void ngen_init();
//Called to compile a block
void ngen_Compile(RuntimeBlockInfo* block,bool force_checks, bool reset, bool staging,bool optimise);
void ngen_Compile(RuntimeBlockInfo* block, SmcCheckEnum smc_checks, bool reset, bool staging,bool optimise);
//Called when blocks are reseted
void ngen_ResetBlocks();

View File

@ -12,6 +12,8 @@
//Types
#define printf_smc(...) // printf
u32 CCN_QACR_TR[2];
@ -72,13 +74,18 @@ void CCN_CCR_write(u32 addr, u32 value)
temp.reg_data=value;
//what is 0xAC13DBF8 from ?
if (temp.ICI && curr_pc!=0xAC13DBF8)
{
//printf("Sh4: i-cache invalidation %08X\n",curr_pc);
// Shikigami No Shiro II sets ICI frequently
// Any reason to flush the dynarec cache for this?
//sh4_cpu.ResetCache();
if (temp.ICI) {
printf_smc("Sh4: i-cache invalidation %08X\n",curr_pc);
if (settings.dynarec.SmcCheckLevel != FullCheck) {
//TODO: Add skip/check vectors for Shikigami No Shiro II (uses ICI frequently)
//which game is 0xAC13DBF8 from ?
if (curr_pc != 0xAC13DBF8)
{
printf("Sh4: code cache clear (ICI) pc: %08X\n",curr_pc);
sh4_cpu.ResetCache();
}
}
}
temp.ICI=0;

View File

@ -495,6 +495,7 @@ void InitSettings()
settings.dreamcast.broadcast = 4; // default
settings.dreamcast.language = 6; // default
settings.dreamcast.FullMMU = false;
settings.dynarec.SmcCheckLevel = FullCheck;
settings.aica.LimitFPS = true;
settings.aica.NoBatch = false; // This also controls the DSP. Disabled by default
settings.aica.NoSound = false;
@ -563,6 +564,7 @@ void LoadSettings(bool game_specific)
settings.dynarec.idleskip = cfgLoadBool(config_section, "Dynarec.idleskip", settings.dynarec.idleskip);
settings.dynarec.unstable_opt = cfgLoadBool(config_section, "Dynarec.unstable-opt", settings.dynarec.unstable_opt);
settings.dynarec.safemode = cfgLoadBool(config_section, "Dynarec.safe-mode", settings.dynarec.safemode);
settings.dynarec.SmcCheckLevel = (SmcCheckEnum)cfgLoadInt(config_section, "Dynarec.SmcCheckLevel", settings.dynarec.SmcCheckLevel);
//disable_nvmem can't be loaded, because nvmem init is before cfg load
settings.dreamcast.cable = cfgLoadInt(config_section, "Dreamcast.Cable", settings.dreamcast.cable);
settings.dreamcast.region = cfgLoadInt(config_section, "Dreamcast.Region", settings.dreamcast.region);
@ -698,6 +700,8 @@ void SaveSettings()
cfgSaveBool("config", "Dynarec.unstable-opt", settings.dynarec.unstable_opt);
if (!safemode_game || !settings.dynarec.safemode)
cfgSaveBool("config", "Dynarec.safe-mode", settings.dynarec.safemode);
cfgSaveInt("config", "Dynarec.SmcCheckLevel", (int)settings.dynarec.SmcCheckLevel);
cfgSaveInt("config", "Dreamcast.Language", settings.dreamcast.language);
cfgSaveBool("config", "aica.LimitFPS", settings.aica.LimitFPS);
cfgSaveBool("config", "aica.NoBatch", settings.aica.NoBatch);

View File

@ -2082,7 +2082,7 @@ __default:
}
void ngen_Compile(RuntimeBlockInfo* block,bool force_checks, bool reset, bool staging,bool optimise)
void ngen_Compile(RuntimeBlockInfo* block, SmcCheckEnum smc_checks, bool reset, bool staging,bool optimise)
{
//printf("Compile: %08X, %d, %d\n",block->addr,staging,optimise);
block->code=(DynarecCodeEntryPtr)EMIT_GET_PTR();
@ -2114,39 +2114,68 @@ void ngen_Compile(RuntimeBlockInfo* block,bool force_checks, bool reset, bool st
reg.OpBegin(&block->oplist[0],0);
//scheduler
if (force_checks)
{
s32 sz = block->sh4_code_size;
u32 addr = block->addr;
MOV32(r0,addr);
switch (smc_checks) {
case NoCheck:
break;
while (sz > 0)
{
if (sz > 2)
case FastCheck: {
MOV32(r0,block->addr);
u32* ptr=(u32*)GetMemPtr(block->addr,4);
if (ptr != NULL)
{
u32* ptr=(u32*)GetMemPtr(addr,4);
MOV32(r2,(u32)ptr);
LDR(r2,r2,0);
MOV32(r1,*ptr);
CMP(r1,r2);
JUMP((u32)ngen_blockcheckfail, CC_NE);
addr += 4;
sz -= 4;
}
else
}
break;
case FullCheck: {
s32 sz = block->sh4_code_size;
u32 addr = block->addr;
MOV32(r0,addr);
while (sz > 0)
{
u16* ptr = (u16 *)GetMemPtr(addr, 2);
MOV32(r2, (u32)ptr);
LDRH(r2, r2, 0, AL);
MOVW(r1, *ptr, AL);
CMP(r1, r2);
if (sz > 2)
{
u32* ptr=(u32*)GetMemPtr(addr,4);
if (ptr != NULL)
{
MOV32(r2,(u32)ptr);
LDR(r2,r2,0);
MOV32(r1,*ptr);
CMP(r1,r2);
JUMP((u32)ngen_blockcheckfail, CC_NE);
addr += 2;
sz -= 2;
JUMP((u32)ngen_blockcheckfail, CC_NE);
}
addr += 4;
sz -= 4;
}
else
{
u16* ptr = (u16 *)GetMemPtr(addr, 2);
if (ptr != NULL)
{
MOV32(r2, (u32)ptr);
LDRH(r2, r2, 0, AL);
MOVW(r1, *ptr, AL);
CMP(r1, r2);
JUMP((u32)ngen_blockcheckfail, CC_NE);
}
addr += 2;
sz -= 2;
}
}
}
break;
default: {
die("unhandled smc_checks");
}
}
u32 cyc=block->guest_cycles;

View File

@ -348,15 +348,15 @@ public:
return *ret_reg;
}
void ngen_Compile(RuntimeBlockInfo* block, bool force_checks, bool reset, bool staging, bool optimise)
void ngen_Compile(RuntimeBlockInfo* block, SmcCheckEnum smc_checks, bool reset, bool staging, bool optimise)
{
//printf("REC-ARM64 compiling %08x\n", block->addr);
#ifdef PROFILING
SaveFramePointer();
#endif
this->block = block;
if (force_checks)
CheckBlock(block);
CheckBlock(smc_checks, block);
// run register allocator
regalloc.DoAlloc(block);
@ -1292,49 +1292,72 @@ private:
verify (GetCursorAddress<Instruction *>() - start_instruction == code_size * kInstructionSize);
}
void CheckBlock(RuntimeBlockInfo* block)
void CheckBlock(SmcCheckEnum smc_checks, RuntimeBlockInfo* block)
{
s32 sz = block->sh4_code_size;
Label blockcheck_fail;
Label blockcheck_success;
u8* ptr = GetMemPtr(block->addr, sz);
if (ptr == NULL)
// FIXME Can a block cross a RAM / non-RAM boundary??
return;
switch (smc_checks) {
case NoCheck:
return;
Ldr(x9, reinterpret_cast<uintptr_t>(ptr));
while (sz > 0)
{
if (sz >= 8)
{
Ldr(x10, MemOperand(x9, 8, PostIndex));
Ldr(x11, *(u64*)ptr);
Cmp(x10, x11);
sz -= 8;
ptr += 8;
}
else if (sz >= 4)
{
Ldr(w10, MemOperand(x9, 4, PostIndex));
case FastCheck: {
u8* ptr = GetMemPtr(block->addr, 4);
if (ptr == NULL)
return;
Ldr(x9, reinterpret_cast<uintptr_t>(ptr));
Ldr(w10, MemOperand(x9));
Ldr(w11, *(u32*)ptr);
Cmp(w10, w11);
sz -= 4;
ptr += 4;
B(eq, &blockcheck_success);
}
else
{
Ldrh(w10, MemOperand(x9, 2, PostIndex));
Mov(w11, *(u16*)ptr);
Cmp(w10, w11);
sz -= 2;
ptr += 2;
break;
case FullCheck: {
s32 sz = block->sh4_code_size;
u8* ptr = GetMemPtr(block->addr, sz);
if (ptr == NULL)
return;
Ldr(x9, reinterpret_cast<uintptr_t>(ptr));
while (sz > 0)
{
if (sz >= 8)
{
Ldr(x10, MemOperand(x9, 8, PostIndex));
Ldr(x11, *(u64*)ptr);
Cmp(x10, x11);
sz -= 8;
ptr += 8;
}
else if (sz >= 4)
{
Ldr(w10, MemOperand(x9, 4, PostIndex));
Ldr(w11, *(u32*)ptr);
Cmp(w10, w11);
sz -= 4;
ptr += 4;
}
else
{
Ldrh(w10, MemOperand(x9, 2, PostIndex));
Mov(w11, *(u16*)ptr);
Cmp(w10, w11);
sz -= 2;
ptr += 2;
}
B(ne, &blockcheck_fail);
}
B(&blockcheck_success);
}
B(ne, &blockcheck_fail);
break;
default:
die("unhandled smc_checks");
}
B(&blockcheck_success);
Bind(&blockcheck_fail);
Ldr(w0, block->addr);
@ -1404,13 +1427,13 @@ private:
static Arm64Assembler* compiler;
void ngen_Compile(RuntimeBlockInfo* block, bool force_checks, bool reset, bool staging, bool optimise)
void ngen_Compile(RuntimeBlockInfo* block, SmcCheckEnum smc_checks, bool reset, bool staging, bool optimise)
{
verify(emit_FreeSpace() >= 16 * 1024);
compiler = new Arm64Assembler();
compiler->ngen_Compile(block, force_checks, reset, staging, optimise);
compiler->ngen_Compile(block, smc_checks, reset, staging, optimise);
delete compiler;
compiler = NULL;

View File

@ -1190,10 +1190,10 @@ public:
size_t opcode_index;
opcodeExec** ptrsg;
void compile(RuntimeBlockInfo* block, bool force_checks, bool reset, bool staging, bool optimise) {
void compile(RuntimeBlockInfo* block, SmcCheckEnum smc_checks, bool reset, bool staging, bool optimise) {
//we need an extra one for the end opcode and optionally one more for block check
auto ptrs = fnnCtor_forreal(block->oplist.size() + 1 + (force_checks ? 1 : 0))(block->guest_cycles);
auto ptrs = fnnCtor_forreal(block->oplist.size() + 1 + (smc_checks != NoCheck ? 1 : 0))(block->guest_cycles);
ptrsg = ptrs.ptrs;
@ -1207,9 +1207,16 @@ public:
}
size_t i = 0;
if (force_checks)
if (smc_checks != NoCheck)
{
verify (smc_checks == FastCheck || smc_checks == FullCheck)
opcodeExec* op;
int check_size = block->sh4_code_size;
if (smc_checks == FastCheck) {
check_size = 4;
}
switch (block->sh4_code_size)
{
case 4:
@ -1227,6 +1234,7 @@ public:
}
ptrs.ptrs[i++] = op;
}
for (size_t opnum = 0; opnum < block->oplist.size(); opnum++, i++) {
opcode_index = i;
shil_opcode& op = block->oplist[opnum];
@ -1551,14 +1559,14 @@ public:
BlockCompiler* compiler;
void ngen_Compile(RuntimeBlockInfo* block, bool force_checks, bool reset, bool staging, bool optimise)
void ngen_Compile(RuntimeBlockInfo* block, SmcCheckEnum smc_checks, bool reset, bool staging, bool optimise)
{
verify(emit_FreeSpace() >= 16 * 1024);
compiler = new BlockCompiler();
compiler->compile(block, force_checks, reset, staging, optimise);
compiler->compile(block, smc_checks, reset, staging, optimise);
delete compiler;
}

View File

@ -239,12 +239,11 @@ public:
call_regsxmm.push_back(xmm3);
}
void compile(RuntimeBlockInfo* block, bool force_checks, bool reset, bool staging, bool optimise)
void compile(RuntimeBlockInfo* block, SmcCheckEnum smc_checks, bool reset, bool staging, bool optimise)
{
//printf("X86_64 compiling %08x to %p\n", block->addr, emit_GetCCPtr());
if (force_checks) {
CheckBlock(block);
}
CheckBlock(smc_checks, block);
regalloc.DoAlloc(block);
sub(dword[rip + &cycle_counter], block->guest_cycles);
@ -1086,41 +1085,66 @@ private:
typedef void (BlockCompiler::*X64BinaryOp)(const Xbyak::Operand&, const Xbyak::Operand&);
typedef void (BlockCompiler::*X64BinaryFOp)(const Xbyak::Xmm&, const Xbyak::Operand&);
void CheckBlock(RuntimeBlockInfo* block) {
mov(call_regs[0], block->addr);
void CheckBlock(SmcCheckEnum smc_checks, RuntimeBlockInfo* block) {
s32 sz=block->sh4_code_size;
u32 sa=block->addr;
switch (smc_checks) {
case NoCheck:
return;
while (sz > 0)
{
void* ptr = (void*)GetMemPtr(sa, sz > 8 ? 8 : sz);
if (ptr)
{
mov(rax, reinterpret_cast<uintptr_t>(ptr));
if (sz >= 8) {
mov(rdx, *(u64*)ptr);
cmp(qword[rax], rdx);
sz -= 8;
sa += 8;
}
else if (sz >= 4) {
case FastCheck: {
void* ptr = (void*)GetMemPtr(block->addr, 4);
if (ptr)
{
mov(call_regs[0], block->addr);
mov(rax, reinterpret_cast<uintptr_t>(ptr));
mov(edx, *(u32*)ptr);
cmp(dword[rax], edx);
sz -= 4;
sa += 4;
jne(reinterpret_cast<const void*>(&ngen_blockcheckfail));
}
else {
mov(edx, *(u16*)ptr);
cmp(word[rax],dx);
sz -= 2;
sa += 2;
}
jne(reinterpret_cast<const void*>(&ngen_blockcheckfail));
}
}
}
break;
case FullCheck: {
s32 sz=block->sh4_code_size;
u32 sa=block->addr;
void* ptr = (void*)GetMemPtr(sa, sz > 8 ? 8 : sz);
if (ptr)
{
mov(call_regs[0], block->addr);
while (sz > 0)
{
mov(rax, reinterpret_cast<uintptr_t>(ptr));
if (sz >= 8) {
mov(rdx, *(u64*)ptr);
cmp(qword[rax], rdx);
sz -= 8;
sa += 8;
}
else if (sz >= 4) {
mov(edx, *(u32*)ptr);
cmp(dword[rax], edx);
sz -= 4;
sa += 4;
}
else {
mov(edx, *(u16*)ptr);
cmp(word[rax],dx);
sz -= 2;
sa += 2;
}
jne(reinterpret_cast<const void*>(&ngen_blockcheckfail));
ptr = (void*)GetMemPtr(sa, sz > 8 ? 8 : sz);
}
}
}
break;
default:
die("unhandled smc_checks");
}
}
void GenBinaryOp(const shil_opcode &op, X64BinaryOp natop)
@ -1262,13 +1286,13 @@ void X64RegAlloc::Writeback_FPU(u32 reg, s8 nreg)
static BlockCompiler* compiler;
void ngen_Compile(RuntimeBlockInfo* block, bool force_checks, bool reset, bool staging, bool optimise)
void ngen_Compile(RuntimeBlockInfo* block, SmcCheckEnum smc_checks, bool reset, bool staging, bool optimise)
{
verify(emit_FreeSpace() >= 16 * 1024);
compiler = new BlockCompiler();
compiler->compile(block, force_checks, reset, staging, optimise);
compiler->compile(block, smc_checks, reset, staging, optimise);
delete compiler;
}

View File

@ -229,29 +229,50 @@ u32 rdmt[6];
extern u32 memops_t,memops_l;
extern int mips_counter;
void CheckBlock(RuntimeBlockInfo* block,x86_ptr_imm place)
//TODO: Get back validating mode for this
void CheckBlock(SmcCheckEnum smc_checks, RuntimeBlockInfo* block)
{
s32 sz=block->sh4_code_size;
u32 sa=block->addr;
while(sz>0)
{
void* ptr=(void*)GetMemPtr(sa,4);
if (ptr)
{
if (sz==2)
x86e->Emit(op_cmp16,ptr,*(u16*)ptr);
else
x86e->Emit(op_cmp32,ptr,*(u32*)ptr);
x86e->Emit(op_jne,place);
switch (smc_checks) {
case NoCheck:
break;
case FastCheck: {
void* ptr = (void*)GetMemPtr(block->addr, 4);
if (ptr)
{
x86e->Emit(op_cmp32, ptr, *(u32*)ptr);
x86e->Emit(op_jne, x86_ptr_imm(ngen_blockcheckfail));
}
}
sz-=4;
sa+=4;
break;
case FullCheck: {
s32 sz=block->sh4_code_size;
u32 sa=block->addr;
while(sz>0)
{
void* ptr=(void*)GetMemPtr(sa,4);
if (ptr)
{
if (sz==2)
x86e->Emit(op_cmp16,ptr,*(u16*)ptr);
else
x86e->Emit(op_cmp32,ptr,*(u32*)ptr);
x86e->Emit(op_jne,x86_ptr_imm(ngen_blockcheckfail));
}
sz-=4;
sa+=4;
}
}
break;
default:
die("unhandled smc_checks");
}
}
void ngen_Compile(RuntimeBlockInfo* block,bool force_checks, bool reset, bool staging,bool optimise)
void ngen_Compile(RuntimeBlockInfo* block, SmcCheckEnum smc_checks, bool reset, bool staging,bool optimise)
{
//initialise stuff
DetectCpuFeatures();
@ -282,7 +303,7 @@ void ngen_Compile(RuntimeBlockInfo* block,bool force_checks, bool reset, bool st
//block invl. checks
x86e->Emit(op_mov32,ECX,block->addr);
CheckBlock(block,force_checks?x86_ptr_imm(ngen_blockcheckfail):x86_ptr_imm(ngen_blockcheckfail2));
CheckBlock(smc_checks, block);
//Scheduler
x86_Label* no_up=x86e->CreateLabel(false,8);

View File

@ -1017,10 +1017,33 @@ static void gui_display_settings()
ShowHelpMarker("Do not optimize integer division. Recommended");
ImGui::Checkbox("Unstable Optimizations", &settings.dynarec.unstable_opt);
ImGui::SameLine();
ShowHelpMarker("Enable unsafe optimizations. May cause crash or environmental disaster");
ShowHelpMarker("Enable unsafe optimizations. Will cause crash or environmental disaster");
ImGui::Checkbox("Idle Skip", &settings.dynarec.idleskip);
ImGui::SameLine();
ShowHelpMarker("Skip wait loops. Recommended");
ImGui::PushItemWidth(ImGui::CalcTextSize("Largeenough").x);
const char *preview = settings.dynarec.SmcCheckLevel == NoCheck ? "Faster" : settings.dynarec.SmcCheckLevel == FastCheck ? "Fast" : "Full";
if (ImGui::BeginCombo("SMC Checks", preview , ImGuiComboFlags_None))
{
bool is_selected = settings.dynarec.SmcCheckLevel == NoCheck;
if (ImGui::Selectable("Faster", &is_selected))
settings.dynarec.SmcCheckLevel = NoCheck;
if (is_selected)
ImGui::SetItemDefaultFocus();
is_selected = settings.dynarec.SmcCheckLevel == FastCheck;
if (ImGui::Selectable("Fast", &is_selected))
settings.dynarec.SmcCheckLevel = FastCheck;
if (is_selected)
ImGui::SetItemDefaultFocus();
is_selected = settings.dynarec.SmcCheckLevel == FullCheck;
if (ImGui::Selectable("Full", &is_selected))
settings.dynarec.SmcCheckLevel = FullCheck;
if (is_selected)
ImGui::SetItemDefaultFocus();
ImGui::EndCombo();
}
ImGui::SameLine();
ShowHelpMarker("How to detect self-modifying code. Full check recommended");
}
if (ImGui::CollapsingHeader("Other", ImGuiTreeNodeFlags_DefaultOpen))
{

View File

@ -601,6 +601,11 @@ struct RegisterStruct
u32 flags; //Access flags !
};
enum SmcCheckEnum {
FullCheck = 0,
FastCheck = 1,
NoCheck = 2
};
struct settings_t
{
@ -637,6 +642,7 @@ struct settings_t
bool unstable_opt;
bool safemode;
bool disable_nvmem;
SmcCheckEnum SmcCheckLevel;
} dynarec;
struct