The core and the headless tools no longer use mutexes, condition variables,
spinlocks, std::atomic, std::thread or volatile flags. Threads hand off through
libretro-common's retro_atomic and rthreads plus a few small pieces on top of
them in Common/Thread: Thread, MpscQueue, EventCounter, ParkingLot waits and
ReaderGate. Every wait blocks until it's woken; nothing polls on a timer.
State that only one thread touches just loses its lock. The PSP filesystem,
sceIo and the savedata dialog now belong to the emulation thread: async reads
and writes run when they're queued and complete later in emulated time, and
the IO and savedata threads are gone. The friend finder only talks to the
adhoc server and hands what it gets to the emulation thread. GPU init always
runs on the thread that starts the game.
The Vulkan render and compile queues, pipeline and render pass caches, delete
list and SPIR-V cache, the GL render queue, the software renderer's jit caches,
the file loader caches, the web debugger, UPnP, input, savestates, logging and
the GE debugger all hand off through queues and atomics. The IR jit's spinning
profiler thread goes.
A CPU thread with nothing to run now sleeps until work is queued instead of
spinning. Headless with --debugger waits for a client while stopped, and ends
the run once every client that connected has left. Web server shutdown keeps
servicing the CPU queue until the server has finished.
The libretro core is the frontend, and the headless tools are the test and
debugging hosts. The Windows, UWP, Android, iOS, macOS and SDL apps, the UI
framework and imgui go, along with what only they used: the Win32 debugger
hooks (g_frameMutex, the shutdown lock), JNI thread attachment, the Android
text drawer, RAIntegration, Discord, recent files, game installs, tilt input,
the audio resamplers, the control mapper, memstick moving and the remote ISO
and upload server modes. The web server keeps the debugger. The MSVC, Gradle
and ndk-build projects go too.
* Destroy the ImGui debugger explicitly, not at static destruction time
* Windows: destroy the debugger windows before NativeShutdown()
* Clear the disassembly cache where it goes stale, not in view destructors
(behind a no-op stub for libretro, which doesn't build DisassemblyManager.cpp)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It stopped being about memory when CPU_Shutdown started holding it across the
whole teardown - it's what keeps kernel objects, the symbol map and the memory
map from being freed while another thread reads them. The old name invited the
reading that it locks memory *access*, which it has never done.
Memory::Reinit() now holds it across both halves rather than relying on
Memory::Shutdown()'s own acquire: between Shutdown() and Init() there is no
memory map at all, and a reader could slip into that gap.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
- DisassemblyFunction/DisassemblyData::getLineAddress() indexed
lineAddresses[0] unconditionally; a zero-size symbol (reachable via
the WebSocket debugger's hle.func.add/hle.data.add with an
attacker-controlled size, a crafted ELF symtab entry with
st_size==0, or the debugger UI's "set function size") leaves that
vector empty, making findDisassemblyEntry's getLineAddress(0) call
undefined behavior. Fall back to the symbol's own base address when
out of range instead.
- DisassemblyData::createLines() detected an invalid address range and
logged it, but fell through anyway into a loop reading through that
whole range with the Unchecked memory accessors, which on
non-masked builds do a raw pointer dereference with no bounds check
at all. Added the missing return.
- DisassemblyLineInfo::ToString()'s snprintf calls all used
sizeof(text) where text is a char* parameter (pointer size, not
buffer size), silently truncating all output to a few characters
instead of using the real bufSize parameter that was passed in but
never used.
- analyze()'s misaligned-tail-data case stored the DisassemblyData
entry under key alignedNext, but constructed it with the earlier
(possibly much earlier) `address` as its own base address instead of
alignedNext, misattributing those bytes to the wrong location.
DisassemblyManager used to fuse lui+addiu/load/store into single pseudo-
instructions ("li", fused loads/stores) for display. This only applied to a
handful of opcodes, complicated DisassemblyManager, and was the root cause of
a stepping bug: Core_PerformCPUStep's Into/Over cases treated stepSize as a
byte count, while the WebSocket cpu.stepInto handler computed it as an
instruction count (needed to step over a whole fused macro in one go) - so a
plain, non-fused stepInto silently executed zero instructions.
Removed the fusion logic entirely (DisassemblyMacro, DISTYPE_MACRO) - every
disassembly line is now exactly one 4-byte instruction. With that,
"how many instructions does this line span" is always 1, so the
getInstructionSizeAt() byte-size queries in the legacy Windows and ImGui
debuggers are gone too; step requests just pass 1. Core_RequestCPUStep's
stepSize is now consistently in instructions everywhere.
Also fixes the PPSSPPHeadless build, broken since 0ed1f3e added
OpenWebDebugger() (which calls System_LaunchUrl) without a headless stub.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hqm11k99viLfbJm2MkH4BH
When sending this in the websocket debugger, it needs to be valid utf-8 or
it will cause clients to abort the connection. We want to reject invalid
utf-8 anyway.