Finish up

This commit is contained in:
TheRealQuantam 2024-02-21 18:38:00 -08:00
parent 8e7c6f68ff
commit 2b418e765e
4 changed files with 273 additions and 129 deletions

View File

@ -95,10 +95,21 @@ QString fceuGetOpcodeToolTip( uint8_t *opcode, int size );
QDialog *fceuCustomToolTipShow( const QPoint &globalPos, QDialog *popup );
// Faster replacement functions for sprintf
// Faster replacement functions for sprintf. It's uglier, but much faster.
class StringBuilder
{
public:
// Helper struct. Do not use directly.
template <unsigned Radix, typename T, typename Prefix = nullptr_t>
struct IntInfo
{
T x;
int minLen;
char leadChar;
bool upperCase;
Prefix prefix;
};
inline StringBuilder(char *str) : start(str), end(str)
{
*end = '\0';
@ -114,13 +125,7 @@ public:
return size_t(end - start);
}
template <class T>
inline StringBuilder &operator << (T value)
{
return append(value);
}
inline StringBuilder &append(char ch)
inline StringBuilder &operator <<(char ch)
{
*(end++) = ch;
*end = '\0';
@ -128,7 +133,7 @@ public:
return *this;
}
inline StringBuilder &append(const char *src)
inline StringBuilder &operator <<(const char *src)
{
size_t len = strlen(src);
memcpy(end, src, len + 1);
@ -138,7 +143,20 @@ public:
return *this;
}
template <unsigned Radix, class T>
template<unsigned Radix, typename T, typename Prefix>
inline StringBuilder &operator <<(const IntInfo<Radix, T, Prefix> intInfo)
{
*this << intInfo.prefix;
return appendInt<Radix>(intInfo.x, intInfo.minLen, intInfo.leadChar, intInfo.upperCase);
}
protected:
inline StringBuilder &operator <<(nullptr_t)
{
return *this;
}
template<unsigned Radix, typename T>
StringBuilder &appendInt(T x, int minLen = 0, char leadChar = ' ', bool upperCase = false)
{
static_assert(Radix >= 2 && Radix <= 36, "Radix must be between 2 and 36");
@ -195,38 +213,43 @@ public:
return *this;
}
template <class T>
inline StringBuilder &appendDec(T x, int minLen = 0, char leadChar = ' ')
{
appendInt<10>(x, minLen, leadChar);
return *this;
}
template <class T>
inline StringBuilder &appendHex(T x, int minLen = 0, bool upperCase = true, char leadChar = '0')
{
appendInt<16>(x, minLen, leadChar, upperCase);
return *this;
}
template <class T>
inline StringBuilder &appendAddr(T addr, int minLen = 4, bool upperCase = true)
{
*(end++) = '$';
return appendHex(addr, minLen, upperCase);
}
template <class T>
inline StringBuilder &appendLit(T x, int minLen = 2, bool upperCase = true)
{
*(end++) = '#';
*(end++) = '$';
return appendHex(x, minLen, upperCase);
}
protected:
char *start;
char *end;
};
};
// Formatters for numbers
// Generic integer with any radius
template<unsigned Radix, typename T>
inline StringBuilder::IntInfo<Radix, T> sb_int(T x, int minLen = 0, char leadChar = ' ', bool upperCase = false)
{
return { x, minLen, leadChar, upperCase, nullptr };
}
// A decimal number
template <typename T>
inline StringBuilder::IntInfo<10, T> sb_dec(T x, int minLen = 0, char leadChar = ' ')
{
return { x, minLen, leadChar, false, nullptr };
}
// A hex number
template <typename T>
inline StringBuilder::IntInfo<16, T> sb_hex(T x, int minLen = 0, bool upperCase = true, char leadChar = '0')
{
return { x, minLen, leadChar, upperCase, nullptr };
}
// An address of the basic form $%x
template <typename T>
inline StringBuilder::IntInfo<16, T, char> sb_addr(T x, int minLen = 4, bool upperCase = true)
{
return { x, minLen, '0', upperCase, '$' };
}
// A literal value of the form #$%x
template <typename T>
inline StringBuilder::IntInfo<16, T, const char *> sb_lit(T x, int minLen = 2, bool upperCase = true)
{
return { x, minLen, '0', upperCase, "#$" };
}

View File

@ -62,7 +62,7 @@ debugSymbol_t *replaceSymbols( int flags, int addr, char *str )
if ( !sym || !( flags & ASM_DEBUG_REPLACE ) )
{
(sb << '$').appendHex( addr, flags & ASM_DEBUG_ADDR_02X ? 2 : 4 );
sb << sb_addr( addr, flags & ASM_DEBUG_ADDR_02X ? 2 : 4 );
if ( sym )
sb << ' ';
@ -169,7 +169,7 @@ int DisassembleWithDebug(int addr, uint8_t *opcode, int flags, char *str, debugS
if ( symDebugEnable )
sym = replaceSymbols( flags, tmp, stmp );
(sb << chr << " (").appendAddr(opcode[1], 2) << ',' << indReg << ')';
sb << chr << " (" << sb_addr(opcode[1], 2) << ',' << indReg << ')';
if (showTrace)
{
@ -177,9 +177,9 @@ int DisassembleWithDebug(int addr, uint8_t *opcode, int flags, char *str, debugS
if (symDebugEnable)
sb << stmp;
else
sb.appendAddr(tmp);
sb << sb_addr(tmp);
(sb << " = ").appendLit(GetMem(tmp));
sb << " = " << sb_lit(GetMem(tmp));
}
break;
@ -216,10 +216,10 @@ int DisassembleWithDebug(int addr, uint8_t *opcode, int flags, char *str, debugS
sb << stmp;
}
else
sb.appendAddr(opcode[1], 2);
sb << sb_addr(opcode[1], 2);
if (showTrace)
(sb << " = ").appendLit(GetMem(opcode[1]));
sb << " = " << sb_lit(GetMem(opcode[1]));
// ################################## End of SP CODE ###########################
break;
@ -238,7 +238,7 @@ int DisassembleWithDebug(int addr, uint8_t *opcode, int flags, char *str, debugS
case 0xE0: chr = "CPX"; goto _immediate;
case 0xE9: chr = "SBC"; goto _immediate;
_immediate:
(sb << chr << ' ').appendLit(opcode[1]);
sb << chr << ' ' << sb_lit(opcode[1]);
break;
//Absolute
@ -273,10 +273,10 @@ int DisassembleWithDebug(int addr, uint8_t *opcode, int flags, char *str, debugS
sb << stmp;
}
else
sb.appendAddr(tmp);
sb << sb_addr(tmp);
if (showTrace)
(sb << " = ").appendLit(GetMem(tmp));
sb << " = " << sb_lit(GetMem(tmp));
break;
@ -299,7 +299,7 @@ int DisassembleWithDebug(int addr, uint8_t *opcode, int flags, char *str, debugS
sb << stmp;
}
else
sb.appendAddr(tmp);
sb << sb_addr(tmp);
break;
@ -345,16 +345,16 @@ int DisassembleWithDebug(int addr, uint8_t *opcode, int flags, char *str, debugS
if ( symDebugEnable )
sym = replaceSymbols( flags, tmp, stmp );
(sb << chr << ' ').appendAddr(opcode[1], 2) << ',' << indReg;
sb << chr << ' ' << sb_addr(opcode[1], 2) << ',' << indReg;
if (showTrace)
{
sb << " @ ";
if (symDebugEnable)
sb << stmp;
else
sb.appendAddr(tmp);
sb << sb_addr(tmp);
(sb << " = ").appendLit(GetMem(tmp));
sb << " = " << sb_lit(GetMem(tmp));
}
// ################################## End of SP CODE ###########################
break;
@ -383,7 +383,7 @@ int DisassembleWithDebug(int addr, uint8_t *opcode, int flags, char *str, debugS
sb << stmp;
}
else
sb.appendAddr(tmp);
sb << sb_addr(tmp);
sb << ',' << indReg;
@ -393,9 +393,9 @@ int DisassembleWithDebug(int addr, uint8_t *opcode, int flags, char *str, debugS
if (symDebugEnable)
sb << stmp2;
else
sb.appendAddr(tmp2);
sb << sb_addr(tmp2);
(sb << " = ").appendLit(GetMem(tmp2));
sb << " = " << sb_lit(GetMem(tmp2));
}
break;
@ -436,15 +436,15 @@ int DisassembleWithDebug(int addr, uint8_t *opcode, int flags, char *str, debugS
sb << stmp;
}
else
sb.appendAddr(tmp);
sb << sb_addr(tmp);
break;
case 0x6C:
absolute(tmp);
(sb << "JMP (").appendAddr(tmp);
(sb << ") = ").appendAddr(GetMem(tmp) | GetMem(tmp + 1) << 8);
sb << "JMP (" << sb_addr(tmp);
sb << ") = " << sb_addr(GetMem(tmp) | GetMem(tmp + 1) << 8);
break;

View File

@ -4,6 +4,9 @@
#include <stdint.h>
#include <string>
// High-performance class for writing a series of text lines to a file, using overlapped, unbuffered I/O
// Works on Windows builds both SDL/Qt and non-SQL/Qt
// Apart from getLastError, the entire API can be adapted to other OS with no client changes
class TraceFileWriter
{
public:
@ -13,6 +16,7 @@ public:
inline TraceFileWriter()
{
// Windows Vista API that allows setting the end of file without closing and reopening it
HMODULE kernel32 = GetModuleHandleA("kernel32");
SetFileInformationByHandle = kernel32 ? (SetFileInformationByHandlePtr)GetProcAddress(kernel32, "SetFileInformationByHandle") : nullptr;
@ -30,14 +34,16 @@ public:
return isOpen;
}
// SUPPOSED to always be valid (ERROR_SUCCESS if no error), but bugs may make it only valid after a failure
inline DWORD getLastError() const
{
return lastErr;
}
// Open the file and allocate all necessary resources
bool open(const char *fileName, bool isPaused = false)
{
HANDLE file = CreateFileA(fileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, NULL);
HANDLE file = CreateFileA(fileName, GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, nullptr);
if (file == INVALID_HANDLE_VALUE)
{
lastErr = GetLastError();
@ -46,6 +52,7 @@ public:
initialize(false, isPaused, fileName, file);
// Allocate resources
do
{
for (unsigned i = 0; i < 2; i++)
@ -70,11 +77,13 @@ public:
} while (false);
if (!isOpen)
// Free resources on failure
cleanup();
return isOpen;
}
// Close the file and free resources
void close()
{
if (!isOpen)
@ -91,16 +100,19 @@ public:
cleanup();
}
// When going from unpaused to paused, flush file and set end of file so it can be accessed externally
bool setPause(bool isPaused)
{
if (isPaused && !this->isPaused)
{
// Wait for any outstanding writes to complete
for (unsigned i = 0; i < 2; i++)
{
if (!waitForBuffer(i))
return false;
}
// Write out anything still in the buffer
if (!writeTail())
return false;
}
@ -111,6 +123,8 @@ public:
return true;
}
// Add a line to the buffer and write it out when the buffer is filled
// Under most failure cirumstances the line is added to the buffer
bool writeLine(const char *line)
{
if (!isOpen)
@ -119,20 +133,23 @@ public:
return false;
}
// Add to buffer
static const char eol[] = "\r\n";
size_t eolSize = strlen(eol);
char *buff = buffers[buffIdx];
OVERLAPPED *ovl = &ovls[buffIdx];
size_t lineLen = strlen(line);
if (buffOffs + lineLen + 1 > BuffSize)
if (buffOffs + lineLen + eolSize > BuffSize)
{
// Buffer is full. This shouldn't ever happen.
lastErr = ERROR_INTERNAL_ERROR;
return false;
}
memcpy(buff + buffOffs, line, lineLen);
buffOffs += lineLen;
buff[buffOffs++] = '\n';
memcpy(buff + buffOffs + lineLen, eol, eolSize);
buffOffs += lineLen + eolSize;
// Check if the previous write is done, to detect it as early as possible
unsigned prevBuff = (buffIdx + 1) % 2;
if (!waitForBuffer(prevBuff, 0) && lastErr != ERROR_TIMEOUT)
return false;
@ -142,35 +159,45 @@ public:
if (buffOffs < FlushSize)
return true;
DWORD writeSize = buffOffs - (buffOffs % BlockSize);
if (!beginWrite(writeSize))
return writeBlocks();
}
// Flush buffer contents. Writes partial blocks, but does NOT set end of file
// Do NOT call frequently, as writes may be significantly undersized and cause poor performance
bool flush()
{
if (!isOpen)
{
lastErr = ERROR_FILE_NOT_FOUND;
return false;
}
// Write full blocks, if any
if (!writeBlocks())
return false;
bool isAsync = bytesToWrite[buffIdx] != INVALID_FILE_SIZE;
if (isAsync)
char *buff = buffers[buffIdx];
if (buffOffs != 0)
{
if (!waitForBuffer(prevBuff))
// Write out partial block at the tail
size_t writeSize = (buffOffs + BlockSize - 1) & ~(BlockSize - 1);
memset(buff + buffOffs, ' ', writeSize - buffOffs);
if (!beginWrite(writeSize)
|| !waitForBuffer(buffIdx))
return false;
// Switch to next buffer
memcpy(buffers[prevBuff], buff + writeSize, buffOffs - writeSize);
buffOffs -= writeSize;
fileOffs += writeSize;
buffIdx = prevBuff;
// Do NOT update buffIdx, buffOffs, or fileOffs, as the partial block must be overwritten later
}
else
// Wait for all writes to complete
for (unsigned i = 0; i < 2; i++)
{
// Stick with same buffer
memmove(buff, buff + writeSize, buffOffs - writeSize);
buffOffs -= writeSize;
if (!waitForBuffer(i))
return false;
}
bytesToWrite[buffIdx] = INVALID_FILE_SIZE;
lastErr = ERROR_SUCCESS;
return true;
}
@ -184,6 +211,8 @@ protected:
SetFileInformationByHandlePtr SetFileInformationByHandle;
const size_t NO_WRITE = size_t(-1);
DWORD lastErr;
bool isOpen;
@ -192,14 +221,16 @@ protected:
std::string fileName;
HANDLE file;
// Double-buffers
char *buffers[2];
OVERLAPPED ovls[2];
size_t bytesToWrite[2];
size_t bytesToWrite[2]; // Write in progress size or size_t(-1) if none
unsigned buffIdx;
size_t buffOffs;
uint64_t fileOffs;
// Put the class into a defined state, but does NOT allocate resources
void initialize(bool isOpen = false, bool isPaused = false, const char *fileName = "", HANDLE file = INVALID_HANDLE_VALUE)
{
lastErr = ERROR_SUCCESS;
@ -213,7 +244,7 @@ protected:
for (unsigned i = 0; i < 2; i++)
{
buffers[i] = nullptr;
bytesToWrite[i] = INVALID_FILE_SIZE;
bytesToWrite[i] = NO_WRITE;
}
memset(ovls, 0, sizeof(ovls));
@ -223,6 +254,7 @@ protected:
fileOffs = 0;
}
// Close file and release resources. Does NOT wait for writes to complete.
void cleanup()
{
isOpen = false;
@ -245,9 +277,52 @@ protected:
}
}
// Write out as many blocks as present in the buffer
bool writeBlocks()
{
if (buffOffs < BlockSize)
return true;
char *buff = buffers[buffIdx];
unsigned prevBuff = (buffIdx + 1) % 2;
OVERLAPPED *ovl = &ovls[buffIdx];
DWORD writeSize = buffOffs - (buffOffs % BlockSize);
if (!beginWrite(writeSize))
return false;
bool isAsync = bytesToWrite[buffIdx] != NO_WRITE;
if (isAsync)
{
if (!waitForBuffer(prevBuff))
return false; // Catastrophic failure case
// Switch to next buffer
memcpy(buffers[prevBuff], buff + writeSize, buffOffs - writeSize);
buffOffs -= writeSize;
fileOffs += writeSize;
buffIdx = prevBuff;
}
else
{
// Stick with same buffer
memmove(buff, buff + writeSize, buffOffs - writeSize);
buffOffs -= writeSize;
}
bytesToWrite[buffIdx] = NO_WRITE;
return true;
}
// Begin a write and handle errors without updating class state
bool beginWrite(size_t writeSize)
{
bytesToWrite[buffIdx] = INVALID_FILE_SIZE;
bytesToWrite[buffIdx] = NO_WRITE;
if (writeSize % BlockSize != 0)
{
@ -281,21 +356,12 @@ protected:
return !lastErr;
}
bool writeTail()
// Set the end of file so it can be accessed
bool setEndOfFile()
{
char *buff = buffers[buffIdx];
if (buffOffs != 0)
{
size_t writeSize = (buffOffs + BlockSize - 1) & ~(BlockSize - 1);
memset(buff + buffOffs, ' ', writeSize - buffOffs);
if (!beginWrite(writeSize)
|| !waitForBuffer(buffIdx))
return false;
}
if (SetFileInformationByHandle != nullptr)
{
// Easy case: Vista or better
FILE_END_OF_FILE_INFO eofInfo;
eofInfo.EndOfFile.QuadPart = int64_t(fileOffs + buffOffs);
@ -304,29 +370,89 @@ protected:
lastErr = GetLastError();
return false;
}
}
return true;
lastErr = ERROR_SUCCESS;
return true;
}
else
{
// Hard case: XP
// If set EOF fails, make a desperate attempt to reopen the file and keep running
lastErr = ERROR_SUCCESS;
do
{
// Set EOF to fileOffs rounded up to the next block
LARGE_INTEGER tgtOffs;
tgtOffs.QuadPart = (fileOffs + buffOffs + BlockSize - 1) & ~(BlockSize - 1);
LARGE_INTEGER newOffs;
if (!SetFilePointerEx(file, tgtOffs, &newOffs, FILE_BEGIN)
|| !SetEndOfFile(file))
break;
CloseHandle(file);
// Open file with buffering so the exact byte size can be set
tgtOffs.QuadPart = fileOffs + buffOffs;
file = CreateFile(fileName.c_str(), GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
if (file != INVALID_HANDLE_VALUE)
{
if (!SetFilePointerEx(file, tgtOffs, &newOffs, FILE_BEGIN)
|| !SetEndOfFile(file))
lastErr = GetLastError();
CloseHandle(file);
}
else
lastErr = GetLastError();
// Finally, reopen the file in original mode
file = CreateFile(fileName.c_str(), GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, nullptr);
if (file == INVALID_HANDLE_VALUE || lastErr)
break;
lastErr = ERROR_SUCCESS;
return true;
} while (false);
// Failed
if (!lastErr)
lastErr = GetLastError();
return false;
}
}
// Write out everything in the buffer and set the file end for pausing
inline bool writeTail()
{
return flush() && setEndOfFile();
}
// Wait for an a buffer to become available, waiting for write completion if necessary
bool waitForBuffer(unsigned buffIdx, DWORD timeout = INFINITE)
{
if (buffIdx >= 2)
lastErr = ERROR_INTERNAL_ERROR;
else if (bytesToWrite[buffIdx] == INVALID_FILE_SIZE)
else if (bytesToWrite[buffIdx] == NO_WRITE)
// No write in progress
lastErr = ERROR_SUCCESS;
else
{
// Wait for the operation to complete
DWORD waitRes = WaitForSingleObject(ovls[buffIdx].hEvent, timeout);
if (waitRes == WAIT_TIMEOUT)
lastErr = ERROR_TIMEOUT;
else if (waitRes != WAIT_OBJECT_0)
{
lastErr = GetLastError();
bytesToWrite[buffIdx] = INVALID_FILE_SIZE;
bytesToWrite[buffIdx] = NO_WRITE;
}
else
{
// Verify it succeeded
DWORD prevBytesWritten;
if (!GetOverlappedResult(file, &ovls[buffIdx], &prevBytesWritten, FALSE))
lastErr = GetLastError();
@ -335,7 +461,7 @@ protected:
else
lastErr = ERROR_SUCCESS;
bytesToWrite[buffIdx] = INVALID_FILE_SIZE;
bytesToWrite[buffIdx] = NO_WRITE;
}
}

View File

@ -915,15 +915,10 @@ traceRecord_t::traceRecord_t(void)
//----------------------------------------------------
int traceRecord_t::appendAsmText(const char *txt)
{
int i = 0;
size_t len = strlen(txt);
memcpy(asmTxt + asmTxtSize, txt, len + 1);
while (txt[i] != 0)
{
asmTxt[asmTxtSize] = txt[i];
i++;
asmTxtSize++;
}
asmTxt[asmTxtSize] = 0;
asmTxtSize += len;
return 0;
}
@ -961,26 +956,26 @@ int traceRecord_t::convToText(char *txt, int *len)
StringBuilder sb(txt + i);
if (skippedLines > 0)
(sb << '(').appendDec(skippedLines) << " lines skipped) ";
sb << '(' << sb_dec(skippedLines) << " lines skipped) ";
// Start filling the str_temp line: Frame count, Cycles count, Instructions count, AXYS state, Processor status, Tabs, Address, Data, Disassembly
if (logging_options & LOG_FRAMES_COUNT)
(sb << 'f').appendDec(frameCount, -6);
sb << 'f' << sb_dec(frameCount, -6);
if (logging_options & LOG_CYCLES_COUNT)
(sb << 'c').appendDec(cycleCount, -11);
sb << 'c' << sb_dec(cycleCount, -11);
if (logging_options & LOG_INSTRUCTIONS_COUNT)
(sb << 'i').appendDec(instrCount, -11);
sb << 'i' << sb_dec(instrCount, -11);
if (logging_options & LOG_REGISTERS)
{
StringBuilder sb(str_axystate);
(sb << "A:").appendHex(cpu.A, 2);
(sb << " X:").appendHex(cpu.X, 2);
(sb << " Y:").appendHex(cpu.Y, 2);
(sb << " S:").appendHex(cpu.S, 2);
sb << ' ';
sb << "A:" << sb_hex(cpu.A, 2)
<< " X:" << sb_hex(cpu.X, 2)
<< " Y:" << sb_hex(cpu.Y, 2)
<< " S:" << sb_hex(cpu.S, 2)
<< ' ';
}
if (logging_options & LOG_PROCESSOR_STATUS)
@ -1020,24 +1015,24 @@ int traceRecord_t::convToText(char *txt, int *len)
if (logging_options & LOG_BANK_NUMBER)
{
if (cpu.PC >= 0x8000)
sb.appendAddr((uint8_t)bank, 2) << ':';
sb << sb_addr((uint8_t)bank, 2) << ':';
else
sb << " $";
}
else
sb << '$';
sb.appendHex(cpu.PC, 4) << ": ";
sb << sb_hex(cpu.PC, 4) << ": ";
for (j = 0; j < opSize; j++)
sb.appendHex(opCode[j], 2) << ' ';
sb << sb_hex(opCode[j], 2) << ' ';
for (; j < 3; j++)
sb << " ";
sb << asmTxt;
if (callAddr >= 0)
(sb << " (from ").appendAddr((uint16_t)callAddr) << ')';
sb << " (from " << sb_addr((uint16_t)callAddr) << ')';
if (!(logging_options & LOG_TO_THE_LEFT))
{