diff --git a/src/drivers/Qt/ConsoleUtilities.h b/src/drivers/Qt/ConsoleUtilities.h index 93827117..d867ff47 100644 --- a/src/drivers/Qt/ConsoleUtilities.h +++ b/src/drivers/Qt/ConsoleUtilities.h @@ -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 + 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 - 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 + template + inline StringBuilder &operator <<(const IntInfo intInfo) + { + *this << intInfo.prefix; + return appendInt(intInfo.x, intInfo.minLen, intInfo.leadChar, intInfo.upperCase); + } + +protected: + inline StringBuilder &operator <<(nullptr_t) + { + return *this; + } + + template 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 - inline StringBuilder &appendDec(T x, int minLen = 0, char leadChar = ' ') - { - appendInt<10>(x, minLen, leadChar); - - return *this; - } - - template - inline StringBuilder &appendHex(T x, int minLen = 0, bool upperCase = true, char leadChar = '0') - { - appendInt<16>(x, minLen, leadChar, upperCase); - - return *this; - } - - template - inline StringBuilder &appendAddr(T addr, int minLen = 4, bool upperCase = true) - { - *(end++) = '$'; - return appendHex(addr, minLen, upperCase); - } - - template - inline StringBuilder &appendLit(T x, int minLen = 2, bool upperCase = true) - { - *(end++) = '#'; - *(end++) = '$'; - return appendHex(x, minLen, upperCase); - } - -protected: char *start; char *end; -}; \ No newline at end of file +}; + +// Formatters for numbers + +// Generic integer with any radius +template +inline StringBuilder::IntInfo sb_int(T x, int minLen = 0, char leadChar = ' ', bool upperCase = false) +{ + return { x, minLen, leadChar, upperCase, nullptr }; +} + +// A decimal number +template +inline StringBuilder::IntInfo<10, T> sb_dec(T x, int minLen = 0, char leadChar = ' ') +{ + return { x, minLen, leadChar, false, nullptr }; +} + +// A hex number +template +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 +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 +inline StringBuilder::IntInfo<16, T, const char *> sb_lit(T x, int minLen = 2, bool upperCase = true) +{ + return { x, minLen, '0', upperCase, "#$" }; +} diff --git a/src/drivers/Qt/SymbolicDebug.cpp b/src/drivers/Qt/SymbolicDebug.cpp index 070fb0ae..2d735d6f 100644 --- a/src/drivers/Qt/SymbolicDebug.cpp +++ b/src/drivers/Qt/SymbolicDebug.cpp @@ -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; diff --git a/src/drivers/Qt/TraceFileWriter.h b/src/drivers/Qt/TraceFileWriter.h index 91a86185..907baf42 100644 --- a/src/drivers/Qt/TraceFileWriter.h +++ b/src/drivers/Qt/TraceFileWriter.h @@ -4,6 +4,9 @@ #include #include +// 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; } } diff --git a/src/drivers/Qt/TraceLogger.cpp b/src/drivers/Qt/TraceLogger.cpp index a0720922..7455beac 100644 --- a/src/drivers/Qt/TraceLogger.cpp +++ b/src/drivers/Qt/TraceLogger.cpp @@ -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)) {