Repository navigation
fix(milesaudiomanager): Prevent undefined behavior when passing atomic storage to the Interlocked functions - #3667
Conversation
…Interlocked functions PlayingAudio::m_status was a PlayingStatus enum that the audio manager cast to volatile long for InterlockedCompareExchange, and MPSCIntrusiveQueue cast its T* head to void* for the pointer variants. Declare both as std::atomic from atomic_adapter.h, which uses the same Interlocked functions on VC6, so no storage is accessed through another type. A file-static advancePlayingStatus does the compare exchange and returns the previous status, as InterlockedCompareExchange did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vancePlayingStatus Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. WalkthroughThe audio status field and MPSC queue head now use ChangesAudio Status Transitions
MPSC Queue Atomics
Estimated code review effort: 2 (Simple) | ~15 minutes Change: Refactor Suggested labels: Suggested reviewers: Merge Risk:
|
Follow-up to #3658, from xezon's "Are there more of these?" on #3580.
MilesAudioManagerandMPSCIntrusiveQueuepass their storage to theInterlocked*functions throughreinterpret_cast<volatile long*>andreinterpret_cast<void* volatile*>. APlayingStatusis not alongand aT*is not avoid*, so those accesses are undefined behavior. Since #3325,Utility/atomic_adapter.hprovidesstd::atomicfor every compiler, VC6 included, so the casts are no longer needed.PlayingAudio::m_statusis now astd::atomic<PlayingStatus>. The three places that advance the status call a new file-staticadvancePlayingStatus, which wrapscompare_exchange_strongand returns the previous status likeInterlockedCompareExchangedid:The
static_assertthatm_statushas the size of alongis removed, since nothing casts it to one any more.MPSCIntrusiveQueue::m_headis now astd::atomic<T*>.Pushusescompare_exchange_weakandFlushusesexchange, in place ofInterlockedCompareExchangePointerandInterlockedExchangePointer.The memory ordering stays sequentially consistent, as with the
Interlocked*functions.Evidence
MilesAudioManager.objandW3DScreenshot.obj(the only user ofMPSCIntrusiveQueue) for both games against a build ofmain.VS 2026 (ClangCL) Release: the code is the same apart from layout.
MilesAudioManager::pauseAudiomerges two copies of its loop into one, and callsgetAudioEventInfoin the same places.screenshotThreadFuncchanges only in register use. The dynamic initializer of the screenshot queue is gone, becausestd::atomic<T*>is zero-initialized at compile time.VC6 Release: the adapter's member functions are not inlined.
m_status, such asm_status == PS_Playing, now callsatomic::load_stored, which calls the adapter'sReadAcquire, a plainmov. Onmainit was one inline load.stopPlayingAudiois inlined into its callers, such asprocessPlayingListandstopAudio, a status change now callsatomic::compare_exchange, which callsInterlockedCompareExchange. Onmainit calledInterlockedCompareExchangedirectly. InstopPlayingAudio,notifyOfAudioCompletionandstartNextLoop, the call is the same as onmain.PushandFlushcall the adapter'sInterlockedCompareExchangeandInterlockedExchangein place of the pointer versions.The extra calls are in the loops over the playing sounds, and add no locked instructions.
AI Disclosure
Claude Opus 5.5 was used to aid in implementation and to compare the disassembly.