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