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