daedalus/Source/DynaRec/TraceRecorder.cpp
2023-06-26 22:11:40 +10:00

438 lines
12 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 "BuildOptions.h"
#include "Base/Types.h"
#include "Core/CPU.h" // For dubious use of PC/NewPC
#include "Core/Registers.h"
#include "Debug/DBGConsole.h"
#include "DynaRec/BranchType.h"
#include "DynaRec/CodeBufferManager.h"
#include "DynaRec/Fragment.h"
#include "DynaRec/TraceRecorder.h"
#include "Utility/Profiler.h"
#include "Core/PrintOpCode.h"
//#define LOG_ABORTED_TRACES
namespace
{
const u32 INVALID_IDX = u32( ~0 );
const u32 INDIRECT_EXIT_ADDRESS = u32( ~0 );
const u32 MAX_TRACE_LENGTH = 1500;
}
CTraceRecorder gTraceRecorder;
//
CTraceRecorder::CTraceRecorder()
: mTracing( false )
, mStartTraceAddress( 0 )
, mExpectedExitTraceAddress( 0 )
, mActiveBranchIdx( INVALID_IDX )
, mStopTraceAfterDelaySlot( false )
, mNeedIndirectExitMap( false )
{
}
//
void CTraceRecorder::StartTrace( u32 address )
{
#ifdef DAEDALUS_PROFILE
DAEDALUS_PROFILE( "CTraceRecorder::StartTrace" );
#endif
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT( !mTracing, "We're already tracing" );
#endif
mTraceBuffer.clear();
mBranchDetails.clear();
mNeedIndirectExitMap = false;
mTracing = true;
mStartTraceAddress = address;
mActiveBranchIdx = INVALID_IDX;
mStopTraceAfterDelaySlot = false;
mExpectedExitTraceAddress = address + 4;
}
//
CTraceRecorder::EUpdateTraceStatus CTraceRecorder::UpdateTrace( u32 address,
bool branch_delay_slot,
bool branch_taken,
OpCode op_code,
CFragment * p_fragment )
{
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT( mTracing, "We're not tracing" );
#endif
bool want_to_stop( p_fragment != nullptr );
if( mTraceBuffer.size() > MAX_TRACE_LENGTH )
{
#ifdef DAEDALUS_DEBUG_CONSOLE
DBGConsole_Msg(0, "Hit max trace size!");
#endif
want_to_stop = true;
}
// Terminate if the current instruction is in the fragment cache or the trace reaches a specified size
if( want_to_stop && (mActiveBranchIdx == INVALID_IDX) )
{
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT( mActiveBranchIdx == INVALID_IDX, "Exiting trace while in the middle of handling branch!" );
#endif
// Stop immediately so we can be sure of linking up with fragment
mTracing = false;
mExpectedExitTraceAddress = address;
return UTS_CREATE_FRAGMENT;
}
//
// Figure out whether to terminate this trace after adding this instruction
//
bool stop_trace_on_exit( false );
//
// We want to record the delay slot op for the active branch
//
if( mActiveBranchIdx != INVALID_IDX )
{
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT( mActiveBranchIdx < mBranchDetails.size(), "Branch index is out of bounds" );
#endif
mBranchDetails[ mActiveBranchIdx ].DelaySlotTraceIndex = mTraceBuffer.size();
if (mBranchDetails[ mActiveBranchIdx ].SpeedHack == SHACK_POSSIBLE)
{
if (op_code._u32 == 0)
{
mBranchDetails[ mActiveBranchIdx ].SpeedHack = SHACK_SKIPTOEVENT;
}
#ifndef DAEDALUS_SILENT
else if (op_code.op == OP_ADDIU || op_code.op == OP_DADDI || op_code.op == OP_ADDI || op_code.op == OP_DADDIU)
{ // We don't handle COPYREG SPEEDHACKS
mBranchDetails[ mActiveBranchIdx ].SpeedHack = SHACK_COPYREG;
}
#endif
}
mActiveBranchIdx = INVALID_IDX;
}
// If we had to terminate on the last branch (e.g. for an indirect jump)
// or if we're jumping backwards, terminate the run when we exit
if( mStopTraceAfterDelaySlot && branch_delay_slot )
{
mStopTraceAfterDelaySlot = false;
stop_trace_on_exit = true;
}
//
// Update the expected trace exit address
// We assume that if we'll exit on the next instruction (assuming this isn't a branch)
//
if( !branch_delay_slot )
{
mExpectedExitTraceAddress = address + 4;
}
StaticAnalysis::RegisterUsage usage;
StaticAnalysis::Analyse(op_code,usage);
//
// If this is a branch, we need to determine if it was taken or not.
// We store a information about the 'off-trace' target.
// If the branch was taken, we need to flip the condition of the
// branch so that anything failing the test is directed off our trace
//
u32 branch_idx( INVALID_IDX );
ER4300BranchType branch_type( usage.BranchType );
if( branch_type != BT_NOT_BRANCH )
{
SBranchDetails details;
details.Likely = IsBranchTypeLikely( branch_type );
if( branch_type == BT_ERET )
{
stop_trace_on_exit = true;
details.Eret = true;
details.Direct = false;
details.TargetAddress = INDIRECT_EXIT_ADDRESS;
mExpectedExitTraceAddress = details.TargetAddress;
}
else if( !IsConditionalBranch( branch_type ) )
{
details.Direct = IsBranchTypeDirect( branch_type );
details.TargetAddress = gCPUState.TargetPC;
details.ConditionalBranchTaken = true;
mExpectedExitTraceAddress = details.TargetAddress;
if (!details.Direct || gCPUState.TargetPC <= gCPUState.CurrentPC)
{
// all indirect call will stop the trace
mStopTraceAfterDelaySlot = true;
}
if (details.Direct && gCPUState.TargetPC == gCPUState.CurrentPC)
{
details.SpeedHack = SHACK_POSSIBLE;
}
}
else
{
// Must be conditional, direct
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT( IsBranchTypeDirect( branch_type ), "Not expecting an indirect branch here" );
#endif
if( branch_taken )
{
// XXXXXX should be able to get this some other way?
bool backwards( gCPUState.TargetPC <= gCPUState.CurrentPC );
if( backwards )
{
mStopTraceAfterDelaySlot = true;
}
if (gCPUState.TargetPC == gCPUState.CurrentPC)
{
details.SpeedHack = SHACK_POSSIBLE;
}
}
u32 branch_target_address( GetBranchTarget( address, op_code, branch_type ) );
u32 fallthrough_address( address + 8 );
u32 target_address = 0;
if( branch_taken )
{
// We're following the branch.
target_address = fallthrough_address;
mExpectedExitTraceAddress = branch_target_address;
}
else
{
// We're not following the branch, and falling through.
target_address = branch_target_address;
mExpectedExitTraceAddress = fallthrough_address;
}
details.ConditionalBranchTaken = branch_taken;
details.Direct = true;
details.TargetAddress = target_address;
}
if( !details.Direct )
{
mNeedIndirectExitMap = true;
}
mActiveBranchIdx = mBranchDetails.size();
branch_idx = mBranchDetails.size();
mBranchDetails.push_back( details );
}
// Add this op to the trace buffer.
STraceEntry entry = { address, op_code, usage, branch_idx, branch_delay_slot };
mTraceBuffer.push_back( entry );
if( stop_trace_on_exit )
{
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT( branch_type == BT_ERET || mActiveBranchIdx == INVALID_IDX, "Exiting trace while in the middle of handling branch!" );
#endif
mTracing = false;
return UTS_CREATE_FRAGMENT;
}
return UTS_CONTINUE_TRACE;
}
//
void CTraceRecorder::StopTrace( u32 exit_address )
{
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT( mTracing, "We're not tracing" );
DAEDALUS_ASSERT( mActiveBranchIdx == INVALID_IDX, "Stopping trace when a branch is active" );
#endif
mTracing = false;
mExpectedExitTraceAddress = exit_address;
}
//
CFragment * CTraceRecorder::CreateFragment( std::unique_ptr<CCodeBufferManager> p_manager )
{
#ifdef DAEDALUS_ENABLE_DYNAREC_PROFILE
DAEDALUS_PROFILE( "CTraceRecorder::CreateFragment" );
#endif
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT( !mTraceBuffer.empty(), "No trace ready for creation?" );
#endif
SRegisterUsageInfo register_usage;
Analyse( register_usage );
CFragment * p_frament( new CFragment( std::move(p_manager), mStartTraceAddress, mExpectedExitTraceAddress,
mTraceBuffer, register_usage, mBranchDetails, mNeedIndirectExitMap ) );
//DBGConsole_Msg( 0, "Inserting hot trace for [R%08x]!", mStartTraceAddress );
mTracing = false;
mStartTraceAddress = 0;
mTraceBuffer.clear();
mBranchDetails.clear();
mExpectedExitTraceAddress = 0;
mActiveBranchIdx = INVALID_IDX;
mStopTraceAfterDelaySlot = false;
mNeedIndirectExitMap = false;
return p_frament;
}
//
void CTraceRecorder::AbortTrace()
{
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_PROFILE( "CTraceRecorder::AbortTrace" );
#endif
if( mTracing )
{
#ifdef LOG_ABORTED_TRACES
FILE * fh( fopen( "aborted_traces.txt", "a" ) );
if(fh)
{
fprintf( fh, "\n\nTrace: (%d ops)\n", mTraceBuffer.size() );
u32 last_address( mTraceBuffer.size() > 0 ? mTraceBuffer[ 0 ].Address-4 : 0 );
for(std::vector< STraceEntry >::const_iterator it = mTraceBuffer.begin(); it != mTraceBuffer.end(); ++it)
{
u32 address( it->Address );
OpCode op_code( it->OpCode );
u32 branch_index( it->BranchIdx );
if( branch_index != INVALID_IDX )
{
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT( branch_index < mBranchDetails.size(), "The branch index is out of range" );
#endif
const SBranchDetails & details( mBranchDetails[ branch_index ] );
#ifdef DAEDALUS_DEBUG_CONSOLE
fprintf( fh, " BRANCH %d -> %08x\n", branch_index, details.TargetAddress );
#endif
}
char buf[100];
SprintOpCodeInfo( buf, address, op_code );
bool is_jump( address != last_address + 4 );
#ifdef DAEDALUS_DEBUG_CONSOLE
fprintf( fh, "%08x: %c%s\n", address, is_jump ? '*' : ' ', buf );
#endif
last_address = address;
}
fclose(fh);
}
#endif
//DBGConsole_Msg( 0, "Aborting tracing of [R%08x]", mStartTraceAddress );
mTracing = false;
mStartTraceAddress = 0;
mTraceBuffer.clear();
mBranchDetails.clear();
mExpectedExitTraceAddress = 0;
mActiveBranchIdx = INVALID_IDX;
mStopTraceAfterDelaySlot = false;
mNeedIndirectExitMap = false;
}
}
//
void CTraceRecorder::Analyse( SRegisterUsageInfo & register_usage )
{
#ifdef DAEDALUS_ENABLE_DYNAREC_PROFILE
DAEDALUS_PROFILE( "CTraceRecorder::Analyse" );
#endif
std::pair< s32, s32 > reg_spans[ NUM_N64_REGS ];
std::pair< s32, s32 > invalid_span( std::pair< s32, s32 >( mTraceBuffer.size(), -1 ) );
std::fill( reg_spans, reg_spans + NUM_N64_REGS, invalid_span ); // Set the interval to an invalid range
for( u32 i = 0; i < mTraceBuffer.size(); ++i )
{
const STraceEntry & ti( mTraceBuffer[ i ] );
const StaticAnalysis::RegisterUsage& usage = ti.Usage;
register_usage.RegistersRead |= usage.RegReads;
register_usage.RegistersWritten |= usage.RegWrites;
register_usage.RegistersAsBases |= usage.RegBase;
u32 all_uses( usage.RegReads | usage.RegWrites | usage.RegBase );
// Update the live span
for( s32 reg = 1; reg < NUM_N64_REGS; ++reg )
{
if( all_uses & (1<<reg) )
{
if( s32( i ) < reg_spans[ reg ].first ) reg_spans[ reg ].first = i;
if( s32( i ) > reg_spans[ reg ].second ) reg_spans[ reg ].second = i;
}
}
}
register_usage.SpanList.clear();
register_usage.SpanList.reserve( NUM_N64_REGS );
// Iterate through registers, inserting all that are used into span list
for( u32 i = 0; i < NUM_N64_REGS; ++i )
{
s32 start( reg_spans[ i ].first );
s32 end( reg_spans[ i ].second );
if( start <= end )
{
SRegisterSpan span( EN64Reg( i ), start, end );
register_usage.SpanList.push_back( span );
}
}
}