forked from swiftlang/swift-corelibs-foundation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCFRunArray.c
375 lines (322 loc) · 14.9 KB
/
CFRunArray.c
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
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
/* CFRunArray.c
Copyright (c) 2004-2018, Apple Inc. All rights reserved.
*/
#include "CFRunArray.h"
#include <CoreFoundation/CFString.h>
#include "CFInternal.h"
#include "CFRuntime_Internal.h"
/* CFRunArrayGuts (which holds an array of CFRunArrayItems) is the actual data keeper; the objects just point at these... CFRunArrayGuts has copy-on-write behavior.
*/
typedef struct {
CFIndex length;
CFTypeRef obj;
} CFRunArrayItem;
typedef struct _CFRunArrayGuts { /* Variable sized block. */
CFIndex numRefs; /* For "copy on write" behavior */
CFIndex length; /* Total count of values stored by the CFRunArrayItems in list */
CFIndex numBlocks, maxBlocks; /* These describe the number of CFRunArrayItems in list */
CFIndex cachedBlock, cachedLocation; /* Cache from last lookup */
CFRunArrayItem list[0]; /* GCC */
} CFRunArrayGuts;
/* Definition of the CF struct for CFRunArray
*/
struct __CFRunArray {
CFRuntimeBase base;
CFRunArrayGuts *guts;
};
/* EXTERNCOPY is used with objects coming in from the outside world, COPY is used with objects that have already been EXTERNCOPY'ed once.
*/
#define FREE(obj) CFRelease(obj)
#define COPY(obj) CFRetain(obj)
#define EXTERNCOPY(obj) CFRetain(obj)
#define ISSAME(obj1, obj2) (CFEqual(obj1, obj2))
/* To protect accesses to the refcounts of shared CFRunArrayGuts
*/
static CFLock_t runArrayLock = CFLockInit;
#define RLEARRAYLOCK {__CFLock(&runArrayLock);}
#define RLEARRAYUNLOCK {__CFUnlock(&runArrayLock);}
/*** Internal utility ***/
/* Return the block number in the run array. Use cache if possible.
*/
static CFIndex blockForLocation(CFRunArrayGuts *guts, CFIndex location, CFRange *effectiveRange) {
CFIndex loc, block;
if (location > (guts->cachedLocation / 2)) { /* Yes, we can use the cached values as our starting point */
loc = guts->cachedLocation;
block = guts->cachedBlock;
} else {
loc = block = 0;
}
if (loc <= location) { /* Going forwards */
while (loc + guts->list[block].length <= location) loc += guts->list[block++].length;
} else { /* Going backwards */
do loc -= guts->list[--block].length; while ((block > 0) && (loc > location));
}
guts->cachedLocation = loc;
guts->cachedBlock = block;
if (effectiveRange) {
effectiveRange->location = loc;
effectiveRange->length = guts->list[block].length;
}
return block;
}
/* Gives the receiver its own copy of the argument list, reduces the ref count of the original. The original list is assumed to have a ref count > 1 (it's not freed). If oldGuts is not NULL, should be called when protected by a lock. Note that this may change array->guts, so any caches of this should be refreshed!
*/
static void __CFRunArrayMakeNewList(CFRunArrayRef array, CFRunArrayGuts *oldGuts) {
CFIndex maxBlocks = (oldGuts ? oldGuts->numBlocks : 2);
CFRunArrayGuts *newGuts = (CFRunArrayGuts *)CFAllocatorAllocate(CFGetAllocator(array), sizeof(CFRunArrayGuts) + maxBlocks * sizeof(CFRunArrayItem), 0);
newGuts->maxBlocks = maxBlocks;
if (oldGuts) {
CFIndex cnt;
for (cnt = 0; cnt < oldGuts->numBlocks; cnt++) {
newGuts->list[cnt].length = oldGuts->list[cnt].length;
newGuts->list[cnt].obj = COPY(oldGuts->list[cnt].obj);
}
newGuts->numBlocks = oldGuts->numBlocks;
newGuts->cachedBlock = oldGuts->cachedBlock;
newGuts->cachedLocation = oldGuts->cachedLocation;
newGuts->length = oldGuts->length;
oldGuts->numRefs--; // !!! We assume the caller has locked; if we have separate locks per RLEArray, need one here
} else {
newGuts->length = newGuts->numBlocks = newGuts->cachedBlock = newGuts->cachedLocation = 0;
}
newGuts->numRefs = 1;
((struct __CFRunArray *)array)->guts = newGuts;
}
/* When you call this, don't forget to recache guts afterwards, if it was cached in a local
*/
static void __CFRunArraySetBlockCapacity(CFRunArrayRef array, CFIndex desiredCount) {
#define MINSIZE 1
if (desiredCount < MINSIZE) desiredCount = MINSIZE;
/* We realloc either when there isn't enough room, or when the needed size is less than half of what's there. In both cases we allocate 33% extra. */
if ((array->guts->maxBlocks < desiredCount) || (array->guts->maxBlocks / 2) > desiredCount) {
CFIndex newCapacity = ((desiredCount + 3) / 3) * 4;
((struct __CFRunArray *)array)->guts = __CFSafelyReallocateWithAllocator(CFGetAllocator(array), array->guts, sizeof(CFRunArrayGuts) + newCapacity * sizeof(CFRunArrayItem), 0, NULL);
array->guts->maxBlocks = newCapacity;
}
}
/*** "Polymorphic" functions ***/
static Boolean __CFRunArrayEqual(CFTypeRef cf1, CFTypeRef cf2) {
return false; // ???
}
static CFHashCode __CFRunArrayHash(CFTypeRef cf) {
CFRunArrayRef array = (CFRunArrayRef)cf;
return CFRunArrayGetCount(array); // ???
}
static CFStringRef __CFRunArrayCopyDescription(CFTypeRef cf) {
CFRunArrayRef array = (CFRunArrayRef)cf;
CFRunArrayGuts *guts = array->guts;
CFIndex cnt;
CFMutableStringRef string = CFStringCreateMutable(kCFAllocatorSystemDefault, 0);
CFStringAppendFormat(string, NULL, CFSTR("%ld blocks used, total length %ld (%ld blocks, block %ld is at %ld)\n"), (long)guts->numBlocks, (long)guts->length, (long)guts->maxBlocks, (long)guts->cachedBlock, (long)guts->cachedLocation);
for (cnt = 0; cnt < guts->numBlocks; cnt++) CFStringAppendFormat(string, NULL, CFSTR(" %ld %p %@\n"), (long)guts->list[cnt].length, guts->list[cnt].obj, guts->list[cnt].obj);
return string;
}
static void __CFRunArrayDeallocate(CFTypeRef cf) {
CFRunArrayRef array = (CFRunArrayRef)cf;
CFRunArrayGuts *guts = array->guts;
RLEARRAYLOCK;
if (guts->numRefs <= 1) {
RLEARRAYUNLOCK;
CFIndex cnt;
for (cnt = 0; cnt < guts->numBlocks; cnt++) {
FREE(guts->list[cnt].obj);
}
CFAllocatorDeallocate(CFGetAllocator(array), guts);
} else {
guts->numRefs--;
RLEARRAYUNLOCK;
}
}
const CFRuntimeClass __CFRunArrayClass = {
0,
"CFRunArray",
NULL, // init
NULL, // copy
__CFRunArrayDeallocate,
__CFRunArrayEqual,
__CFRunArrayHash,
NULL, //
__CFRunArrayCopyDescription
};
CFTypeID CFRunArrayGetTypeID(void) {
return _kCFRuntimeIDCFRunArray;
}
/*** CFRunArray ***/
CFRunArrayRef _CFRunArrayCreateWithGuts(CFAllocatorRef allocator, struct _CFRunArrayGuts *runArrayGuts) {
CFIndex size = sizeof(struct __CFRunArray) - sizeof(CFRuntimeBase);
CFRunArrayRef array = (CFRunArrayRef)_CFRuntimeCreateInstance(allocator, CFRunArrayGetTypeID(), size, NULL);
if (array == NULL) return NULL;
if (runArrayGuts) {
array->guts = runArrayGuts;
RLEARRAYLOCK;
array->guts->numRefs++;
RLEARRAYUNLOCK;
} else {
__CFRunArrayMakeNewList(array, NULL);
}
return array;
}
CFRunArrayRef CFRunArrayCreate(CFAllocatorRef allocator) {
CFRunArrayRef array = _CFRunArrayCreateWithGuts(allocator, NULL);
return array;
}
CFIndex CFRunArrayGetCount(CFRunArrayRef array) {
return array->guts->length;
}
CFTypeRef CFRunArrayGetValueAtIndex(CFRunArrayRef array, CFIndex loc, CFRange *effectiveRange, CFIndex *blockIndexPtr) {
// ??? if (loc >= array->guts->length) BoundsError;
CFIndex blockIndex = blockForLocation(array->guts, loc, effectiveRange);
if (blockIndexPtr) *blockIndexPtr = blockIndex;
return array->guts->list[blockIndex].obj;
}
CFTypeRef CFRunArrayGetValueAtRunArrayIndex(CFRunArrayRef array, CFIndex blockIndex, CFIndex *lengthPtr) {
if (blockIndex >= array->guts->numBlocks) return NULL;
if (lengthPtr) *lengthPtr = array->guts->list[blockIndex].length;
return array->guts->list[blockIndex].obj;
}
void CFRunArrayInsert(CFRunArrayRef array, CFRange range, CFTypeRef obj) {
CFRunArrayGuts *guts = array->guts;
// ??? if (range.location > guts->length) BoundsError;
if (range.length == 0) return;
RLEARRAYLOCK;
if (guts->numRefs > 1) {
__CFRunArrayMakeNewList(array, guts);
guts = array->guts; // Refresh
}
RLEARRAYUNLOCK;
if (range.location == guts->length) { // Append
if (guts->length > 0) { // The list isn't empty
if (ISSAME(obj, guts->list[guts->numBlocks - 1].obj)) {
guts->list[guts->numBlocks - 1].length += range.length;
// We might have invalidated the cached info, fix it up!
if (guts->cachedBlock >= guts->numBlocks) guts->cachedLocation += range.length;
} else {
__CFRunArraySetBlockCapacity(array, guts->numBlocks + 1);
guts = array->guts; // Recache
guts->list[guts->numBlocks].obj = EXTERNCOPY(obj);
guts->list[guts->numBlocks].length = range.length;
guts->numBlocks++;
}
} else { // The list is empty...
__CFRunArraySetBlockCapacity(array, 1);
guts = array->guts; // Recache
guts->list[0].obj = EXTERNCOPY(obj);
guts->list[0].length = range.length;
guts->numBlocks++;
}
} else { // At this stage we are inserting, and the length of the list is > 0.
CFRange blockRange;
CFIndex cnt, block = blockForLocation(guts, range.location, &blockRange);
if (ISSAME(obj, guts->list[block].obj)) {
guts->list[block].length += range.length;
} else if ((block > 0) && (blockRange.location == range.location) && (ISSAME(obj, guts->list[block - 1].obj))) {
guts->list[block - 1].length += range.length;
// We might have invalidated the cached info, fix it up!
if (guts->cachedBlock >= block) guts->cachedLocation += range.length;
} else if (blockRange.location == range.location) {
__CFRunArraySetBlockCapacity(array, guts->numBlocks + 1);
guts = array->guts; // Recache
for (cnt = guts->numBlocks; cnt > block; cnt--) guts->list[cnt] = guts->list[cnt - 1];
guts->list[block].obj = EXTERNCOPY(obj);
guts->list[block].length = range.length;
guts->numBlocks++;
} else {
__CFRunArraySetBlockCapacity(array, guts->numBlocks + 2);
guts = array->guts; // Recache
for (cnt = guts->numBlocks + 1; cnt >= block + 2; cnt--) guts->list[cnt] = guts->list[cnt - 2];
guts->list[block + 1].obj = EXTERNCOPY(obj);
guts->list[block + 1].length = range.length;
guts->list[block].length = (range.location - blockRange.location);
guts->list[block + 2].obj = COPY(guts->list[block].obj);
guts->list[block + 2].length = blockRange.length - (range.location - blockRange.location);
guts->numBlocks += 2;
}
}
guts->length += range.length;
}
void CFRunArrayDelete(CFRunArrayRef array, CFRange range) {
CFRunArrayReplace(array, range, NULL, 0);
}
void CFRunArrayReplace(CFRunArrayRef array, CFRange range, CFTypeRef newObject, CFIndex newLength) {
CFRunArrayGuts *guts = array->guts;
CFRange blockRange;
CFIndex block, toBeDeleted, firstEmptyBlock, lastEmptyBlock;
// ??? if (range.location + range.length > guts->length) BoundsError;
if (range.length == 0) return;
if (newLength == 0) newObject = NULL;
RLEARRAYLOCK;
if (guts->numRefs > 1) {
__CFRunArrayMakeNewList(array, guts);
guts = array->guts; // Refresh
}
RLEARRAYUNLOCK;
/* This call also sets the cache to point to this block */
block = blockForLocation(guts, range.location, &blockRange);
guts->length -= range.length;
/* Figure out how much to delete from this block */
toBeDeleted = blockRange.length - (range.location - blockRange.location);
if (toBeDeleted > range.length) toBeDeleted = range.length;
/* Delete that count */
if ((guts->list[block].length -= toBeDeleted) == 0) FREE(guts->list[block].obj);
range.length -= toBeDeleted;
firstEmptyBlock = (guts->list[block].length == 0) ? block : block + 1;
while (range.length) {
block++;
toBeDeleted = range.length;
if (toBeDeleted >= guts->list[block].length) toBeDeleted = guts->list[block].length;
if ((guts->list[block].length -= toBeDeleted) == 0) FREE(guts->list[block].obj);
range.length -= toBeDeleted;
}
lastEmptyBlock = (block == 0 || guts->list[block].length == 0) ? block : block - 1;
if (firstEmptyBlock <= lastEmptyBlock) { /* Do we have any blocks that need to be removed? */
/* One assumption at this point is that the cache isn't beyond firstEmptyBlock */
if ((firstEmptyBlock > 0) && (firstEmptyBlock == guts->cachedBlock)) { /* Update the cachedBlock it's pointing at the hole */
guts->cachedLocation -= guts->list[firstEmptyBlock - 1].length;
guts->cachedBlock--; /* Simply make the cached block be the previous one */
}
if (newObject) { /* See if the new object can be merged with one of the end blocks */
if ((firstEmptyBlock > 0) && ISSAME(guts->list[firstEmptyBlock - 1].obj, newObject)) {
guts->list[firstEmptyBlock - 1].length += newLength;
guts->length += newLength;
newObject = NULL;
} else if ((lastEmptyBlock + 1 < guts->numBlocks) && ISSAME(guts->list[lastEmptyBlock + 1].obj, newObject)) {
guts->list[lastEmptyBlock + 1].length += newLength;
guts->length += newLength;
newObject = NULL;
}
}
if (!newObject && (firstEmptyBlock > 0) && (lastEmptyBlock + 1 < guts->numBlocks) && ISSAME(guts->list[firstEmptyBlock - 1].obj, guts->list[lastEmptyBlock + 1].obj)) { /* Can we merge the blocks around the empty space? */
/* Often the cachedBlock will point before the firstEmptyBlock; however, if it happens to be pointing at the firstEmptyBlock, we need to move it back. */
lastEmptyBlock++;
guts->list[firstEmptyBlock - 1].length += guts->list[lastEmptyBlock].length;
FREE(guts->list[lastEmptyBlock].obj);
}
if (newObject && (firstEmptyBlock < guts->numBlocks) /* Sanity check */) { /* Slip this into the first empty block */
guts->list[firstEmptyBlock].obj = EXTERNCOPY(newObject);
guts->list[firstEmptyBlock].length = newLength;
guts->length += newLength;
firstEmptyBlock++;
newObject = NULL;
}
if (firstEmptyBlock <= lastEmptyBlock) { /* Do we still need to shift anything over? */
CFIndex toBeMoved = guts->numBlocks - lastEmptyBlock - 1;
CFIndex firstFullBlock = lastEmptyBlock + 1;
for (block = 0; block < toBeMoved; block++) {
guts->list[firstEmptyBlock + block] = guts->list[firstFullBlock + block];
}
guts->numBlocks -= (firstFullBlock - firstEmptyBlock);
__CFRunArraySetBlockCapacity(array, guts->numBlocks);
}
}
if (newObject) { /* If this is still set, that means we didn't get to insert it... Do it now. */
CFRunArrayInsert(array, CFRangeMake(range.location, newLength), newObject);
}
}