summaryrefslogtreecommitdiff
path: root/src/md/winmd/inc/memotable.h
blob: df6823d184425443acad2e7bdb82089a709ccdfc (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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#ifndef __MDMemoTable__h__
#define __MDMemoTable__h__


//========================================================================================
// A MemoTable is a 0-based N element array designed for safe multithreaded access
// and single-initialization rules (e.g. multiple threads can race to initialize an entry
// but only the first one actually modifies the table.)
// 
// Template parameters:
//
//    ELEMTYPE   - Type of the data stored in the array. This type must be small enough
//                 to be supported by InterlockedCompareExchange.
//
//    INITVALUE  - Sentinel that indicates that InitEntry() has not been called for that entry yet.
//                 This is the "initial value" for all elements of the array.
//
//    ELEMDELETER - (set to NULL if ELEMTYPE does not represent allocated storage.) 
//                 A function that deallocates the memory referenced by a pointer-typed ELEMTYPE.
//                 MemoTable will never pass INITVALUE to this function.
//
//                 Restriction: if ELEMDELETER != NULL, ELEMTYPE must be a C++ pointer type
//                   (annoying template weirdness with InterlockedCompareExchangeT that
//                   life is too short to disentangle.)
//========================================================================================
template <typename ELEMTYPE, void (*ELEMDELETER)(ELEMTYPE)>
class MemoTable
{
public:
    MemoTable(ULONG numElems, ELEMTYPE initValue);
    ~MemoTable();
    HRESULT GetEntry(ULONG index, /* [out] */ ELEMTYPE *pElem);
    HRESULT InitEntry(ULONG index, /* [in,out] */ ELEMTYPE *pElem);

private:
    MemoTable(const MemoTable &other);
    MemoTable& operator=(const MemoTable &other);

    const ULONG     m_numElems;
    const ELEMTYPE  m_initValue;
    ELEMTYPE        *m_pTable;     // Lazily allocated 0-based array with m_numElems elements.
};




//==============================================================================
// MemoTable::MemoTable()
//==============================================================================
template <typename ELEMTYPE, void (*ELEMDELETER)(ELEMTYPE)>
MemoTable<ELEMTYPE, ELEMDELETER>::MemoTable(ULONG numElems, ELEMTYPE initValue) :
    m_numElems(numElems),
    m_initValue(initValue)
{
    m_pTable = NULL;
}



//==============================================================================
// MemoTable::~MemoTable()
//==============================================================================
template <typename ELEMTYPE, void (*ELEMDELETER)(ELEMTYPE)>
MemoTable<ELEMTYPE, ELEMDELETER>::~MemoTable()
{
    if (m_pTable != NULL)
    {
        if (ELEMDELETER != NULL)
        {
            for (ULONG index = 0; index < m_numElems; index++)
            {
                if (m_pTable[index] != m_initValue)
                {
                    ELEMDELETER(m_pTable[index]);
                }
            }
        }
        delete [] m_pTable;
    }
}

//==============================================================================
// HRESULT MemoTable::GetEntry(ULONG index, [out] ELEMTYPE *pElem)
//
// Retrieves the element at the specified index.
//
// Returns:
//   S_OK                   if returned element is not INITVALUE
//   S_FALSE                if returned element is INITVALUE
//   CDLB_E_INDEX_NOT_FOUND if index is out of range.
//==============================================================================
template <typename ELEMTYPE, void (*ELEMDELETER)(ELEMTYPE)>
HRESULT MemoTable<ELEMTYPE, ELEMDELETER>::GetEntry(ULONG index, /* [out] */ ELEMTYPE *pElem)
{
    _ASSERTE(pElem);
    if (index >= m_numElems)
    {
        return CLDB_E_INDEX_NOTFOUND;
    }
    if (m_pTable == NULL)
    {
        *pElem = m_initValue;
        return S_FALSE;
    }
    ELEMTYPE elem = m_pTable[index];
    *pElem = elem;
    return (elem == m_initValue) ? S_FALSE : S_OK;
}

//==============================================================================
// HRESULT MemoTable::InitEntry(ULONG index, [in, out] ELEMTYPE *pElem)
//
// Initalizes the elment at the specified index. It is illegal to attempt
// to initialize to INITVALUE. For scalar tables, it is illegal to attempt
// to overwrite a previously initialized entry with a different value.
// For pointer tables, the entry will be initialized using InterlockedCompareExchangeT
// and your new value thrown away via ELEMDELETER if you lose the race to initialize.
//
// Returns:
//    *pElem overwritten with the actual value written into the table 
//           (for pointer tables, this may not be the original pointer you passed
//           due to races.)
//    
//    S_OK is the only success value.
//==============================================================================
template <typename ELEMTYPE, void (*ELEMDELETER)(ELEMTYPE)>
HRESULT MemoTable<ELEMTYPE, ELEMDELETER>::InitEntry(ULONG index, /* [in,out] */ ELEMTYPE *pElem)
{
    // This can cause allocations (thus entering the host) during a profiler stackwalk.
    // But we're ok since we're not supporting SQL/F1 profiling with WinMDs. FUTURE:
    // Would be nice to eliminate allocations on stack walks regardless.
    PERMANENT_CONTRACT_VIOLATION(HostViolation, ReasonUnsupportedForSQLF1Profiling);

    HRESULT hr = S_OK;
    _ASSERTE(pElem);
    ELEMTYPE incomingElem = *pElem;

    if (index >= m_numElems)
    {
        IfFailGo(CLDB_E_INDEX_NOTFOUND);
    }
    _ASSERTE(incomingElem != m_initValue); 

    // If this is first call to InitEntry(), must initialize the table itself.
    if (m_pTable == NULL)
    {
        NewHolder<ELEMTYPE> pNewTable = NULL;

        if ((((size_t)(-1)) / sizeof(ELEMTYPE)) < m_numElems)
        {
            IfFailGo(E_OUTOFMEMORY);
        }
        
        // When loading NGen images we go through code paths that expect no faults and no
        // throws.  We will need to take a look at how we use the winmd metadata with ngen,
        // potentially storing the post-mangled metadata in the NI because as the adapter grows
        // we'll see more of these.
        CONTRACT_VIOLATION(FaultViolation);
        pNewTable = new (nothrow) ELEMTYPE[m_numElems];
        IfNullGo(pNewTable);
        
        for (ULONG walk = 0; walk < m_numElems; walk++)
        {
            pNewTable[walk] = m_initValue;
        }
        if (InterlockedCompareExchangeT<ELEMTYPE *>(&m_pTable, pNewTable, NULL) == NULL)
        {   // We won the initialization race
            pNewTable.SuppressRelease();
        }
    }


    //-------------------------------------------------------------------------
    // Cannot fail after this point, or we may delete *pElem after entering it into table.
    //-------------------------------------------------------------------------
    hr = S_OK;
    if (ELEMDELETER == NULL)
    {
        _ASSERTE(m_pTable[index] == m_initValue || m_pTable[index] == incomingElem);
        m_pTable[index] = incomingElem;
    }
    else
    {
        ELEMTYPE winner = InterlockedCompareExchangeT((ELEMTYPE volatile *)&m_pTable[index], incomingElem, m_initValue);
        if (winner != m_initValue)
        {
            ELEMDELETER(incomingElem); // Lost the race
            *pElem = winner; 
        }
    }
    _ASSERTE(*pElem != m_initValue);

ErrExit:
    if (ELEMDELETER != NULL && FAILED(hr))
    {
        ELEMDELETER(*pElem);
        *pElem = m_initValue;
    }
    return hr;
}

#endif // __MDMemoTable__h__