Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 59e3686

Browse files
authored
Prevent compiler optimization that could cause local var values to change in multithreaded environments, in some places (#16089) (#16093)
1 parent 89f1279 commit 59e3686

File tree

5 files changed

+55
-19
lines changed

5 files changed

+55
-19
lines changed

src/vm/syncblk.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2888,7 +2888,7 @@ void AwareLock::Enter()
28882888
CONTRACTL_END;
28892889

28902890
Thread *pCurThread = GetThread();
2891-
LockState state = m_lockState;
2891+
LockState state = m_lockState.VolatileLoadWithoutBarrier();
28922892
if (!state.IsLocked() || m_HoldingThread != pCurThread)
28932893
{
28942894
if (m_lockState.InterlockedTryLock_Or_RegisterWaiter(this, state))
@@ -2950,7 +2950,7 @@ BOOL AwareLock::TryEnter(INT32 timeOut)
29502950
pCurThread->HandleThreadAbort();
29512951
}
29522952

2953-
LockState state = m_lockState;
2953+
LockState state = m_lockState.VolatileLoadWithoutBarrier();
29542954
if (!state.IsLocked() || m_HoldingThread != pCurThread)
29552955
{
29562956
if (timeOut == 0

src/vm/syncblk.h

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,12 @@ class AwareLock
386386
}
387387

388388
public:
389+
LockState VolatileLoadWithoutBarrier() const
390+
{
391+
WRAPPER_NO_CONTRACT;
392+
return ::VolatileLoadWithoutBarrier(&m_state);
393+
}
394+
389395
LockState VolatileLoad() const
390396
{
391397
WRAPPER_NO_CONTRACT;
@@ -423,7 +429,27 @@ class AwareLock
423429
friend class LockState;
424430

425431
private:
432+
// Take care to use 'm_lockState.VolatileLoadWithoutBarrier()` when loading this value into a local variable that will be
433+
// reused. That prevents an optimization in the compiler that avoids stack-spilling a value loaded from memory and instead
434+
// reloads the value from the original memory location under the assumption that it would not be changed by another thread,
435+
// which can result in the local variable's value changing between reads if the memory location is modifed by another
436+
// thread. This is important for patterns such as:
437+
//
438+
// T x = m_x; // no barrier
439+
// if (meetsCondition(x))
440+
// {
441+
// assert(meetsCondition(x)); // This may fail!
442+
// }
443+
//
444+
// The code should be written like this instead:
445+
//
446+
// T x = VolatileLoadWithoutBarrier(&m_x); // compile-time barrier, no run-time barrier
447+
// if (meetsCondition(x))
448+
// {
449+
// assert(meetsCondition(x)); // This will not fail
450+
// }
426451
LockState m_lockState;
452+
427453
ULONG m_Recursion;
428454
PTR_Thread m_HoldingThread;
429455

@@ -482,13 +508,13 @@ class AwareLock
482508
UINT32 GetLockState() const
483509
{
484510
WRAPPER_NO_CONTRACT;
485-
return m_lockState.GetState();
511+
return m_lockState.VolatileLoadWithoutBarrier().GetState();
486512
}
487513

488514
bool IsUnlockedWithNoWaiters() const
489515
{
490516
WRAPPER_NO_CONTRACT;
491-
return m_lockState.IsUnlockedWithNoWaiters();
517+
return m_lockState.VolatileLoadWithoutBarrier().IsUnlockedWithNoWaiters();
492518
}
493519

494520
UINT32 GetMonitorHeldStateVolatile() const

src/vm/syncblk.inl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
FORCEINLINE bool AwareLock::LockState::InterlockedTryLock()
1212
{
1313
WRAPPER_NO_CONTRACT;
14-
return InterlockedTryLock(*this);
14+
return InterlockedTryLock(VolatileLoadWithoutBarrier());
1515
}
1616

1717
FORCEINLINE bool AwareLock::LockState::InterlockedTryLock(LockState state)
@@ -80,7 +80,7 @@ FORCEINLINE bool AwareLock::LockState::InterlockedUnlock()
8080
FORCEINLINE bool AwareLock::LockState::InterlockedTrySetShouldNotPreemptWaitersIfNecessary(AwareLock *awareLock)
8181
{
8282
WRAPPER_NO_CONTRACT;
83-
return InterlockedTrySetShouldNotPreemptWaitersIfNecessary(awareLock, *this);
83+
return InterlockedTrySetShouldNotPreemptWaitersIfNecessary(awareLock, VolatileLoadWithoutBarrier());
8484
}
8585

8686
FORCEINLINE bool AwareLock::LockState::InterlockedTrySetShouldNotPreemptWaitersIfNecessary(
@@ -178,7 +178,7 @@ FORCEINLINE AwareLock::EnterHelperResult AwareLock::LockState::InterlockedTry_Lo
178178
WRAPPER_NO_CONTRACT;
179179

180180
// This function is called from inside a spin loop, it must unregister the spinner if and only if the lock is acquired
181-
LockState state = *this;
181+
LockState state = VolatileLoadWithoutBarrier();
182182
while (true)
183183
{
184184
_ASSERTE(state.HasAnySpinners());
@@ -288,7 +288,7 @@ FORCEINLINE void AwareLock::LockState::InterlockedUnregisterWaiter()
288288
{
289289
WRAPPER_NO_CONTRACT;
290290

291-
LockState state = *this;
291+
LockState state = VolatileLoadWithoutBarrier();
292292
while (true)
293293
{
294294
_ASSERTE(state.HasAnyWaiters());
@@ -319,7 +319,7 @@ FORCEINLINE bool AwareLock::LockState::InterlockedTry_LockAndUnregisterWaiterAnd
319319
// This function is called from the waiter's spin loop and should observe the wake signal only if the lock is taken, to
320320
// prevent a lock releaser from waking another waiter while one is already spinning to acquire the lock
321321
bool waiterStarvationStartTimeWasRecorded = false;
322-
LockState state = *this;
322+
LockState state = VolatileLoadWithoutBarrier();
323323
while (true)
324324
{
325325
_ASSERTE(state.HasAnyWaiters());
@@ -502,7 +502,7 @@ FORCEINLINE AwareLock::EnterHelperResult AwareLock::TryEnterBeforeSpinLoopHelper
502502
MODE_ANY;
503503
} CONTRACTL_END;
504504

505-
LockState state = m_lockState;
505+
LockState state = m_lockState.VolatileLoadWithoutBarrier();
506506

507507
// Check the recursive case once before the spin loop. If it's not the recursive case in the beginning, it will not
508508
// be in the future, so the spin loop can avoid checking the recursive case.
@@ -685,7 +685,7 @@ FORCEINLINE AwareLock::LeaveHelperAction AwareLock::LeaveHelper(Thread* pCurThre
685685
if (m_HoldingThread != pCurThread)
686686
return AwareLock::LeaveHelperAction_Error;
687687

688-
_ASSERTE(m_lockState.IsLocked());
688+
_ASSERTE(m_lockState.VolatileLoadWithoutBarrier().IsLocked());
689689
_ASSERTE(m_Recursion >= 1);
690690

691691
#if defined(_DEBUG) && defined(TRACK_SYNC) && !defined(CROSSGEN_COMPILE)

src/vm/synch.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ bool CLRLifoSemaphore::WaitForSignal(DWORD timeoutMs)
644644

645645
_ASSERTE(timeoutMs != 0);
646646
_ASSERTE(m_handle != nullptr);
647-
_ASSERTE(m_counts.waiterCount != (UINT16)0);
647+
_ASSERTE(m_counts.VolatileLoadWithoutBarrier().waiterCount != (UINT16)0);
648648

649649
while (true)
650650
{
@@ -680,7 +680,7 @@ bool CLRLifoSemaphore::WaitForSignal(DWORD timeoutMs)
680680
}
681681

682682
// Unregister the waiter if this thread will not be waiting anymore, and try to acquire the semaphore
683-
Counts counts = m_counts;
683+
Counts counts = m_counts.VolatileLoadWithoutBarrier();
684684
while (true)
685685
{
686686
_ASSERTE(counts.waiterCount != (UINT16)0);
@@ -719,7 +719,7 @@ bool CLRLifoSemaphore::Wait(DWORD timeoutMs)
719719
_ASSERTE(m_handle != nullptr);
720720

721721
// Acquire the semaphore or register as a waiter
722-
Counts counts = m_counts;
722+
Counts counts = m_counts.VolatileLoadWithoutBarrier();
723723
while (true)
724724
{
725725
_ASSERTE(counts.signalCount <= m_maximumSignalCount);
@@ -762,7 +762,7 @@ bool CLRLifoSemaphore::Wait(DWORD timeoutMs, UINT32 spinCount, UINT32 processorC
762762
}
763763

764764
// Try to acquire the semaphore or register as a spinner
765-
Counts counts = m_counts;
765+
Counts counts = m_counts.VolatileLoadWithoutBarrier();
766766
while (true)
767767
{
768768
Counts newCounts = counts;
@@ -809,7 +809,7 @@ bool CLRLifoSemaphore::Wait(DWORD timeoutMs, UINT32 spinCount, UINT32 processorC
809809
ClrSleepEx(0, false);
810810

811811
// Try to acquire the semaphore and unregister as a spinner
812-
counts = m_counts;
812+
counts = m_counts.VolatileLoadWithoutBarrier();
813813
while (true)
814814
{
815815
_ASSERTE(counts.spinnerCount != (UINT8)0);
@@ -868,7 +868,7 @@ bool CLRLifoSemaphore::Wait(DWORD timeoutMs, UINT32 spinCount, UINT32 processorC
868868
}
869869

870870
// Try to acquire the semaphore and unregister as a spinner
871-
counts = m_counts;
871+
counts = m_counts.VolatileLoadWithoutBarrier();
872872
while (true)
873873
{
874874
_ASSERTE(counts.spinnerCount != (UINT8)0);
@@ -893,7 +893,7 @@ bool CLRLifoSemaphore::Wait(DWORD timeoutMs, UINT32 spinCount, UINT32 processorC
893893
#endif // _TARGET_ARM64_
894894

895895
// Unregister as a spinner, and acquire the semaphore or register as a waiter
896-
counts = m_counts;
896+
counts = m_counts.VolatileLoadWithoutBarrier();
897897
while (true)
898898
{
899899
_ASSERTE(counts.spinnerCount != (UINT8)0);
@@ -934,7 +934,7 @@ void CLRLifoSemaphore::Release(INT32 releaseCount)
934934
_ASSERTE(m_handle != INVALID_HANDLE_VALUE);
935935

936936
INT32 countOfWaitersToWake;
937-
Counts counts = m_counts;
937+
Counts counts = m_counts.VolatileLoadWithoutBarrier();
938938
while (true)
939939
{
940940
Counts newCounts = counts;

src/vm/synch.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,12 @@ class CLRLifoSemaphore
219219
return *this;
220220
}
221221

222+
Counts VolatileLoadWithoutBarrier() const
223+
{
224+
LIMITED_METHOD_CONTRACT;
225+
return ::VolatileLoadWithoutBarrier(&data);
226+
}
227+
222228
Counts CompareExchange(Counts toCounts, Counts fromCounts)
223229
{
224230
LIMITED_METHOD_CONTRACT;
@@ -264,7 +270,11 @@ class CLRLifoSemaphore
264270

265271
private:
266272
BYTE __padding1[MAX_CACHE_LINE_SIZE]; // padding to ensure that m_counts gets its own cache line
273+
274+
// Take care to use 'm_counts.VolatileLoadWithoutBarrier()` when loading this value into a local variable that will be
275+
// reused. See AwareLock::m_lockState for details.
267276
Counts m_counts;
277+
268278
BYTE __padding2[MAX_CACHE_LINE_SIZE]; // padding to ensure that m_counts gets its own cache line
269279

270280
#if defined(DEBUG)

0 commit comments

Comments
 (0)