daedalus/Source/Core/Dynamo.cpp
2021-10-18 18:58:14 +11:00

599 lines
18 KiB
C++

/*
Copyright (C) 2009 StrmnNrmn
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <algorithm>
#include <stdio.h>
#include "stdafx.h"
#include "Core/Dynamo.h"
#include "Core/CPU.h"
// For REG_?? defines
#include "Core/Memory.h"
#include "Core/Interrupt.h"
#include "Core/R4300.h"
#include "Core/Registers.h"
#include "Config/ConfigOptions.h"
#include "Debug/DBGConsole.h"
#include "Debug/DebugLog.h"
#include "DynaRec/DynaRecProfile.h"
#include "DynaRec/Fragment.h"
#include "DynaRec/FragmentCache.h"
#include "DynaRec/TraceRecorder.h"
#include "OSHLE/patch.h" // GetCorrectOp
#include "Ultra/ultra_R4300.h"
#include "System/IO.h"
#include "Base/Macros.h"
#include "Utility/Profiler.h"
#include "Debug/Synchroniser.h"
#ifdef DAEDALUS_ENABLE_DYNAREC
// These values are very sensitive to change in some games so be carefull!!! //Corn
// War God is sensitive to gHotTraceThreshold
// PD is sensitive to gMaxHotTraceMapSize
//
// Banjo Tooie needs a larger cache size
// BUT leave PSP cache size untouched for now
#ifdef DAEDALUS_PSP
#define TRACE_SIZE 512
#else
#define TRACE_SIZE 1024
#endif
static const u32 gMaxFragmentCacheSize = (8192 + 1024); //Maximum amount of fragments in the cache
static const u32 gMaxHotTraceMapSize = (2048 + TRACE_SIZE);
static const u32 gHotTraceThreshold = 10; //How many times interpreter has to loop a trace before it becomes hot and sent to dynarec
//std::map< u32, u32, std::less<u32>, MyAllocator > gHotTraceCountMap;
//std::map< u32, u32, std::less<u32>, boost::pool_allocator<std::pair< const u32, u32 > > > gHotTraceCountMap;
std::map< u32, u32 > gHotTraceCountMap {};
CFragmentCache gFragmentCache {};
static bool gResetFragmentCache {false};
#ifdef DAEDALUS_DEBUG_DYNAREC
std::map< u32, u32 > gAbortedTraceReasons;
void CPU_DumpFragmentCache();
#endif
static void CPU_HandleDynaRecOnBranch( bool backwards, bool trace_already_enabled );
static void CPU_UpdateTrace( u32 address, OpCode op_code, bool branch_delay_slot, bool branch_taken );
static void CPU_CreateAndAddFragment();
#ifdef DAEDALUS_PROFILE_EXECUTION
u32 gFragmentLookupFailure {};
u32 gFragmentLookupSuccess {};
#endif
//*****************************************************************************
// Indicate that the instruction cache is invalid
// (we have to dump the dynarec contents and start over, but this is
// better than crashing :) )
//*****************************************************************************
void R4300_CALL_TYPE CPU_InvalidateICache()
{
CPU_ResetFragmentCache();
}
//*****************************************************************************
//
//*****************************************************************************
void CPU_DynarecEnable()
{
gDynarecEnabled = true;
gCPUState.AddJob(CPU_CHANGE_CORE);
}
//*****************************************************************************
// If fragments overlap dynarec has to start all over which is very costly
//*****************************************************************************
void R4300_CALL_TYPE CPU_InvalidateICacheRange( u32 address, u32 length )
{
if( gFragmentCache.ShouldInvalidateOnWrite( address, length ) )
{
#ifndef DAEDALUS_SILENT
printf( "Write to %08x (%d bytes) overlaps fragment cache entries\n", address, length );
#endif
CPU_ResetFragmentCache();
}
}
//*****************************************************************************
// Execute a single MIPS op. The conditionals for the templated arguments
// are completely optimised away by the compiler.
//
// DynaRec: Run this function with dynarec enabled
// TranslateOp: Use this to translate breakpoints/patches to original op
// before execution.
//*****************************************************************************
template< bool TraceEnabled > DAEDALUS_FORCEINLINE void CPU_EXECUTE_OP()
{
u8 * p_Instruction = 0;
CPU_FETCH_INSTRUCTION( p_Instruction, gCPUState.CurrentPC );
OpCode op_code = *(OpCode*)p_Instruction;
// Cache instruction base pointer (used for SpeedHack() @ R4300.0)
gLastAddress = p_Instruction;
#ifdef DAEDALUS_BREAKPOINTS_ENABLED
op_code = GetCorrectOp( op_code );
#endif
#ifdef DAEDALUS_ENABLE_SYNCHRONISATION
SYNCH_POINT( DAED_SYNC_REG_PC, gCPUState.CurrentPC, "Program Counter doesn't match" );
SYNCH_POINT( DAED_SYNC_FRAGMENT_PC, gCPUState.CurrentPC + gCPUState.Delay, "Program Counter/Delay doesn't match while interpreting" );
SYNCH_POINT( DAED_SYNC_REG_PC, gCPUState.CPUControl[C0_COUNT]._u32, "Count doesn't match" );
#endif
if( TraceEnabled )
{
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT( gTraceRecorder.IsTraceActive(), "If TraceEnabled is set, trace should be active" );
#endif
u32 pc( gCPUState.CurrentPC ) ;
bool branch_delay_slot( gCPUState.Delay == EXEC_DELAY );
R4300_ExecuteInstruction(op_code);
gGPR[0]._u64 = 0; //Ensure r0 is zero
bool branch_taken( gCPUState.Delay == DO_DELAY );
CPU_UpdateTrace( pc, op_code, branch_delay_slot, branch_taken );
}
else
{
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT( !gTraceRecorder.IsTraceActive(), "If TraceEnabled is not set, trace should be inactive" );
#endif
R4300_ExecuteInstruction(op_code);
gGPR[0]._u64 = 0; //Ensure r0 is zero
#ifdef DAEDALUS_PROFILE_EXECUTION
gTotalInstructionsEmulated++;
#endif
}
#ifdef DAEDALUS_ENABLE_SYNCHRONISATION
SYNCH_POINT( DAED_SYNC_REGS, CPU_ProduceRegisterHash(), "Registers don't match" );
#endif
// Increment count register
gCPUState.CPUControl[C0_COUNT]._u32 = gCPUState.CPUControl[C0_COUNT]._u32 + COUNTER_INCREMENT_PER_OP;
if (CPU_ProcessEventCycles( COUNTER_INCREMENT_PER_OP ) )
{
CPU_HANDLE_COUNT_INTERRUPT();
}
switch (gCPUState.Delay)
{
case DO_DELAY:
// We've got a delayed instruction to execute. Increment
// PC as normal, so that subsequent instruction is executed
INCREMENT_PC();
gCPUState.Delay = EXEC_DELAY;
break;
case EXEC_DELAY:
{
bool backwards( gCPUState.TargetPC <= gCPUState.CurrentPC );
// We've just executed the delayed instr. Now carry out jump as stored in gCPUState.TargetPC;
CPU_SetPC(gCPUState.TargetPC);
gCPUState.Delay = NO_DELAY;
CPU_HandleDynaRecOnBranch( backwards, TraceEnabled );
}
break;
case NO_DELAY:
// Normal operation - just increment the PC
INCREMENT_PC();
break;
default:
NODEFAULT;
}
}
//*****************************************************************************
//
//*****************************************************************************
void CPU_ResetFragmentCache()
{
// Need to make sure this happens at a safe point, so we use a flag
gResetFragmentCache = true;
}
//*****************************************************************************
// Keep executing instructions until there are other tasks to do (i.e. gCPUState.GetStuffToDo() is set)
// Process these tasks and loop
//*****************************************************************************
template < bool DynaRec, bool TraceEnabled > void CPU_Go()
{
DAEDALUS_PROFILE( __FUNCTION__ );
while (CPU_KeepRunning())
{
//
// Keep executing ops as long as there's nothing to do
//
u32 stuff_to_do( gCPUState.GetStuffToDo() );
while(stuff_to_do == 0)
{
CPU_EXECUTE_OP< TraceEnabled >();
stuff_to_do = gCPUState.GetStuffToDo();
}
if( TraceEnabled && (stuff_to_do != CPU_CHANGE_CORE) )
{
if(gTraceRecorder.IsTraceActive())
{
#ifdef DAEDALUS_DEBUG_DYNAREC
u32 start_address( gTraceRecorder.GetStartTraceAddress() );
//DBGConsole_Msg( 0, "Aborting tracing of [R%08x] - StuffToDo is %08x", start_address, stuff_to_do );
gAbortedTraceReasons[ start_address ] = stuff_to_do;
#endif
#ifdef ALLOW_TRACES_WHICH_EXCEPT
if(stuff_to_do == CPU_CHECK_INTERRUPTS && gCPUState.Delay == NO_DELAY ) // Note checking for exactly equal, not just that it's set
{
//DBGConsole_Msg( 0, "Adding chunk at %08x after interrupt\n", gTraceRecorder.GetStartTraceAddress() );
gTraceRecorder.StopTrace( gCPUState.CurrentPC );
CPU_CreateAndAddFragment();
}
#endif
gTraceRecorder.AbortTrace(); // Abort any traces that were terminated through an interrupt etc
}
CPU_SelectCore();
}
if (CPU_CheckStuffToDo())
break;
}
}
#ifdef DAEDALUS_DEBUG_DYNAREC
struct SAddressHitCount
{
u32 Address = 0;
u32 HitCount = 0;
SAddressHitCount( u32 address, u32 hitcount ) : Address( address ), HitCount( hitcount ) {}
u32 GetAbortReason() const
{
std::map<u32, u32>::const_iterator it( gAbortedTraceReasons.find( Address ) );
if( it != gAbortedTraceReasons.end() )
{
return it->second;
}
return 0;
}
};
bool SortByHitCount( const SAddressHitCount & a, const SAddressHitCount & b )
{
return a.HitCount > b.HitCount;
}
//*****************************************************************************
//
//*****************************************************************************
void CPU_DumpFragmentCache()
{
IO::Directory::EnsureExists( "DynarecDump" );
FILE * fh( fopen( "DynarecDump/hot_trace_map.html", "w" ) );
if( fh != nullptr )
{
std::vector< SAddressHitCount > hit_counts;
hit_counts.reserve( gHotTraceCountMap.size() );
for(std::map<u32,u32>::const_iterator it = gHotTraceCountMap.begin(); it != gHotTraceCountMap.end(); ++it )
{
hit_counts.push_back( SAddressHitCount( it->first, it->second ) );
}
std::sort( hit_counts.begin(), hit_counts.end(), SortByHitCount );
fputs( "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">", fh );
fputs( "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n", fh );
fputs( "<head><title>Hot Trace Map</title>\n", fh );
fputs( "<link rel=\"stylesheet\" href=\"default.css\" type=\"text/css\" media=\"all\" />\n", fh );
fputs( "</head><body>\n", fh );
fputs( "<h1>Hot Trace Map</h1>\n", fh );
fputs( "<div align=\"center\"><table>\n", fh );
fputs( "<tr><th>Address</th><th>Hit Count</th><th>Abort Reason</th></tr>\n", fh );
for( u32 i = 0; i < hit_counts.size(); ++i )
{
const SAddressHitCount & info( hit_counts[ i ] );
u32 abort_reason( info.GetAbortReason() );
fprintf( fh, "<tr><td>%08x</td><td>%d</td>\n", info.Address, info.HitCount );
fputs( "<td>", fh );
if(abort_reason & CPU_CHECK_EXCEPTIONS) { fputs( " Exception", fh ); }
if(abort_reason & CPU_CHECK_INTERRUPTS) { fputs( " Interrupt", fh ); }
if(abort_reason & CPU_STOP_RUNNING) { fputs( " StopRunning", fh ); }
if(abort_reason & CPU_CHANGE_CORE) { fputs( " ChangeCore", fh ); }
fputs( "</td></tr>\n", fh );
//if( info.HitCount >= gHotTraceThreshold )
}
fputs( "</table></div>\n", fh );
fputs( "</body></html>\n", fh );
fclose(fh);
}
gFragmentCache.DumpStats( "DynarecDump/" );
}
#endif
//*****************************************************************************
//
//*****************************************************************************
void CPU_CreateAndAddFragment()
{
CFragment * p_fragment( gTraceRecorder.CreateFragment( gFragmentCache.GetCodeBufferManager() ) );
if( p_fragment != nullptr )
{
gHotTraceCountMap.erase( p_fragment->GetEntryAddress() );
gFragmentCache.InsertFragment( p_fragment );
//DBGConsole_Msg( 0, "Inserted hot trace at [R%08x]! (size is %d. %dKB)", p_fragment->GetEntryAddress(), gFragmentCache.GetCacheSize(), gFragmentCache.GetMemoryUsage() / 1024 );
}
}
//*****************************************************************************
//
//*****************************************************************************
void CPU_UpdateTrace( u32 address, OpCode op_code, bool branch_delay_slot, bool branch_taken )
{
#ifdef DAEDALUS_PROFILE
DAEDALUS_PROFILE( "CPU_UpdateTrace" );
#endif
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT_Q( (gCPUState.Delay == EXEC_DELAY) == branch_delay_slot );
#endif
#ifdef DAEDALUS_DEBUG_DYNAREC
CFragment * p_address_fragment( gFragmentCache.LookupFragment( address ) );
#else
CFragment * p_address_fragment( gFragmentCache.LookupFragmentQ( address ) );
#endif
if( gTraceRecorder.UpdateTrace( address, branch_delay_slot, branch_taken, op_code, p_address_fragment ) == CTraceRecorder::UTS_CREATE_FRAGMENT )
{
CPU_CreateAndAddFragment();
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT( !gTraceRecorder.IsTraceActive(), "Why is a trace still active?" );
#endif
CPU_SelectCore();
}
#ifdef DAEDALUS_ENABLE_ASSERTS
else
{
DAEDALUS_ASSERT( gTraceRecorder.IsTraceActive(), "The trace should still be enabled" );
}
#endif
}
//*****************************************************************************
//
//*****************************************************************************
void CPU_HandleDynaRecOnBranch( bool backwards, bool trace_already_enabled )
{
#ifdef DAEDALUS_PROFILE
DAEDALUS_PROFILE( "CPU_HandleDynaRecOnBranch" );
#endif
bool start_of_trace( false );
if( backwards )
{
start_of_trace = true;
}
bool change_core( false );
#ifdef DAEDALUS_LOG
DAED_LOG( DEBUG_DYNAREC_CACHE, "CPU_HandleDynaRecOnBranch" );
#endif
while( gCPUState.GetStuffToDo() == 0 && gCPUState.Delay == NO_DELAY )
{
#ifdef DAEDALUS_ENABLE_DYNAREC_PROFILE
DAEDALUS_ASSERT( gCPUState.Delay == NO_DELAY, "Why are we entering with a delay slot active?" );
u32 entry_count( gCPUState.CPUControl[C0_COUNT]._u32 ); // Just used DYNAREC_PROFILE_ENTEREXIT
#endif
u32 entry_address( gCPUState.CurrentPC );
#ifdef DAEDALUS_DEBUG_DYNAREC
CFragment * p_fragment( gFragmentCache.LookupFragment( entry_address ) );
#else
CFragment * p_fragment( gFragmentCache.LookupFragmentQ( entry_address ) );
#endif
if( p_fragment != nullptr )
{
#ifdef DAEDALUS_PROFILE_EXECUTION
gFragmentLookupSuccess++;
#endif
// Check if another trace is active and we're about to enter
if( gTraceRecorder.IsTraceActive() )
{
gTraceRecorder.StopTrace( gCPUState.CurrentPC );
CPU_CreateAndAddFragment();
// We need to change the core when exiting
change_core = true;
}
p_fragment->Execute();
DYNAREC_PROFILE_ENTEREXIT( entry_address, gCPUState.CurrentPC, gCPUState.CPUControl[C0_COUNT]._u32 - entry_count );
start_of_trace = true;
}
else
{
#ifdef DAEDALUS_PROFILE_EXECUTION
gFragmentLookupFailure++;
#endif
if( start_of_trace )
{
start_of_trace = false;
if( !gTraceRecorder.IsTraceActive() )
{
if (gResetFragmentCache)
{
#ifdef DAEDALUS_ENABLE_OS_HOOKS
//Don't reset the cache if there is no fragment except OSHLE function stubs
if (gFragmentCache.GetCacheSize() >= gNumOfOSFunctions)
#else
if(true)
#endif
{
gFragmentCache.Clear();
gHotTraceCountMap.clear(); // Makes sense to clear this now, to get accurate usage stats
#ifdef DAEDALUS_ENABLE_OS_HOOKS
Patch_PatchAll();
#endif
}
#ifdef DAEDALUS_DEBUG_CONSOLE
else
{
DBGConsole_Msg(0, "Safely skipped one flush");
}
#endif
gResetFragmentCache = false;
}
if( gFragmentCache.GetCacheSize() > gMaxFragmentCacheSize)
{
gFragmentCache.Clear();
gHotTraceCountMap.clear(); // Makes sense to clear this now, to get accurate usage stats
#ifdef DAEDALUS_ENABLE_OS_HOOKS
Patch_PatchAll();
#endif
}
// If there is no fragment for this target, start tracing
u32 trace_count( ++gHotTraceCountMap[ gCPUState.CurrentPC ] );
if( gHotTraceCountMap.size() >= gMaxHotTraceMapSize )
{
#ifdef DAEDALUS_DEBUG_CONSOLE
DBGConsole_Msg( 0, "Hot trace cache hit %d, dumping", gHotTraceCountMap.size() );
#endif
gHotTraceCountMap.clear();
gFragmentCache.Clear();
#ifdef DAEDALUS_ENABLE_OS_HOOKS
Patch_PatchAll();
#endif
}
else if( trace_count == gHotTraceThreshold )
{
//DBGConsole_Msg( 0, "Identified hot trace at [R%08x]! (size is %d)", gCPUState.CurrentPC, gHotTraceCountMap.size() );
gTraceRecorder.StartTrace( gCPUState.CurrentPC );
if(!trace_already_enabled)
{
change_core = true;
}
DAED_LOG( DEBUG_DYNAREC_CACHE, "StartTrace( %08x )", gCPUState.CurrentPC );
}
#ifdef DAEDALUS_DEBUG_DYNAREC
else if( trace_count > gHotTraceThreshold )
{
if(gAbortedTraceReasons.find( gCPUState.CurrentPC ) != gAbortedTraceReasons.end() )
{
u32 reason( gAbortedTraceReasons[ gCPUState.CurrentPC ] );
use( reason );
//DBGConsole_Msg( 0, "Hot trace at [R%08x] has count of %d! (reason is %x) size %d", gCPUState.CurrentPC, trace_count, reason, gHotTraceCountMap.size( ) );
DAED_LOG( DEBUG_DYNAREC_CACHE, "Hot trace at %08x has count of %d! (reason is %x) size %d", gCPUState.CurrentPC, trace_count, reason, gHotTraceCountMap.size( ) );
}
else
{
DAED_LOG( DEBUG_DYNAREC_CACHE, "Hot trace at %08x has count of %d! (reason is UNKNOWN!)", gCPUState.CurrentPC, trace_count );
}
}
#endif //DAEDALUS_DEBUG_DYNAREC
}
}
#ifdef DAEDALUS_LOG
else
{
DAED_LOG( DEBUG_DYNAREC_CACHE, "Not start of trace" );
}
#endif
break;
}
}
if(change_core)
{
CPU_SelectCore();
}
}
void Dynamo_Reset()
{
gHotTraceCountMap.clear();
gFragmentCache.Clear();
gResetFragmentCache = false;
gTraceRecorder.AbortTrace();
#ifdef DAEDALUS_DEBUG_DYNAREC
gAbortedTraceReasons.clear();
#endif
}
void Dynamo_SelectCore()
{
bool trace_enabled = gTraceRecorder.IsTraceActive();
if (trace_enabled)
{
g_pCPUCore = CPU_Go< true, true >;
}
else
{
g_pCPUCore = CPU_Go< true, false >;
}
}
#else
void CPU_ResetFragmentCache() {}
void Dynamo_Reset() {}
void R4300_CALL_TYPE CPU_InvalidateICacheRange( u32 address, u32 length ) {}
#endif //DAEDALUS_ENABLE_DYNAREC