|
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 } |