Skip to content

Commit a5d5932

Browse files
committed
Incomplete read ordering in CastCache::Get
1 parent 6ed9f1b commit a5d5932

File tree

3 files changed

+35
-4
lines changed

3 files changed

+35
-4
lines changed

src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/CastHelpers.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ private static CastResult TryGet(nuint source, nuint target)
113113
{
114114
ref CastCacheEntry pEntry = ref Element(ref tableData, index);
115115

116-
// must read in this order: version -> entry parts -> version
116+
// must read in this order: version -> [entry parts] -> version
117117
// if version is odd or changes, the entry is inconsistent and thus ignored
118118
int version = Volatile.Read(ref pEntry._version);
119119
nuint entrySource = pEntry._source;
@@ -124,12 +124,20 @@ private static CastResult TryGet(nuint source, nuint target)
124124

125125
if (entrySource == source)
126126
{
127-
nuint entryTargetAndResult = Volatile.Read(ref pEntry._targetAndResult);
127+
nuint entryTargetAndResult = pEntry._targetAndResult;
128128
// target never has its lower bit set.
129129
// a matching entryTargetAndResult would the have same bits, except for the lowest one, which is the result.
130130
entryTargetAndResult ^= target;
131131
if (entryTargetAndResult <= 1)
132132
{
133+
// make sure 'version' is loaded after 'source' and 'targetAndResults'
134+
#if TARGET_ARM64 || TARGET_ARM
135+
// We can either:
136+
// - use acquires for both _source and _targetAndResults or
137+
// - issue a full fence before reading _version
138+
// benchmarks on available hardware show that use of full fence here is cheaper.
139+
Interlocked.MemoryBarrier();
140+
#endif
133141
if (version != pEntry._version)
134142
{
135143
// oh, so close, the entry is in inconsistent state.

src/coreclr/src/inc/volatile.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,27 @@ void VolatileStoreWithoutBarrier(T* pt, T val)
286286
#endif
287287
}
288288

289+
//
290+
// Memory ordering barrier that waits for loads in progress to complete.
291+
// Any effects of loads or stores that appear after, in program order, will "happen after" relative to this.
292+
// Other operations such as computation or instruction prefetch are not affected.
293+
//
294+
// Architectural mapping:
295+
// arm64 : dmb ishld
296+
// arm : dmb ish
297+
// x86/64 : compiler fence
298+
inline
299+
void VolatileLoadBarrier()
300+
{
301+
#if defined(HOST_ARM64) && defined(__GNUC__)
302+
asm volatile ("dmb ishld" : : : "memory");
303+
#elif defined(HOST_ARM64) && defined(_MSC_VER)
304+
__dmb(_ARM64_BARRIER_ISHLD);
305+
#else
306+
VOLATILE_MEMORY_BARRIER();
307+
#endif
308+
}
309+
289310
//
290311
// Volatile<T> implements accesses with our volatile semantics over a variable of type T.
291312
// Wherever you would have used a "volatile Foo" or, equivalently, "Foo volatile", use Volatile<Foo>

src/coreclr/src/vm/castcache.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ TypeHandle::CastResult CastCache::TryGet(TADDR source, TADDR target)
160160
{
161161
CastCacheEntry* pEntry = &Elements(tableData)[index];
162162

163-
// must read in this order: version -> entry parts -> version
163+
// must read in this order: version -> [entry parts] -> version
164164
// if version is odd or changes, the entry is inconsistent and thus ignored
165165
DWORD version1 = VolatileLoad(&pEntry->version);
166166
TADDR entrySource = pEntry->source;
@@ -171,12 +171,14 @@ TypeHandle::CastResult CastCache::TryGet(TADDR source, TADDR target)
171171

172172
if (entrySource == source)
173173
{
174-
TADDR entryTargetAndResult = VolatileLoad(&pEntry->targetAndResult);
174+
TADDR entryTargetAndResult = pEntry->targetAndResult;
175175
// target never has its lower bit set.
176176
// a matching entryTargetAndResult would have the same bits, except for the lowest one, which is the result.
177177
entryTargetAndResult ^= target;
178178
if (entryTargetAndResult <= 1)
179179
{
180+
// make sure 'version' is loaded after 'source' and 'targetAndResults'
181+
VolatileLoadBarrier();
180182
if (version1 != pEntry->version)
181183
{
182184
// oh, so close, the entry is in inconsistent state.

0 commit comments

Comments
 (0)