Welcome to mirror list, hosted at ThFree Co, Russian Federation.

castcache.cpp « vm « coreclr « src - github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: bc5831a0a7ea154e9addf1c1cfc6dedb48cade5b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//
// File: castcache.cpp
//

#include "common.h"
#include "castcache.h"

#if !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)

BASEARRAYREF* CastCache::s_pTableRef = NULL;
OBJECTHANDLE CastCache::s_sentinelTable = NULL;
DWORD CastCache::s_lastFlushSize     = INITIAL_CACHE_SIZE;

BASEARRAYREF CastCache::CreateCastCache(DWORD size)
{
    CONTRACTL
    {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    }
    CONTRACTL_END;

    // size must be positive
    _ASSERTE(size > 1);
    // size must be a power of two
    _ASSERTE((size & (size - 1)) == 0);

    BASEARRAYREF table = NULL;

    // if we get an OOM here, we try a smaller size
    EX_TRY
    {
        FAULT_NOT_FATAL();
        table = (BASEARRAYREF)AllocatePrimitiveArray(CorElementType::ELEMENT_TYPE_I4, (size + 1) * sizeof(CastCacheEntry) / sizeof(INT32));
    }
    EX_CATCH
    {
    }
    EX_END_CATCH(RethrowTerminalExceptions)

    if (!table)
    {
        size = INITIAL_CACHE_SIZE;
        // if we get an OOM again we return NULL
        EX_TRY
        {
            FAULT_NOT_FATAL();
            table = (BASEARRAYREF)AllocatePrimitiveArray(CorElementType::ELEMENT_TYPE_I4, (size + 1) * sizeof(CastCacheEntry) / sizeof(INT32));
        }
        EX_CATCH
        {
        }
        EX_END_CATCH(RethrowTerminalExceptions)

        if (!table)
        {
            // OK, no cache then
            return NULL;
        }
    }

    DWORD* tableData = TableData(table);
    TableMask(tableData) = size - 1;

    // Fibonacci hash reduces the value into desired range by shifting right by the number of leading zeroes in 'size-1'
    DWORD bitCnt;
#if HOST_64BIT
    BitScanReverse64(&bitCnt, size - 1);
    HashShift(tableData) = (BYTE)(63 - bitCnt);
#else
    BitScanReverse(&bitCnt, size - 1);
    HashShift(tableData) = (BYTE)(31 - bitCnt);
#endif

    return table;
}

BOOL CastCache::MaybeReplaceCacheWithLarger(DWORD size)
{
    CONTRACTL
    {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    }
    CONTRACTL_END;

    BASEARRAYREF newTable = CreateCastCache(size);
    if (!newTable)
    {
        return FALSE;
    }

    SetObjectReference((OBJECTREF *)s_pTableRef, newTable);
    return TRUE;
}

void CastCache::FlushCurrentCache()
{
    CONTRACTL
    {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    }
    CONTRACTL_END;

    DWORD* tableData = TableData(*s_pTableRef);
    s_lastFlushSize = max(INITIAL_CACHE_SIZE, CacheElementCount(tableData));

    SetObjectReference((OBJECTREF *)s_pTableRef, ObjectFromHandle(s_sentinelTable));
}

void CastCache::Initialize()
{
    CONTRACTL
    {
        THROWS;
        GC_TRIGGERS;
        MODE_ANY;
    }
    CONTRACTL_END;

    FieldDesc* pTableField = CoreLibBinder::GetField(FIELD__CASTHELPERS__TABLE);

    GCX_COOP();
    s_pTableRef = (BASEARRAYREF*)pTableField->GetCurrentStaticAddress();

    BASEARRAYREF sentinelTable = CreateCastCache(2);
    if (!sentinelTable)
    {
        // no memory for 2 element cache while initializing?
        ThrowOutOfMemory();
    }

    s_sentinelTable = CreateGlobalHandle(sentinelTable);

    // initialize to the sentinel value, this should not be null.
    SetObjectReference((OBJECTREF *)s_pTableRef, sentinelTable);
}

TypeHandle::CastResult CastCache::TryGet(TADDR source, TADDR target)
{
    CONTRACTL
    {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    }
    CONTRACTL_END;

    DWORD* tableData = TableData(*s_pTableRef);

    DWORD index = KeyToBucket(tableData, source, target);
    for (DWORD i = 0; i < BUCKET_SIZE;)
    {
        CastCacheEntry* pEntry = &Elements(tableData)[index];

        // must read in this order: version -> [entry parts] -> version
        // if version is odd or changes, the entry is inconsistent and thus ignored
        DWORD version1 = VolatileLoad(&pEntry->version);
        TADDR entrySource = pEntry->source;

        // mask the lower version bit to make it even.
        // This way we can check if version is odd or changing in just one compare.
        version1 &= ~1;

        if (entrySource == source)
        {
            TADDR entryTargetAndResult = pEntry->targetAndResult;
            // target never has its lower bit set.
            // a matching entryTargetAndResult would have the same bits, except for the lowest one, which is the result.
            entryTargetAndResult ^= target;
            if (entryTargetAndResult <= 1)
            {
                // make sure 'version' is loaded after 'source' and 'targetAndResults'
                VolatileLoadBarrier();
                if (version1 != pEntry->version)
                {
                    // oh, so close, the entry is in inconsistent state.
                    // it is either changing or has changed while we were reading.
                    // treat it as a miss.
                    break;
                }

                return TypeHandle::CastResult(entryTargetAndResult);
            }
        }

        if (version1 == 0)
        {
            // the rest of the bucket is unclaimed, no point to search further
            break;
        }

        // quadratic reprobe
        i++;
        index = (index + i) & TableMask(tableData);
    }

    return TypeHandle::MaybeCast;
}

void CastCache::TrySet(TADDR source, TADDR target, BOOL result)
{
    CONTRACTL
    {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    }
    CONTRACTL_END;

    DWORD bucket;
    DWORD* tableData;

    do
    {
        tableData = TableData(*s_pTableRef);
        if (TableMask(tableData) == 1)
        {
            // 2-element table is used as a sentinel.
            // we did not allocate a real table yet or have flushed it.
            // try replacing the table, but do not insert anything.
            MaybeReplaceCacheWithLarger(s_lastFlushSize);
            return;
        }

        bucket = KeyToBucket(tableData, source, target);
        DWORD index = bucket;
        CastCacheEntry* pEntry = &Elements(tableData)[index];

        for (DWORD i = 0; i < BUCKET_SIZE;)
        {
            // claim the entry if unused or is more distant than us from its origin.
            // Note - someone familiar with Robin Hood hashing will notice that
            //        we do the opposite - we are "robbing the poor".
            //        Robin Hood strategy improves average lookup in a lossles dictionary by reducing
            //        outliers via giving preference to more distant entries.
            //        What we have here is a lossy cache with outliers bounded by the bucket size.
            //        We improve average lookup by giving preference to the "richer" entries.
            //        If we used Robin Hood strategy we could eventually end up with all
            //        entries in the table being maximally "poor".

            // VolatileLoadWithoutBarrier is to ensure that the version cannot be re-fetched between here and CompareExchange.
            DWORD version = VolatileLoadWithoutBarrier(&pEntry->version);

            // mask the lower version bit to make it even.
            // This way we will detect both if version is changing (odd) or has changed (even, but different).
            version &= ~1;

            if ((version & VERSION_NUM_MASK) >= (VERSION_NUM_MASK - 2))
            {
                // If exactly VERSION_NUM_MASK updates happens between here and publishing, we may not recognise a race.
                // It is extremely unlikely, but to not worry about the possibility, lets not allow version to go this high and just get a new cache.
                // This will not happen often.
                FlushCurrentCache();
                return;
            }

            if (version == 0 || (version >> VERSION_NUM_SIZE) > i)
            {
                DWORD newVersion = (i << VERSION_NUM_SIZE) + (version & VERSION_NUM_MASK) + 1;
                DWORD versionOrig = InterlockedCompareExchangeT(&pEntry->version, newVersion, version);
                if (versionOrig == version)
                {
                    pEntry->SetEntry(source, target, result);

                    // entry is in inconsistent state and cannot be read or written to until we
                    // update the version, which is the last thing we do here
                    VolatileStore(&pEntry->version, newVersion + 1);
                    return;
                }
                // someone snatched the entry. try the next one in the bucket.
            }

            if (pEntry->Source() == source && pEntry->Target() == target)
            {
                // looks like we already have an entry for this.
                // duplicate entries are harmless, but a bit of a waste.
                return;
            }

            // quadratic reprobe
            i++;
            index += i;
            pEntry = &Elements(tableData)[index & TableMask(tableData)];
        }

        // bucket is full.
    } while (TryGrow(tableData));

    // reread tableData after TryGrow.
    tableData = TableData(*s_pTableRef);
    if (TableMask(tableData) == 1)
    {
        // do not insert into a sentinel.
        return;
    }

    // pick a victim somewhat randomly within a bucket
    // NB: ++ is not interlocked. We are ok if we lose counts here. It is just a number that changes.
    DWORD victimDistance = VictimCounter(tableData)++ & (BUCKET_SIZE - 1);
    // position the victim in a quadratic reprobe bucket
    DWORD victim = (victimDistance * victimDistance + victimDistance) / 2;

    {
        CastCacheEntry* pEntry = &Elements(tableData)[(bucket + victim) & TableMask(tableData)];

        // VolatileLoadWithoutBarrier is to ensure that the version cannot be re-fetched between here and CompareExchange.
        DWORD version = VolatileLoadWithoutBarrier(&pEntry->version);

        // mask the lower version bit to make it even.
        // This way we will detect both if version is changing (odd) or has changed (even, but different).
        version &= ~1;

        if ((version & VERSION_NUM_MASK) >= (VERSION_NUM_MASK - 2))
        {
            // If exactly VERSION_NUM_MASK updates happens between here and publishing, we may not recognise a race.
            // It is extremely unlikely, but to not worry about the possibility, lets not allow version to go this high and just get a new cache.
            // This will not happen often.
            FlushCurrentCache();
            return;
        }

        DWORD newVersion = (victimDistance << VERSION_NUM_SIZE) + (version & VERSION_NUM_MASK) + 1;
        DWORD versionOrig = InterlockedCompareExchangeT(&pEntry->version, newVersion, version);

        if (versionOrig == version)
        {
            pEntry->SetEntry(source, target, result);
            VolatileStore(&pEntry->version, newVersion + 1);
        }
    }
}

#endif // !DACCESS_COMPILE && !CROSSGEN_COMPILE