summaryrefslogtreecommitdiff
path: root/src/md/runtime/recordpool.cpp
blob: da4bcbc9e934ed471f59597ccbd60d23b039d8fd (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
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
// 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.
//*****************************************************************************
// RecordPool.cpp -- Implementation of record heaps.
// 

//
//*****************************************************************************
#include "stdafx.h"
#include <recordpool.h>

#define RECORDPOOL_GROW_FACTOR 8
#define RECORDPOOL_GROW_MAX 2048
#define RECORDPOOL_GROW_MINROWS 2
#define RECORDPOOL_GROW_DEFAULTROWS 16

HRESULT 
RecordPool::InitNew(
    UINT32 cbRec,       // Record size.
    UINT32 cRecsInit)   // Initial guess of count of record.
{
    HRESULT  hr;
    S_UINT32 cbGrow;    // Initial grow size of the pool.
    
    // Size of each record is fixed.
    m_cbRec = cbRec;
    
    if (cRecsInit > 0)
    {
        cbGrow = S_UINT32(cbRec) * S_UINT32(cRecsInit);
    }
    else
    {
        cbGrow = S_UINT32(cbRec) * S_UINT32(RECORDPOOL_GROW_DEFAULTROWS);
    }
    if (cbGrow.IsOverflow())
    {
        Debug_ReportInternalError("Growing record pool overflowed.");
        return CLDB_E_INTERNALERROR;
    }
    
    m_ulGrowInc = cbGrow.Value();
    
    IfFailRet(StgPool::InitNew());
    
    // If there is an initial size for the record pool, grow to that now.
    if (cRecsInit > 0)
    {
        if (!Grow(cbGrow.Value()))
            return E_OUTOFMEMORY;
    }
    
    return S_OK;
} // RecordPool::InitNew

//*****************************************************************************
// Load a Record heap from persisted memory.  If a copy of the data is made
// (so that it may be updated), then a new hash table is generated which can
// be used to elminate duplicates with new Records.
//*****************************************************************************
HRESULT 
RecordPool::InitOnMem(
    ULONG cbRec,        // Record size.
    void *pData,        // Predefined data.
    ULONG iSize,        // Size of data.
    BOOL  fReadOnly)    // true if append is forbidden.
{
    HRESULT hr;
    m_cbRec = cbRec;
    
    // Let base class init our memory structure.
    IfFailRet(StgPool::InitOnMem(pData, iSize, fReadOnly));
    
    // For init on existing mem case.
    if ((pData != NULL) && (iSize != 0))
    {
        // If we are doing an update in place don't make a copy
        // If we cannot update, then we don't need a hash table.
        if (fReadOnly)
            return S_OK;
        
        // Other wise copy the memory to do the update
        IfFailRet(TakeOwnershipOfInitMem());
    }
    
    return S_OK;
} // RecordPool::InitOnMem

//*****************************************************************************
// Allocate memory if we don't have any, or grow what we have.  If successful,
// then at least iRequired bytes will be allocated.
//*****************************************************************************
bool RecordPool::Grow(                 // true if successful.
    ULONG       iRequired)              // Min required bytes to allocate.
{
    // Allocate the memory.
    if (!StgPool::Grow(iRequired))
        return false;

    // Zero the new memory.
    memset(GetNextLocation(), 0, GetCbSegAvailable());

    return true;
} // bool RecordProol::Grow()

//*****************************************************************************
// The Record will be added to the pool.  The index of the Record in the pool
// is returned in *piIndex.  If the Record is already in the pool, then the
// index will be to the existing copy of the Record.
//*****************************************************************************
HRESULT 
RecordPool::AddRecord(
    BYTE  **ppRecord, 
    UINT32 *pnIndex)        // Return 1-based index of Record here.
{
    _ASSERTE(pnIndex != NULL);
    
    // Space on heap for new Record?
    if (m_cbRec > GetCbSegAvailable())
    {
        if (!Grow(m_cbRec))
        {
            *ppRecord = NULL;
            return E_OUTOFMEMORY;
        }
    }
    
    // Records should be aligned on record boundaries.
    _ASSERTE((GetNextOffset() % m_cbRec) == 0);
    
    // Copy the Record to the heap.
    *ppRecord = GetNextLocation();
    
    // Give the 1-based index back to caller.
    *pnIndex = (GetNextOffset() / m_cbRec) + 1;
    
    // Update heap counters.
    SegAllocate(m_cbRec);
    
    return S_OK;
} // RecordPool::AddRecord

//*****************************************************************************
// Insert a Record into the pool.  The index of the Record before which to
// insert is specified.  Shifts all records down.  Return a pointer to the
// new record.
//*****************************************************************************
HRESULT 
RecordPool::InsertRecord(
    UINT32 nIndex,          // [IN] Insert record before this.
    BYTE **ppRecord)
{
    HRESULT      hr;
    StgPoolSeg *pCurSeg;                // Current segment.
    StgPoolSeg *pPrevSeg;                // Previous segment.
    BYTE        *pSegEnd;                // Last record in a segment.
    BYTE        *pFrom;                    // A copy/move source.
    ULONG        cbMove;                    // Bytes to move.
    BYTE        *pNew;                    // New record.
    
    // Notice the case of appending.
    if (nIndex == (Count() + 1))
    {
        UINT32 nNewIndex_Ignore;
        return AddRecord(ppRecord, &nNewIndex_Ignore);
    }
    
    // If past end or before beginning, invalid.
    if ((nIndex > Count()) || (nIndex == 0))
    {
        Debug_ReportError("Invalid index passed for inserting record.");
        return CLDB_E_INDEX_NOTFOUND;
    }
    
    // This code works by allocating a new record at the end.
    // The last record is moved to the new end record.
    // Working backwards through the chained segments,
    //     shift the segment by one record, so the empty record
    //      is at the start of the segment instead of the end.
    //        copy the last record of the previous segment to the
    //      newly emptied first record of the current segment.
    // When the segment containing the insert point is finally
    //  reached, its last record is empty (from above loop), so
    //  shift from the insertion point to the end-1 by one record.
    
    // Current last record.
    pCurSeg = m_pCurSeg;
    IfFailRet(GetRecord(Count(), &pSegEnd));
    _ASSERTE(hr == S_OK);
    
    // Add an empty record to the end of the heap.
    {
        UINT32 nLastRecordIndex_Ignore;
        IfFailRet(AddRecord(&pNew, &nLastRecordIndex_Ignore));
    }
    
    // Copy the current last record to the new record.
    memcpy(pNew, pSegEnd, m_cbRec);
    
    // While the insert location is prior to the current segment,
    while (nIndex < GetIndexForRecord(pCurSeg->m_pSegData))
    {
        // Shift the segment up by one record.
        cbMove = (ULONG)(pSegEnd - pCurSeg->m_pSegData);
        memmove(pCurSeg->m_pSegData + m_cbRec, pCurSeg->m_pSegData, cbMove);
        
        // Find the previous segment.
        pPrevSeg = this;
        while (pPrevSeg->m_pNextSeg != pCurSeg)
        {
            pPrevSeg = pPrevSeg->m_pNextSeg;
        }
        
        // Copy the final record of the previous segment to the start of this one.
        pSegEnd = pPrevSeg->m_pSegData+pPrevSeg->m_cbSegNext-m_cbRec;
        memcpy(pCurSeg->m_pSegData, pSegEnd, m_cbRec);
        
        // Make the previous segment the current segment.
        pCurSeg = pPrevSeg;
    }
    
    // Shift at the insert location, forward by one.
    IfFailRet(GetRecord(nIndex, &pFrom));
    _ASSERTE(hr == S_OK);
    
    cbMove = (ULONG)(pSegEnd - pFrom);
    memmove(pFrom + m_cbRec, pFrom, cbMove);
    
    *ppRecord = pFrom;
    
    return S_OK;
} // RecordPool::InsertRecord

//*****************************************************************************
// Return a pointer to a Record given an index previously handed out by
// AddRecord or FindRecord.
//*****************************************************************************
HRESULT 
RecordPool::GetRecord(
    UINT32 nIndex,          // 1-based index of Record in pool.
    BYTE **ppRecord)
{
    MetaData::DataBlob record;
    
    if (nIndex == 0)
    {
        Debug_ReportError("Invalid index 0 passed.");
        *ppRecord = NULL;
        return CLDB_E_INDEX_NOTFOUND;
    }
    
    // Convert to 0-based internal form, defer to implementation.
    HRESULT hr = GetData((nIndex - 1) * m_cbRec, &record);
    if (FAILED(hr))
    {
        *ppRecord = NULL;
        return hr;
    }
    _ASSERTE(record.ContainsData(m_cbRec));
    *ppRecord = record.GetDataPointer();
    
    return hr;
} // RecordPool::GetRecord

//*****************************************************************************
// Return the first record in a pool, and set up a context for fast
//  iterating through the pool.  Note that this scheme does pretty minimal
//  error checking.
//*****************************************************************************
void *RecordPool::GetFirstRecord(        // Pointer to Record in pool.
    void        **pContext)                // Store context here.
{
    StgPoolSeg    **ppSeg = reinterpret_cast<StgPoolSeg**>(pContext);

    *ppSeg = static_cast<StgPoolSeg*>(this);
    return (*ppSeg)->m_pSegData;
} // void *RecordPool::GetFirstRecord()

//*****************************************************************************
// Given a pointer to a record, return a pointer to the next record.
//  Note that this scheme does pretty minimal error checking. In particular,
//  this will let the caller walk off of the end of valid data in the last
//  segment.
//*****************************************************************************
void *RecordPool::GetNextRecord(        // Pointer to Record in pool.
    void        *pRecord,                // Current record.
    void        **pContext)                // Stored context here.
{
    BYTE        *pbRec = reinterpret_cast<BYTE*>(pRecord);
    StgPoolSeg    **ppSeg = reinterpret_cast<StgPoolSeg**>(pContext);

    // Get the next record.
    pbRec += m_cbRec;

    // Is the next record outside of the current segment?
    if (static_cast<ULONG>(pbRec - (*ppSeg)->m_pSegData) >= (*ppSeg)->m_cbSegSize)
    {
        // Better be exactly one past current segment.
        _ASSERTE(static_cast<ULONG>(pbRec - (*ppSeg)->m_pSegData) == (*ppSeg)->m_cbSegSize);
        // Switch the context pointer.
        *ppSeg = (*ppSeg)->m_pNextSeg;
        // Next record is start of next segment.
        if (*ppSeg)
            return (*ppSeg)->m_pSegData;
        else
            return 0;
    }

    return pbRec;
} // void *RecordPool::GetNextRecord()

//*****************************************************************************
// Given a pointer to a record, determine the index corresponding to the
// record.
//*****************************************************************************
ULONG RecordPool::GetIndexForRecord(    // 1-based index of Record in pool.
    const void *pvRecord)                // Pointer to Record in pool.
{
    ULONG        iPrev = 0;                // cumulative index of previous segments.
    const StgPoolSeg *pSeg = this;
    const BYTE  *pRecord = reinterpret_cast<const BYTE*>(pvRecord);
    const BYTE  *pSegData = NULL;
    ULONG       ulSegSize;
    for (;;)
    {    // Does the current segment contain the record?
        pSegData = pSeg->GetSegData();
        ulSegSize = pSeg->GetSegSize();
        if (pRecord >= pSegData && pRecord < pSegData + ulSegSize)
        {    // The pointer should be to the start of a record.
            _ASSERTE(((pRecord - pSegData) % m_cbRec) == 0);
            return (ULONG)(1 + iPrev + (pRecord - pSegData) / m_cbRec);
        }
        _ASSERTE((ulSegSize % m_cbRec) == 0);
        iPrev += ulSegSize / m_cbRec;
        pSeg = pSeg->GetNextSeg();
        // If out of data, didn't find the record.
        if (pSeg == 0)
            return 0;
    }
} // ULONG RecordPool::GetIndexForRecord()

//*****************************************************************************
// Given a purported pointer to a record, determine if the pointer is valid.
//*****************************************************************************
int RecordPool::IsValidPointerForRecord(// true or false.
    const void *pvRecord)                // Pointer to Record in pool.
{
    const StgPoolSeg *pSeg;
    const BYTE  *pRecord = reinterpret_cast<const BYTE*>(pvRecord);
    const BYTE  *pSegData = NULL;
    for (pSeg = this; (pSeg); pSeg = pSeg->GetNextSeg())
    {    // Does the current segment contain the record?
        pSegData = pSeg->GetSegData();
        if ((pRecord >= pSegData) && (pRecord < pSegData + pSeg->GetSegSize()))
        {    // The pointer should be to the start of a record.
            return (((pRecord - pSegData) % m_cbRec) == 0);
        }
        _ASSERTE((pSeg->GetSegSize() % m_cbRec) == 0);
    }
    return 0;
} // int RecordPool::IsValidPointerForRecord()

//*****************************************************************************
// Replace the contents of this pool with those from another pool.  The other
//    pool loses ownership of the memory.
//*****************************************************************************
HRESULT RecordPool::ReplaceContents(
    RecordPool *pOther)                    // The other record pool.
{
    // Release any memory currently held.
    Uninit();

    // Grab the new data.
    *this = *pOther;

    // If the other pool's curseg pointed to itself, make this pool point to itself.
    if (pOther->m_pCurSeg == pOther)
        m_pCurSeg = this;

    // Fix the other pool so it won't free the memory that this one
    //  just hijacked.
    pOther->m_pSegData = (BYTE*)m_zeros;
    pOther->m_pNextSeg = 0;
    pOther->Uninit();

    return S_OK;
} // HRESULT RecordPool::ReplaceContents()