xpcom/glue/nsTArray-inl.h

branch
TOR_BUG_9701
changeset 8
97036ab72558
equal deleted inserted replaced
-1:000000000000 0:3a18f5da469f
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #ifndef nsTArray_h__
8 # error "Don't include this file directly"
9 #endif
10
11 template<class Alloc, class Copy>
12 nsTArray_base<Alloc, Copy>::nsTArray_base()
13 : mHdr(EmptyHdr()) {
14 MOZ_COUNT_CTOR(nsTArray_base);
15 }
16
17 template<class Alloc, class Copy>
18 nsTArray_base<Alloc, Copy>::~nsTArray_base() {
19 if (mHdr != EmptyHdr() && !UsesAutoArrayBuffer()) {
20 Alloc::Free(mHdr);
21 }
22 MOZ_COUNT_DTOR(nsTArray_base);
23 }
24
25 template<class Alloc, class Copy>
26 const nsTArrayHeader* nsTArray_base<Alloc, Copy>::GetAutoArrayBufferUnsafe(size_t elemAlign) const {
27 // Assuming |this| points to an nsAutoArray, we want to get a pointer to
28 // mAutoBuf. So just cast |this| to nsAutoArray* and read &mAutoBuf!
29
30 const void* autoBuf = &reinterpret_cast<const nsAutoArrayBase<nsTArray<uint32_t>, 1>*>(this)->mAutoBuf;
31
32 // If we're on a 32-bit system and elemAlign is 8, we need to adjust our
33 // pointer to take into account the extra alignment in the auto array.
34
35 static_assert(sizeof(void*) != 4 ||
36 (MOZ_ALIGNOF(mozilla::AlignedElem<8>) == 8 &&
37 sizeof(nsAutoTArray<mozilla::AlignedElem<8>, 1>) ==
38 sizeof(void*) + sizeof(nsTArrayHeader) +
39 4 + sizeof(mozilla::AlignedElem<8>)),
40 "auto array padding wasn't what we expected");
41
42 // We don't support alignments greater than 8 bytes.
43 NS_ABORT_IF_FALSE(elemAlign <= 4 || elemAlign == 8, "unsupported alignment.");
44 if (sizeof(void*) == 4 && elemAlign == 8) {
45 autoBuf = reinterpret_cast<const char*>(autoBuf) + 4;
46 }
47
48 return reinterpret_cast<const Header*>(autoBuf);
49 }
50
51 template<class Alloc, class Copy>
52 bool nsTArray_base<Alloc, Copy>::UsesAutoArrayBuffer() const {
53 if (!mHdr->mIsAutoArray) {
54 return false;
55 }
56
57 // This is nuts. If we were sane, we'd pass elemAlign as a parameter to
58 // this function. Unfortunately this function is called in nsTArray_base's
59 // destructor, at which point we don't know elem_type's alignment.
60 //
61 // We'll fall on our face and return true when we should say false if
62 //
63 // * we're not using our auto buffer,
64 // * elemAlign == 4, and
65 // * mHdr == GetAutoArrayBuffer(8).
66 //
67 // This could happen if |*this| lives on the heap and malloc allocated our
68 // buffer on the heap adjacent to |*this|.
69 //
70 // However, we can show that this can't happen. If |this| is an auto array
71 // (as we ensured at the beginning of the method), GetAutoArrayBuffer(8)
72 // always points to memory owned by |*this|, because (as we assert below)
73 //
74 // * GetAutoArrayBuffer(8) is at most 4 bytes past GetAutoArrayBuffer(4), and
75 // * sizeof(nsTArrayHeader) > 4.
76 //
77 // Since nsAutoTArray always contains an nsTArrayHeader,
78 // GetAutoArrayBuffer(8) will always point inside the auto array object,
79 // even if it doesn't point at the beginning of the header.
80 //
81 // Note that this means that we can't store elements with alignment 16 in an
82 // nsTArray, because GetAutoArrayBuffer(16) could lie outside the memory
83 // owned by this nsAutoTArray. We statically assert that elem_type's
84 // alignment is 8 bytes or less in nsAutoArrayBase.
85
86 static_assert(sizeof(nsTArrayHeader) > 4,
87 "see comment above");
88
89 #ifdef DEBUG
90 ptrdiff_t diff = reinterpret_cast<const char*>(GetAutoArrayBuffer(8)) -
91 reinterpret_cast<const char*>(GetAutoArrayBuffer(4));
92 NS_ABORT_IF_FALSE(diff >= 0 && diff <= 4, "GetAutoArrayBuffer doesn't do what we expect.");
93 #endif
94
95 return mHdr == GetAutoArrayBuffer(4) || mHdr == GetAutoArrayBuffer(8);
96 }
97
98
99 template<class Alloc, class Copy>
100 typename Alloc::ResultTypeProxy
101 nsTArray_base<Alloc, Copy>::EnsureCapacity(size_type capacity, size_type elemSize) {
102 // This should be the most common case so test this first
103 if (capacity <= mHdr->mCapacity)
104 return Alloc::SuccessResult();
105
106 // If the requested memory allocation exceeds size_type(-1)/2, then
107 // our doubling algorithm may not be able to allocate it.
108 // Additionally we couldn't fit in the Header::mCapacity
109 // member. Just bail out in cases like that. We don't want to be
110 // allocating 2 GB+ arrays anyway.
111 if ((uint64_t)capacity * elemSize > size_type(-1)/2) {
112 Alloc::SizeTooBig((size_t)capacity * elemSize);
113 return Alloc::FailureResult();
114 }
115
116 if (mHdr == EmptyHdr()) {
117 // Malloc() new data
118 Header *header = static_cast<Header*>
119 (Alloc::Malloc(sizeof(Header) + capacity * elemSize));
120 if (!header)
121 return Alloc::FailureResult();
122 header->mLength = 0;
123 header->mCapacity = capacity;
124 header->mIsAutoArray = 0;
125 mHdr = header;
126
127 return Alloc::SuccessResult();
128 }
129
130 // We increase our capacity so |capacity * elemSize + sizeof(Header)| is the
131 // next power of two, if this value is less than pageSize bytes, or otherwise
132 // so it's the next multiple of pageSize.
133 const uint32_t pageSizeBytes = 12;
134 const uint32_t pageSize = 1 << pageSizeBytes;
135
136 uint32_t minBytes = capacity * elemSize + sizeof(Header);
137 uint32_t bytesToAlloc;
138 if (minBytes >= pageSize) {
139 // Round up to the next multiple of pageSize.
140 bytesToAlloc = pageSize * ((minBytes + pageSize - 1) / pageSize);
141 }
142 else {
143 // Round up to the next power of two. See
144 // http://graphics.stanford.edu/~seander/bithacks.html
145 bytesToAlloc = minBytes - 1;
146 bytesToAlloc |= bytesToAlloc >> 1;
147 bytesToAlloc |= bytesToAlloc >> 2;
148 bytesToAlloc |= bytesToAlloc >> 4;
149 bytesToAlloc |= bytesToAlloc >> 8;
150 bytesToAlloc |= bytesToAlloc >> 16;
151 bytesToAlloc++;
152
153 MOZ_ASSERT((bytesToAlloc & (bytesToAlloc - 1)) == 0,
154 "nsTArray's allocation size should be a power of two!");
155 }
156
157 Header *header;
158 if (UsesAutoArrayBuffer() || !Copy::allowRealloc) {
159 // Malloc() and copy
160 header = static_cast<Header*>(Alloc::Malloc(bytesToAlloc));
161 if (!header)
162 return Alloc::FailureResult();
163
164 Copy::CopyHeaderAndElements(header, mHdr, Length(), elemSize);
165
166 if (!UsesAutoArrayBuffer())
167 Alloc::Free(mHdr);
168 } else {
169 // Realloc() existing data
170 header = static_cast<Header*>(Alloc::Realloc(mHdr, bytesToAlloc));
171 if (!header)
172 return Alloc::FailureResult();
173 }
174
175 // How many elements can we fit in bytesToAlloc?
176 uint32_t newCapacity = (bytesToAlloc - sizeof(Header)) / elemSize;
177 MOZ_ASSERT(newCapacity >= capacity, "Didn't enlarge the array enough!");
178 header->mCapacity = newCapacity;
179
180 mHdr = header;
181
182 return Alloc::SuccessResult();
183 }
184
185 template<class Alloc, class Copy>
186 void
187 nsTArray_base<Alloc, Copy>::ShrinkCapacity(size_type elemSize, size_t elemAlign) {
188 if (mHdr == EmptyHdr() || UsesAutoArrayBuffer())
189 return;
190
191 if (mHdr->mLength >= mHdr->mCapacity) // should never be greater than...
192 return;
193
194 size_type length = Length();
195
196 if (IsAutoArray() && GetAutoArrayBuffer(elemAlign)->mCapacity >= length) {
197 Header* header = GetAutoArrayBuffer(elemAlign);
198
199 // Copy data, but don't copy the header to avoid overwriting mCapacity
200 header->mLength = length;
201 Copy::CopyElements(header + 1, mHdr + 1, length, elemSize);
202
203 Alloc::Free(mHdr);
204 mHdr = header;
205 return;
206 }
207
208 if (length == 0) {
209 MOZ_ASSERT(!IsAutoArray(), "autoarray should have fit 0 elements");
210 Alloc::Free(mHdr);
211 mHdr = EmptyHdr();
212 return;
213 }
214
215 size_type size = sizeof(Header) + length * elemSize;
216 void *ptr = Alloc::Realloc(mHdr, size);
217 if (!ptr)
218 return;
219 mHdr = static_cast<Header*>(ptr);
220 mHdr->mCapacity = length;
221 }
222
223 template<class Alloc, class Copy>
224 void
225 nsTArray_base<Alloc, Copy>::ShiftData(index_type start,
226 size_type oldLen, size_type newLen,
227 size_type elemSize, size_t elemAlign) {
228 if (oldLen == newLen)
229 return;
230
231 // Determine how many elements need to be shifted
232 size_type num = mHdr->mLength - (start + oldLen);
233
234 // Compute the resulting length of the array
235 mHdr->mLength += newLen - oldLen;
236 if (mHdr->mLength == 0) {
237 ShrinkCapacity(elemSize, elemAlign);
238 } else {
239 // Maybe nothing needs to be shifted
240 if (num == 0)
241 return;
242 // Perform shift (change units to bytes first)
243 start *= elemSize;
244 newLen *= elemSize;
245 oldLen *= elemSize;
246 char *base = reinterpret_cast<char*>(mHdr + 1) + start;
247 Copy::MoveElements(base + newLen, base + oldLen, num, elemSize);
248 }
249 }
250
251 template<class Alloc, class Copy>
252 bool
253 nsTArray_base<Alloc, Copy>::InsertSlotsAt(index_type index, size_type count,
254 size_type elementSize, size_t elemAlign) {
255 MOZ_ASSERT(index <= Length(), "Bogus insertion index");
256 size_type newLen = Length() + count;
257
258 EnsureCapacity(newLen, elementSize);
259
260 // Check for out of memory conditions
261 if (Capacity() < newLen)
262 return false;
263
264 // Move the existing elements as needed. Note that this will
265 // change our mLength, so no need to call IncrementLength.
266 ShiftData(index, 0, count, elementSize, elemAlign);
267
268 return true;
269 }
270
271 // nsTArray_base::IsAutoArrayRestorer is an RAII class which takes
272 // |nsTArray_base &array| in its constructor. When it's destructed, it ensures
273 // that
274 //
275 // * array.mIsAutoArray has the same value as it did when we started, and
276 // * if array has an auto buffer and mHdr would otherwise point to sEmptyHdr,
277 // array.mHdr points to array's auto buffer.
278
279 template<class Alloc, class Copy>
280 nsTArray_base<Alloc, Copy>::IsAutoArrayRestorer::IsAutoArrayRestorer(
281 nsTArray_base<Alloc, Copy> &array,
282 size_t elemAlign)
283 : mArray(array),
284 mElemAlign(elemAlign),
285 mIsAuto(array.IsAutoArray())
286 {
287 }
288
289 template<class Alloc, class Copy>
290 nsTArray_base<Alloc, Copy>::IsAutoArrayRestorer::~IsAutoArrayRestorer() {
291 // Careful: We don't want to set mIsAutoArray = 1 on sEmptyHdr.
292 if (mIsAuto && mArray.mHdr == mArray.EmptyHdr()) {
293 // Call GetAutoArrayBufferUnsafe() because GetAutoArrayBuffer() asserts
294 // that mHdr->mIsAutoArray is true, which surely isn't the case here.
295 mArray.mHdr = mArray.GetAutoArrayBufferUnsafe(mElemAlign);
296 mArray.mHdr->mLength = 0;
297 }
298 else if (mArray.mHdr != mArray.EmptyHdr()) {
299 mArray.mHdr->mIsAutoArray = mIsAuto;
300 }
301 }
302
303 template<class Alloc, class Copy>
304 template<class Allocator>
305 typename Alloc::ResultTypeProxy
306 nsTArray_base<Alloc, Copy>::SwapArrayElements(nsTArray_base<Allocator, Copy>& other,
307 size_type elemSize, size_t elemAlign) {
308
309 // EnsureNotUsingAutoArrayBuffer will set mHdr = sEmptyHdr even if we have an
310 // auto buffer. We need to point mHdr back to our auto buffer before we
311 // return, otherwise we'll forget that we have an auto buffer at all!
312 // IsAutoArrayRestorer takes care of this for us.
313
314 IsAutoArrayRestorer ourAutoRestorer(*this, elemAlign);
315 typename nsTArray_base<Allocator, Copy>::IsAutoArrayRestorer otherAutoRestorer(other, elemAlign);
316
317 // If neither array uses an auto buffer which is big enough to store the
318 // other array's elements, then ensure that both arrays use malloc'ed storage
319 // and swap their mHdr pointers.
320 if ((!UsesAutoArrayBuffer() || Capacity() < other.Length()) &&
321 (!other.UsesAutoArrayBuffer() || other.Capacity() < Length())) {
322
323 if (!EnsureNotUsingAutoArrayBuffer(elemSize) ||
324 !other.EnsureNotUsingAutoArrayBuffer(elemSize)) {
325 return Alloc::FailureResult();
326 }
327
328 Header *temp = mHdr;
329 mHdr = other.mHdr;
330 other.mHdr = temp;
331
332 return Alloc::SuccessResult();
333 }
334
335 // Swap the two arrays by copying, since at least one is using an auto
336 // buffer which is large enough to hold all of the other's elements. We'll
337 // copy the shorter array into temporary storage.
338 //
339 // (We could do better than this in some circumstances. Suppose we're
340 // swapping arrays X and Y. X has space for 2 elements in its auto buffer,
341 // but currently has length 4, so it's using malloc'ed storage. Y has length
342 // 2. When we swap X and Y, we don't need to use a temporary buffer; we can
343 // write Y straight into X's auto buffer, write X's malloc'ed buffer on top
344 // of Y, and then switch X to using its auto buffer.)
345
346 if (!Alloc::Successful(EnsureCapacity(other.Length(), elemSize)) ||
347 !Allocator::Successful(other.EnsureCapacity(Length(), elemSize))) {
348 return Alloc::FailureResult();
349 }
350
351 // The EnsureCapacity calls above shouldn't have caused *both* arrays to
352 // switch from their auto buffers to malloc'ed space.
353 NS_ABORT_IF_FALSE(UsesAutoArrayBuffer() ||
354 other.UsesAutoArrayBuffer(),
355 "One of the arrays should be using its auto buffer.");
356
357 size_type smallerLength = XPCOM_MIN(Length(), other.Length());
358 size_type largerLength = XPCOM_MAX(Length(), other.Length());
359 void *smallerElements, *largerElements;
360 if (Length() <= other.Length()) {
361 smallerElements = Hdr() + 1;
362 largerElements = other.Hdr() + 1;
363 }
364 else {
365 smallerElements = other.Hdr() + 1;
366 largerElements = Hdr() + 1;
367 }
368
369 // Allocate temporary storage for the smaller of the two arrays. We want to
370 // allocate this space on the stack, if it's not too large. Sounds like a
371 // job for AutoTArray! (One of the two arrays we're swapping is using an
372 // auto buffer, so we're likely not allocating a lot of space here. But one
373 // could, in theory, allocate a huge AutoTArray on the heap.)
374 nsAutoArrayBase<nsTArray_Impl<uint8_t, Alloc>, 64> temp;
375 if (!Alloc::Successful(temp.EnsureCapacity(smallerLength, elemSize))) {
376 return Alloc::FailureResult();
377 }
378
379 Copy::CopyElements(temp.Elements(), smallerElements, smallerLength, elemSize);
380 Copy::CopyElements(smallerElements, largerElements, largerLength, elemSize);
381 Copy::CopyElements(largerElements, temp.Elements(), smallerLength, elemSize);
382
383 // Swap the arrays' lengths.
384 NS_ABORT_IF_FALSE((other.Length() == 0 || mHdr != EmptyHdr()) &&
385 (Length() == 0 || other.mHdr != EmptyHdr()),
386 "Don't set sEmptyHdr's length.");
387 size_type tempLength = Length();
388 mHdr->mLength = other.Length();
389 other.mHdr->mLength = tempLength;
390
391 return Alloc::SuccessResult();
392 }
393
394 template<class Alloc, class Copy>
395 bool
396 nsTArray_base<Alloc, Copy>::EnsureNotUsingAutoArrayBuffer(size_type elemSize) {
397 if (UsesAutoArrayBuffer()) {
398
399 // If you call this on a 0-length array, we'll set that array's mHdr to
400 // sEmptyHdr, in flagrant violation of the nsAutoTArray invariants. It's
401 // up to you to set it back! (If you don't, the nsAutoTArray will forget
402 // that it has an auto buffer.)
403 if (Length() == 0) {
404 mHdr = EmptyHdr();
405 return true;
406 }
407
408 size_type size = sizeof(Header) + Length() * elemSize;
409
410 Header* header = static_cast<Header*>(Alloc::Malloc(size));
411 if (!header)
412 return false;
413
414 Copy::CopyHeaderAndElements(header, mHdr, Length(), elemSize);
415 header->mCapacity = Length();
416 mHdr = header;
417 }
418
419 return true;
420 }

mercurial