mirror of
https://github.com/DaedalusX64/daedalus.git
synced 2025-04-02 10:21:48 -04:00
451 lines
14 KiB
C++
451 lines
14 KiB
C++
/*
|
|
Copyright (C) 2006 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 "Base/Types.h"
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
|
|
#include "DynaRec/AssemblyUtils.h"
|
|
#include "DynaRec/CodeBufferManager.h"
|
|
#include "DynaRec/DynaRecProfile.h"
|
|
#include "DynaRec/Fragment.h"
|
|
#include "DynaRec/FragmentCache.h"
|
|
#include "Debug/DBGConsole.h"
|
|
#include "Utility/Profiler.h"
|
|
|
|
|
|
//Define to show hash table statistics cache hit/miss
|
|
//#define HASH_TABLE_STATS
|
|
|
|
using namespace AssemblyUtils;
|
|
|
|
//*************************************************************************************
|
|
//
|
|
//*************************************************************************************
|
|
CFragmentCache::CFragmentCache()
|
|
: mMemoryUsage( 0 )
|
|
, mInputLength( 0 )
|
|
, mOutputLength( 0 )
|
|
, mCachedFragmentAddress( 0 )
|
|
, mpCachedFragment( nullptr )
|
|
{
|
|
std::memset( mpCacheHashTable.data(), 0, mpCacheHashTable.size() * sizeof(mpCacheHashTable[0]));
|
|
|
|
mFragments.reserve( 2000 );
|
|
|
|
mpCodeBufferManager = CCodeBufferManager::Create();
|
|
if(mpCodeBufferManager != nullptr)
|
|
{
|
|
mpCodeBufferManager->Initialise();
|
|
}
|
|
}
|
|
|
|
//*************************************************************************************
|
|
//
|
|
//*************************************************************************************
|
|
CFragmentCache::~CFragmentCache()
|
|
{
|
|
Clear();
|
|
|
|
mpCodeBufferManager->Finalise();
|
|
}
|
|
|
|
//*************************************************************************************
|
|
//
|
|
//*************************************************************************************
|
|
#ifdef DAEDALUS_DEBUG_DYNAREC
|
|
CFragment * CFragmentCache::LookupFragment( u32 address ) const
|
|
{
|
|
DAEDALUS_PROFILE( "CFragmentCache::LookupFragment" );
|
|
|
|
if( address != mCachedFragmentAddress )
|
|
{
|
|
mCachedFragmentAddress = address;
|
|
|
|
// check if in hash table
|
|
u32 ix = MakeHashIdx( address );
|
|
|
|
if ( address != mpCacheHashTable[ix].addr )
|
|
{
|
|
SFragmentEntry entry( address, nullptr );
|
|
FragmentVec::const_iterator it( std::lower_bound( mFragments.begin(), mFragments.end(), entry ) );
|
|
if( it != mFragments.end() && it->Address == address )
|
|
{
|
|
mpCachedFragment = it->Fragment;
|
|
}
|
|
else
|
|
{
|
|
mpCachedFragment = nullptr;
|
|
}
|
|
|
|
// put in hash table
|
|
mpCacheHashTable[ix].addr = address;
|
|
mpCacheHashTable[ix].ptr = (uintptr_t)mpCachedFragment;
|
|
}
|
|
else
|
|
{
|
|
mpCachedFragment = (CFragment *)mpCacheHashTable[ix].ptr;
|
|
}
|
|
}
|
|
|
|
CFragment * p = mpCachedFragment;
|
|
|
|
DYNAREC_PROFILE_LOGLOOKUP( address, p );
|
|
|
|
return p;
|
|
}
|
|
#endif
|
|
//*************************************************************************************
|
|
//
|
|
//*************************************************************************************
|
|
CFragment * CFragmentCache::LookupFragmentQ( u32 address ) const
|
|
{
|
|
#ifdef DAEDALUS_DEBUG_DYNAREC
|
|
DAEDALUS_PROFILE( "CFragmentCache::LookupFragmentQ" );
|
|
#endif
|
|
#ifdef HASH_TABLE_STATS
|
|
static u32 hit=0, miss=0;
|
|
#endif
|
|
if( address != mCachedFragmentAddress )
|
|
{
|
|
mCachedFragmentAddress = address;
|
|
|
|
// check if in hash table
|
|
u32 ix = MakeHashIdx( address );
|
|
|
|
if ( address != mpCacheHashTable[ix].addr )
|
|
{
|
|
#ifdef HASH_TABLE_STATS
|
|
miss++;
|
|
#endif
|
|
SFragmentEntry entry( address, nullptr );
|
|
FragmentVec::const_iterator it( std::lower_bound( mFragments.begin(), mFragments.end(), entry ) );
|
|
if( it != mFragments.end() && it->Address == address )
|
|
{
|
|
mpCachedFragment = it->Fragment;
|
|
}
|
|
else
|
|
{
|
|
mpCachedFragment = nullptr;
|
|
}
|
|
|
|
// put in hash table
|
|
mpCacheHashTable[ix].addr = address;
|
|
mpCacheHashTable[ix].ptr = reinterpret_cast< uintptr_t >( mpCachedFragment );
|
|
}
|
|
else
|
|
{
|
|
#ifdef HASH_TABLE_STATS
|
|
hit++;
|
|
#endif
|
|
mpCachedFragment = reinterpret_cast< CFragment * >( mpCacheHashTable[ix].ptr );
|
|
}
|
|
|
|
#ifdef HASH_TABLE_STATS
|
|
if(miss+hit==10000)
|
|
{
|
|
printf("Hit[%d] Miss[%d]\n", hit, miss);
|
|
miss=hit=0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return mpCachedFragment;
|
|
}
|
|
|
|
//*************************************************************************************
|
|
//
|
|
//*************************************************************************************
|
|
void CFragmentCache::InsertFragment( CFragment * p_fragment )
|
|
{
|
|
u32 fragment_address( p_fragment->GetEntryAddress() );
|
|
|
|
mCacheCoverage.ExtendCoverage( fragment_address, p_fragment->GetInputLength() );
|
|
|
|
SFragmentEntry entry( fragment_address, nullptr );
|
|
FragmentVec::iterator it( std::lower_bound( mFragments.begin(), mFragments.end(), entry ) );
|
|
#ifdef DAEDALUS_ENABLE_ASSERTS
|
|
DAEDALUS_ASSERT( it == mFragments.end() || it->Address != fragment_address, "A fragment with this address already exists" );
|
|
#endif
|
|
entry.Fragment = p_fragment;
|
|
mFragments.insert( it, entry );
|
|
|
|
// Update the hash table (it stores failed lookups now, so we need to be sure to purge any stale entries in there
|
|
u32 ix {MakeHashIdx( fragment_address )};
|
|
mpCacheHashTable[ix].addr = fragment_address;
|
|
mpCacheHashTable[ix].ptr = reinterpret_cast< uintptr_t >( p_fragment );
|
|
|
|
// Process any jumps for this before inserting new ones
|
|
JumpMap::iterator jump_it( mJumpMap.find( fragment_address ) );
|
|
if( jump_it != mJumpMap.end() )
|
|
{
|
|
const JumpList & jumps( jump_it->second );
|
|
for( JumpList::const_iterator it = jumps.begin(); it != jumps.end(); ++it )
|
|
{
|
|
//DBGConsole_Msg( 0, "Inserting [R%08x], patching jump at %08x ", address, (*it) );
|
|
PatchJumpLongAndFlush( (*it), p_fragment->GetEntryTarget() );
|
|
}
|
|
|
|
// All patched - clear
|
|
mJumpMap.erase( jump_it );
|
|
}
|
|
|
|
// Finally register any links that this fragment may have
|
|
const FragmentPatchList & patch_list( p_fragment->GetPatchList() );
|
|
for( FragmentPatchList::const_iterator it = patch_list.begin(); it != patch_list.end(); ++it )
|
|
{
|
|
u32 target_address( it->Address );
|
|
CJumpLocation jump( it->Jump );
|
|
|
|
#ifdef DAEDALUS_ENABLE_ASSERTS
|
|
DAEDALUS_ASSERT( jump.IsSet(), "No exit jump?" );
|
|
#endif
|
|
|
|
#ifdef DAEDALUS_DEBUG_DYNAREC
|
|
CFragment * p_fragment( LookupFragment( target_address ) );
|
|
#else
|
|
CFragment * p_fragment( LookupFragmentQ( target_address ) );
|
|
#endif
|
|
if( p_fragment != nullptr )
|
|
{
|
|
PatchJumpLongAndFlush( jump, p_fragment->GetEntryTarget() );
|
|
|
|
#ifdef DAEDALUS_ENABLE_ASSERTS
|
|
DAEDALUS_ASSERT( mJumpMap.find( target_address ) == mJumpMap.end(), "Jump map still contains an entry for this" );
|
|
#endif
|
|
}
|
|
else if( target_address != u32(~0) )
|
|
{
|
|
// Store the address for later processing
|
|
mJumpMap[ target_address ].push_back( jump );
|
|
}
|
|
}
|
|
|
|
// Free memoire
|
|
p_fragment->DiscardPatchList();
|
|
|
|
// For simulation only
|
|
p_fragment->SetCache( this );
|
|
|
|
// Update memory usage etc after discarding patch list
|
|
mMemoryUsage += p_fragment->GetMemoryUsage();
|
|
mInputLength += p_fragment->GetInputLength();
|
|
mOutputLength += p_fragment->GetOutputLength();
|
|
|
|
#ifdef DAEDALUS_DEBUG_CONSOLE
|
|
if((mFragments.size() % 100) == 0)
|
|
{
|
|
u32 expansion = 1;
|
|
if(mInputLength > 0)
|
|
{
|
|
expansion = (100 * mOutputLength) / mInputLength;
|
|
}
|
|
DBGConsole_Msg( 0, "Dynarec: %d fragments, %dKB (i:o %d:%d = %d%%)",
|
|
mFragments.size(), mMemoryUsage / 1024, mInputLength / 1024, mOutputLength / 1024, expansion );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//*************************************************************************************
|
|
//
|
|
//*************************************************************************************
|
|
void CFragmentCache::Clear()
|
|
{
|
|
#ifdef DAEDALUS_DEBUG_CONSOLE
|
|
if(CDebugConsole::IsAvailable())
|
|
{
|
|
DBGConsole_Msg( 0, "Clearing fragment cache of %d fragments", mFragments.size() );
|
|
}
|
|
#endif
|
|
// Clear out all the framents
|
|
for(FragmentVec::iterator it = mFragments.begin(); it != mFragments.end(); ++it)
|
|
{
|
|
delete it->Fragment;
|
|
}
|
|
|
|
mFragments.erase( mFragments.begin(), mFragments.end() );
|
|
mMemoryUsage = 0;
|
|
mInputLength = 0;
|
|
mOutputLength = 0;
|
|
mCachedFragmentAddress = 0;
|
|
mpCachedFragment = nullptr;
|
|
std::memset( mpCacheHashTable.data(), 0, mpCacheHashTable.size() * sizeof(mpCacheHashTable[0]));
|
|
mJumpMap.clear();
|
|
|
|
mCacheCoverage.Reset();
|
|
|
|
mpCodeBufferManager->Reset();
|
|
}
|
|
|
|
//*************************************************************************************
|
|
//
|
|
//*************************************************************************************
|
|
bool CFragmentCache::ShouldInvalidateOnWrite( u32 address, u32 length ) const
|
|
{
|
|
return mCacheCoverage.IsCovered( address, length );
|
|
}
|
|
|
|
|
|
|
|
|
|
//*************************************************************************************
|
|
//
|
|
//*************************************************************************************
|
|
#define AddressToIndex( addr ) ((addr - BASE_ADDRESS) >> MEM_USAGE_SHIFT)
|
|
|
|
//*************************************************************************************
|
|
//
|
|
//*************************************************************************************
|
|
void CFragmentCacheCoverage::ExtendCoverage( u32 address, u32 len )
|
|
{
|
|
u32 first_entry = AddressToIndex( address );
|
|
u32 last_entry = AddressToIndex( address + len );
|
|
|
|
// Mark all entries as true
|
|
for( u32 i = first_entry; i <= last_entry && i < NUM_MEM_USAGE_ENTRIES; ++i )
|
|
{
|
|
mCacheCoverage[ i ] = true;
|
|
}
|
|
}
|
|
|
|
//*************************************************************************************
|
|
//
|
|
//*************************************************************************************
|
|
bool CFragmentCacheCoverage::IsCovered( u32 address, u32 len ) const
|
|
{
|
|
#ifdef DAEDALUS_DEBUG_CONSOLE
|
|
if((address - BASE_ADDRESS) == 0)
|
|
{
|
|
DBGConsole_Msg( 0, "Cache coverage address is overlapping" );
|
|
return true;
|
|
}
|
|
#endif
|
|
u32 first_entry = AddressToIndex( address );
|
|
u32 last_entry = AddressToIndex( address + len );
|
|
|
|
// Mark all entries as true
|
|
for( u32 i = first_entry; i <= last_entry && i < NUM_MEM_USAGE_ENTRIES; ++i )
|
|
{
|
|
if( mCacheCoverage[ i ] )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//*************************************************************************************
|
|
//
|
|
//*************************************************************************************
|
|
|
|
#ifdef DAEDALUS_DEBUG_DYNAREC
|
|
//*************************************************************************************
|
|
//
|
|
//*************************************************************************************
|
|
struct SDescendingCyclesSort
|
|
{
|
|
bool operator()(CFragment* const & a, CFragment* const & b)
|
|
{
|
|
return b->GetCyclesExecuted() < a->GetCyclesExecuted();
|
|
}
|
|
};
|
|
|
|
//*************************************************************************************
|
|
//
|
|
//*************************************************************************************
|
|
void CFragmentCache::DumpStats( const std::filesystem::path outputdir ) const
|
|
{
|
|
|
|
using FragmentList = std::vector< CFragment *>;
|
|
|
|
FragmentList all_fragments;
|
|
|
|
all_fragments.reserve( mFragments.size() );
|
|
|
|
u32 total_cycles( 0 );
|
|
|
|
// Sort in order of expended cycles
|
|
for(FragmentVec::const_iterator it = mFragments.begin(); it != mFragments.end(); ++it)
|
|
{
|
|
all_fragments.push_back( it->Fragment );
|
|
total_cycles += it->Fragment->GetCyclesExecuted();
|
|
}
|
|
|
|
std::sort( all_fragments.begin(), all_fragments.end(), SDescendingCyclesSort() );
|
|
|
|
|
|
std::filesystem::path filename = "fragments.html";
|
|
std::filesystem::path fragments_dir = setbasePath("fragments");
|
|
fragments_dir /= filename;
|
|
|
|
|
|
std::ofstream fh(filename);
|
|
if (fh.is_open())
|
|
{
|
|
fh << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
|
|
fh << "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n";
|
|
fh << "<head><title>Fragments</title>\n";
|
|
fh << "<link rel=\"stylesheet\" href=\"default.css\" type=\"text/css\" media=\"all\" />\n";
|
|
fh << "</head><body>\n";
|
|
|
|
fh << "<h1>Fragments</h1>\n";
|
|
fh << "<div align=\"center\"><table>\n";
|
|
fh << "<tr><th>Address</th><th>Loops</th><th>Cycle Count</th><th>Cycle %</th><th>Hit Count</th><th>Input Bytes</th><th>Output Bytes</th><th>Expansion Ratio</th></tr>\n";
|
|
|
|
for (auto it = all_fragments.begin(); it != all_fragments.end(); ++it) {
|
|
const CFragment* fragment = *it;
|
|
|
|
if (fragment->GetCyclesExecuted() == 0)
|
|
continue;
|
|
|
|
fh << "<tr>";
|
|
fh << "<td><a href=\"fragments//" << std::hex << std::setw(8) << std::setfill('0') << fragment->GetEntryAddress() << ".html\">"
|
|
<< "0x" << std::hex << std::setw(8) << std::setfill('0') << fragment->GetEntryAddress() << "</a></td>";
|
|
fh << "<td>" << (fragment->GetEntryAddress() == fragment->GetExitAddress() ? "*" : " ") << "</td>";
|
|
fh << "<td>" << fragment->GetCyclesExecuted() << "</td>";
|
|
fh << "<td>" << std::fixed << std::setprecision(2) << (static_cast<float>(fragment->GetCyclesExecuted()) * 100.0f / total_cycles) << "%</td>";
|
|
fh << "<td>" << fragment->GetHitCount() << "</td>";
|
|
fh << "<td>" << fragment->GetInputLength() << "</td>";
|
|
fh << "<td>" << fragment->GetOutputLength() << "</td>";
|
|
fh << "<td>" << std::fixed << std::setprecision(2) << (static_cast<float>(fragment->GetOutputLength()) / fragment->GetInputLength()) << "</td>";
|
|
fh << "</tr>\n";
|
|
|
|
std::string fragment_name = fragments_dir + "/" + std::to_string(fragment->GetEntryAddress()) + ".html";
|
|
std::ofstream fragment_fh(fragment_name);
|
|
if (fragment_fh.is_open()) {
|
|
fragment->DumpFragmentInfoHtml(fragment_fh, total_cycles);
|
|
fragment_fh.close();
|
|
}
|
|
}
|
|
|
|
fh << "</table></div>\n";
|
|
fh << "</body></html>\n";
|
|
}
|
|
}
|
|
#endif // DAEDALUS_DEBUG_DYNAREC
|
|
|
|
void CFragmentCacheCoverage::Reset( )
|
|
{
|
|
std::fill(std::begin(mCacheCoverage), std::end(mCacheCoverage), 0);
|
|
// std::memset( mCacheCoverage.data(), false, mCacheCoverage.size() * sizeof( mCacheCoverage ) );
|
|
}
|