xpcom/glue/nsTArray-inl.h

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/xpcom/glue/nsTArray-inl.h	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,420 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#ifndef nsTArray_h__
    1.11 +#  error "Don't include this file directly"
    1.12 +#endif
    1.13 +
    1.14 +template<class Alloc, class Copy>
    1.15 +nsTArray_base<Alloc, Copy>::nsTArray_base()
    1.16 +  : mHdr(EmptyHdr()) {
    1.17 +  MOZ_COUNT_CTOR(nsTArray_base);
    1.18 +}
    1.19 +
    1.20 +template<class Alloc, class Copy>
    1.21 +nsTArray_base<Alloc, Copy>::~nsTArray_base() {
    1.22 +  if (mHdr != EmptyHdr() && !UsesAutoArrayBuffer()) {
    1.23 +    Alloc::Free(mHdr);
    1.24 +  }
    1.25 +  MOZ_COUNT_DTOR(nsTArray_base);
    1.26 +}
    1.27 +
    1.28 +template<class Alloc, class Copy>
    1.29 +const nsTArrayHeader* nsTArray_base<Alloc, Copy>::GetAutoArrayBufferUnsafe(size_t elemAlign) const {
    1.30 +  // Assuming |this| points to an nsAutoArray, we want to get a pointer to
    1.31 +  // mAutoBuf.  So just cast |this| to nsAutoArray* and read &mAutoBuf!
    1.32 +
    1.33 +  const void* autoBuf = &reinterpret_cast<const nsAutoArrayBase<nsTArray<uint32_t>, 1>*>(this)->mAutoBuf;
    1.34 +
    1.35 +  // If we're on a 32-bit system and elemAlign is 8, we need to adjust our
    1.36 +  // pointer to take into account the extra alignment in the auto array.
    1.37 +
    1.38 +  static_assert(sizeof(void*) != 4 ||
    1.39 +                (MOZ_ALIGNOF(mozilla::AlignedElem<8>) == 8 &&
    1.40 +                 sizeof(nsAutoTArray<mozilla::AlignedElem<8>, 1>) ==
    1.41 +                   sizeof(void*) + sizeof(nsTArrayHeader) +
    1.42 +                   4 + sizeof(mozilla::AlignedElem<8>)),
    1.43 +                "auto array padding wasn't what we expected");
    1.44 +
    1.45 +  // We don't support alignments greater than 8 bytes.
    1.46 +  NS_ABORT_IF_FALSE(elemAlign <= 4 || elemAlign == 8, "unsupported alignment.");
    1.47 +  if (sizeof(void*) == 4 && elemAlign == 8) {
    1.48 +    autoBuf = reinterpret_cast<const char*>(autoBuf) + 4;
    1.49 +  }
    1.50 +
    1.51 +  return reinterpret_cast<const Header*>(autoBuf);
    1.52 +}
    1.53 +
    1.54 +template<class Alloc, class Copy>
    1.55 +bool nsTArray_base<Alloc, Copy>::UsesAutoArrayBuffer() const {
    1.56 +  if (!mHdr->mIsAutoArray) {
    1.57 +    return false;
    1.58 +  }
    1.59 +
    1.60 +  // This is nuts.  If we were sane, we'd pass elemAlign as a parameter to
    1.61 +  // this function.  Unfortunately this function is called in nsTArray_base's
    1.62 +  // destructor, at which point we don't know elem_type's alignment.
    1.63 +  //
    1.64 +  // We'll fall on our face and return true when we should say false if
    1.65 +  //
    1.66 +  //   * we're not using our auto buffer,
    1.67 +  //   * elemAlign == 4, and
    1.68 +  //   * mHdr == GetAutoArrayBuffer(8).
    1.69 +  //
    1.70 +  // This could happen if |*this| lives on the heap and malloc allocated our
    1.71 +  // buffer on the heap adjacent to |*this|.
    1.72 +  //
    1.73 +  // However, we can show that this can't happen.  If |this| is an auto array
    1.74 +  // (as we ensured at the beginning of the method), GetAutoArrayBuffer(8)
    1.75 +  // always points to memory owned by |*this|, because (as we assert below)
    1.76 +  //
    1.77 +  //   * GetAutoArrayBuffer(8) is at most 4 bytes past GetAutoArrayBuffer(4), and 
    1.78 +  //   * sizeof(nsTArrayHeader) > 4.
    1.79 +  //
    1.80 +  // Since nsAutoTArray always contains an nsTArrayHeader,
    1.81 +  // GetAutoArrayBuffer(8) will always point inside the auto array object,
    1.82 +  // even if it doesn't point at the beginning of the header.
    1.83 +  //
    1.84 +  // Note that this means that we can't store elements with alignment 16 in an
    1.85 +  // nsTArray, because GetAutoArrayBuffer(16) could lie outside the memory
    1.86 +  // owned by this nsAutoTArray.  We statically assert that elem_type's
    1.87 +  // alignment is 8 bytes or less in nsAutoArrayBase.
    1.88 +
    1.89 +  static_assert(sizeof(nsTArrayHeader) > 4,
    1.90 +                "see comment above");
    1.91 +
    1.92 +#ifdef DEBUG
    1.93 +  ptrdiff_t diff = reinterpret_cast<const char*>(GetAutoArrayBuffer(8)) -
    1.94 +                   reinterpret_cast<const char*>(GetAutoArrayBuffer(4));
    1.95 +  NS_ABORT_IF_FALSE(diff >= 0 && diff <= 4, "GetAutoArrayBuffer doesn't do what we expect.");
    1.96 +#endif
    1.97 +
    1.98 +  return mHdr == GetAutoArrayBuffer(4) || mHdr == GetAutoArrayBuffer(8);
    1.99 +}
   1.100 +
   1.101 +
   1.102 +template<class Alloc, class Copy>
   1.103 +typename Alloc::ResultTypeProxy
   1.104 +nsTArray_base<Alloc, Copy>::EnsureCapacity(size_type capacity, size_type elemSize) {
   1.105 +  // This should be the most common case so test this first
   1.106 +  if (capacity <= mHdr->mCapacity)
   1.107 +    return Alloc::SuccessResult();
   1.108 +
   1.109 +  // If the requested memory allocation exceeds size_type(-1)/2, then
   1.110 +  // our doubling algorithm may not be able to allocate it.
   1.111 +  // Additionally we couldn't fit in the Header::mCapacity
   1.112 +  // member. Just bail out in cases like that.  We don't want to be
   1.113 +  // allocating 2 GB+ arrays anyway.
   1.114 +  if ((uint64_t)capacity * elemSize > size_type(-1)/2) {
   1.115 +    Alloc::SizeTooBig((size_t)capacity * elemSize);
   1.116 +    return Alloc::FailureResult();
   1.117 +  }
   1.118 +
   1.119 +  if (mHdr == EmptyHdr()) {
   1.120 +    // Malloc() new data
   1.121 +    Header *header = static_cast<Header*>
   1.122 +                     (Alloc::Malloc(sizeof(Header) + capacity * elemSize));
   1.123 +    if (!header)
   1.124 +      return Alloc::FailureResult();
   1.125 +    header->mLength = 0;
   1.126 +    header->mCapacity = capacity;
   1.127 +    header->mIsAutoArray = 0;
   1.128 +    mHdr = header;
   1.129 +
   1.130 +    return Alloc::SuccessResult();
   1.131 +  }
   1.132 +
   1.133 +  // We increase our capacity so |capacity * elemSize + sizeof(Header)| is the
   1.134 +  // next power of two, if this value is less than pageSize bytes, or otherwise
   1.135 +  // so it's the next multiple of pageSize.
   1.136 +  const uint32_t pageSizeBytes = 12;
   1.137 +  const uint32_t pageSize = 1 << pageSizeBytes;
   1.138 +
   1.139 +  uint32_t minBytes = capacity * elemSize + sizeof(Header);
   1.140 +  uint32_t bytesToAlloc;
   1.141 +  if (minBytes >= pageSize) {
   1.142 +    // Round up to the next multiple of pageSize.
   1.143 +    bytesToAlloc = pageSize * ((minBytes + pageSize - 1) / pageSize);
   1.144 +  }
   1.145 +  else {
   1.146 +    // Round up to the next power of two.  See
   1.147 +    // http://graphics.stanford.edu/~seander/bithacks.html
   1.148 +    bytesToAlloc = minBytes - 1;
   1.149 +    bytesToAlloc |= bytesToAlloc >> 1;
   1.150 +    bytesToAlloc |= bytesToAlloc >> 2;
   1.151 +    bytesToAlloc |= bytesToAlloc >> 4;
   1.152 +    bytesToAlloc |= bytesToAlloc >> 8;
   1.153 +    bytesToAlloc |= bytesToAlloc >> 16;
   1.154 +    bytesToAlloc++;
   1.155 +
   1.156 +    MOZ_ASSERT((bytesToAlloc & (bytesToAlloc - 1)) == 0,
   1.157 +               "nsTArray's allocation size should be a power of two!");
   1.158 +  }
   1.159 +
   1.160 +  Header *header;
   1.161 +  if (UsesAutoArrayBuffer() || !Copy::allowRealloc) {
   1.162 +    // Malloc() and copy
   1.163 +    header = static_cast<Header*>(Alloc::Malloc(bytesToAlloc));
   1.164 +    if (!header)
   1.165 +      return Alloc::FailureResult();
   1.166 +
   1.167 +    Copy::CopyHeaderAndElements(header, mHdr, Length(), elemSize);
   1.168 +
   1.169 +    if (!UsesAutoArrayBuffer())
   1.170 +      Alloc::Free(mHdr);
   1.171 +  } else {
   1.172 +    // Realloc() existing data
   1.173 +    header = static_cast<Header*>(Alloc::Realloc(mHdr, bytesToAlloc));
   1.174 +    if (!header)
   1.175 +      return Alloc::FailureResult();
   1.176 +  }
   1.177 +
   1.178 +  // How many elements can we fit in bytesToAlloc?
   1.179 +  uint32_t newCapacity = (bytesToAlloc - sizeof(Header)) / elemSize;
   1.180 +  MOZ_ASSERT(newCapacity >= capacity, "Didn't enlarge the array enough!");
   1.181 +  header->mCapacity = newCapacity;
   1.182 +
   1.183 +  mHdr = header;
   1.184 +
   1.185 +  return Alloc::SuccessResult();
   1.186 +}
   1.187 +
   1.188 +template<class Alloc, class Copy>
   1.189 +void
   1.190 +nsTArray_base<Alloc, Copy>::ShrinkCapacity(size_type elemSize, size_t elemAlign) {
   1.191 +  if (mHdr == EmptyHdr() || UsesAutoArrayBuffer())
   1.192 +    return;
   1.193 +
   1.194 +  if (mHdr->mLength >= mHdr->mCapacity)  // should never be greater than...
   1.195 +    return;
   1.196 +
   1.197 +  size_type length = Length();
   1.198 +
   1.199 +  if (IsAutoArray() && GetAutoArrayBuffer(elemAlign)->mCapacity >= length) {
   1.200 +    Header* header = GetAutoArrayBuffer(elemAlign);
   1.201 +
   1.202 +    // Copy data, but don't copy the header to avoid overwriting mCapacity
   1.203 +    header->mLength = length;
   1.204 +    Copy::CopyElements(header + 1, mHdr + 1, length, elemSize);
   1.205 +
   1.206 +    Alloc::Free(mHdr);
   1.207 +    mHdr = header;
   1.208 +    return;
   1.209 +  }
   1.210 +
   1.211 +  if (length == 0) {
   1.212 +    MOZ_ASSERT(!IsAutoArray(), "autoarray should have fit 0 elements");
   1.213 +    Alloc::Free(mHdr);
   1.214 +    mHdr = EmptyHdr();
   1.215 +    return;
   1.216 +  }
   1.217 +
   1.218 +  size_type size = sizeof(Header) + length * elemSize;
   1.219 +  void *ptr = Alloc::Realloc(mHdr, size);
   1.220 +  if (!ptr)
   1.221 +    return;
   1.222 +  mHdr = static_cast<Header*>(ptr);
   1.223 +  mHdr->mCapacity = length;
   1.224 +}
   1.225 +
   1.226 +template<class Alloc, class Copy>
   1.227 +void
   1.228 +nsTArray_base<Alloc, Copy>::ShiftData(index_type start,
   1.229 +                                size_type oldLen, size_type newLen,
   1.230 +                                size_type elemSize, size_t elemAlign) {
   1.231 +  if (oldLen == newLen)
   1.232 +    return;
   1.233 +
   1.234 +  // Determine how many elements need to be shifted
   1.235 +  size_type num = mHdr->mLength - (start + oldLen);
   1.236 +
   1.237 +  // Compute the resulting length of the array
   1.238 +  mHdr->mLength += newLen - oldLen;
   1.239 +  if (mHdr->mLength == 0) {
   1.240 +    ShrinkCapacity(elemSize, elemAlign);
   1.241 +  } else {
   1.242 +    // Maybe nothing needs to be shifted
   1.243 +    if (num == 0)
   1.244 +      return;
   1.245 +    // Perform shift (change units to bytes first)
   1.246 +    start *= elemSize;
   1.247 +    newLen *= elemSize;
   1.248 +    oldLen *= elemSize;
   1.249 +    char *base = reinterpret_cast<char*>(mHdr + 1) + start;
   1.250 +    Copy::MoveElements(base + newLen, base + oldLen, num, elemSize);
   1.251 +  }
   1.252 +}
   1.253 +
   1.254 +template<class Alloc, class Copy>
   1.255 +bool
   1.256 +nsTArray_base<Alloc, Copy>::InsertSlotsAt(index_type index, size_type count,
   1.257 +                                    size_type elementSize, size_t elemAlign)  {
   1.258 +  MOZ_ASSERT(index <= Length(), "Bogus insertion index");
   1.259 +  size_type newLen = Length() + count;
   1.260 +
   1.261 +  EnsureCapacity(newLen, elementSize);
   1.262 +
   1.263 +  // Check for out of memory conditions
   1.264 +  if (Capacity() < newLen)
   1.265 +    return false;
   1.266 +
   1.267 +  // Move the existing elements as needed.  Note that this will
   1.268 +  // change our mLength, so no need to call IncrementLength.
   1.269 +  ShiftData(index, 0, count, elementSize, elemAlign);
   1.270 +
   1.271 +  return true;
   1.272 +}
   1.273 +
   1.274 +// nsTArray_base::IsAutoArrayRestorer is an RAII class which takes
   1.275 +// |nsTArray_base &array| in its constructor.  When it's destructed, it ensures
   1.276 +// that
   1.277 +//
   1.278 +//   * array.mIsAutoArray has the same value as it did when we started, and
   1.279 +//   * if array has an auto buffer and mHdr would otherwise point to sEmptyHdr,
   1.280 +//     array.mHdr points to array's auto buffer.
   1.281 +
   1.282 +template<class Alloc, class Copy>
   1.283 +nsTArray_base<Alloc, Copy>::IsAutoArrayRestorer::IsAutoArrayRestorer(
   1.284 +  nsTArray_base<Alloc, Copy> &array,
   1.285 +  size_t elemAlign)
   1.286 +  : mArray(array),
   1.287 +    mElemAlign(elemAlign),
   1.288 +    mIsAuto(array.IsAutoArray())
   1.289 +{
   1.290 +}
   1.291 +
   1.292 +template<class Alloc, class Copy>
   1.293 +nsTArray_base<Alloc, Copy>::IsAutoArrayRestorer::~IsAutoArrayRestorer() {
   1.294 +  // Careful: We don't want to set mIsAutoArray = 1 on sEmptyHdr.
   1.295 +  if (mIsAuto && mArray.mHdr == mArray.EmptyHdr()) {
   1.296 +    // Call GetAutoArrayBufferUnsafe() because GetAutoArrayBuffer() asserts
   1.297 +    // that mHdr->mIsAutoArray is true, which surely isn't the case here.
   1.298 +    mArray.mHdr = mArray.GetAutoArrayBufferUnsafe(mElemAlign);
   1.299 +    mArray.mHdr->mLength = 0;
   1.300 +  }
   1.301 +  else if (mArray.mHdr != mArray.EmptyHdr()) {
   1.302 +    mArray.mHdr->mIsAutoArray = mIsAuto;
   1.303 +  }
   1.304 +}
   1.305 +
   1.306 +template<class Alloc, class Copy>
   1.307 +template<class Allocator>
   1.308 +typename Alloc::ResultTypeProxy
   1.309 +nsTArray_base<Alloc, Copy>::SwapArrayElements(nsTArray_base<Allocator, Copy>& other,
   1.310 +                                              size_type elemSize, size_t elemAlign) {
   1.311 +
   1.312 +  // EnsureNotUsingAutoArrayBuffer will set mHdr = sEmptyHdr even if we have an
   1.313 +  // auto buffer.  We need to point mHdr back to our auto buffer before we
   1.314 +  // return, otherwise we'll forget that we have an auto buffer at all!
   1.315 +  // IsAutoArrayRestorer takes care of this for us.
   1.316 +
   1.317 +  IsAutoArrayRestorer ourAutoRestorer(*this, elemAlign);
   1.318 +  typename nsTArray_base<Allocator, Copy>::IsAutoArrayRestorer otherAutoRestorer(other, elemAlign);
   1.319 +
   1.320 +  // If neither array uses an auto buffer which is big enough to store the
   1.321 +  // other array's elements, then ensure that both arrays use malloc'ed storage
   1.322 +  // and swap their mHdr pointers.
   1.323 +  if ((!UsesAutoArrayBuffer() || Capacity() < other.Length()) &&
   1.324 +      (!other.UsesAutoArrayBuffer() || other.Capacity() < Length())) {
   1.325 +
   1.326 +    if (!EnsureNotUsingAutoArrayBuffer(elemSize) ||
   1.327 +        !other.EnsureNotUsingAutoArrayBuffer(elemSize)) {
   1.328 +      return Alloc::FailureResult();
   1.329 +    }
   1.330 +
   1.331 +    Header *temp = mHdr;
   1.332 +    mHdr = other.mHdr;
   1.333 +    other.mHdr = temp;
   1.334 +
   1.335 +    return Alloc::SuccessResult();
   1.336 +  }
   1.337 +
   1.338 +  // Swap the two arrays by copying, since at least one is using an auto
   1.339 +  // buffer which is large enough to hold all of the other's elements.  We'll
   1.340 +  // copy the shorter array into temporary storage.
   1.341 +  //
   1.342 +  // (We could do better than this in some circumstances.  Suppose we're
   1.343 +  // swapping arrays X and Y.  X has space for 2 elements in its auto buffer,
   1.344 +  // but currently has length 4, so it's using malloc'ed storage.  Y has length
   1.345 +  // 2.  When we swap X and Y, we don't need to use a temporary buffer; we can
   1.346 +  // write Y straight into X's auto buffer, write X's malloc'ed buffer on top
   1.347 +  // of Y, and then switch X to using its auto buffer.)
   1.348 +
   1.349 +  if (!Alloc::Successful(EnsureCapacity(other.Length(), elemSize)) ||
   1.350 +      !Allocator::Successful(other.EnsureCapacity(Length(), elemSize))) {
   1.351 +    return Alloc::FailureResult();
   1.352 +  }
   1.353 +
   1.354 +  // The EnsureCapacity calls above shouldn't have caused *both* arrays to
   1.355 +  // switch from their auto buffers to malloc'ed space.
   1.356 +  NS_ABORT_IF_FALSE(UsesAutoArrayBuffer() ||
   1.357 +                    other.UsesAutoArrayBuffer(),
   1.358 +                    "One of the arrays should be using its auto buffer.");
   1.359 +
   1.360 +  size_type smallerLength = XPCOM_MIN(Length(), other.Length());
   1.361 +  size_type largerLength = XPCOM_MAX(Length(), other.Length());
   1.362 +  void *smallerElements, *largerElements;
   1.363 +  if (Length() <= other.Length()) {
   1.364 +    smallerElements = Hdr() + 1;
   1.365 +    largerElements = other.Hdr() + 1;
   1.366 +  }
   1.367 +  else {
   1.368 +    smallerElements = other.Hdr() + 1;
   1.369 +    largerElements = Hdr() + 1;
   1.370 +  }
   1.371 +
   1.372 +  // Allocate temporary storage for the smaller of the two arrays.  We want to
   1.373 +  // allocate this space on the stack, if it's not too large.  Sounds like a
   1.374 +  // job for AutoTArray!  (One of the two arrays we're swapping is using an
   1.375 +  // auto buffer, so we're likely not allocating a lot of space here.  But one
   1.376 +  // could, in theory, allocate a huge AutoTArray on the heap.)
   1.377 +  nsAutoArrayBase<nsTArray_Impl<uint8_t, Alloc>, 64> temp;
   1.378 +  if (!Alloc::Successful(temp.EnsureCapacity(smallerLength, elemSize))) {
   1.379 +    return Alloc::FailureResult();
   1.380 +  }
   1.381 +
   1.382 +  Copy::CopyElements(temp.Elements(), smallerElements, smallerLength, elemSize);
   1.383 +  Copy::CopyElements(smallerElements, largerElements, largerLength, elemSize);
   1.384 +  Copy::CopyElements(largerElements, temp.Elements(), smallerLength, elemSize);
   1.385 +
   1.386 +  // Swap the arrays' lengths.
   1.387 +  NS_ABORT_IF_FALSE((other.Length() == 0 || mHdr != EmptyHdr()) &&
   1.388 +                    (Length() == 0 || other.mHdr != EmptyHdr()),
   1.389 +                    "Don't set sEmptyHdr's length.");
   1.390 +  size_type tempLength = Length();
   1.391 +  mHdr->mLength = other.Length();
   1.392 +  other.mHdr->mLength = tempLength;
   1.393 +
   1.394 +  return Alloc::SuccessResult();
   1.395 +}
   1.396 +
   1.397 +template<class Alloc, class Copy>
   1.398 +bool
   1.399 +nsTArray_base<Alloc, Copy>::EnsureNotUsingAutoArrayBuffer(size_type elemSize) {
   1.400 +  if (UsesAutoArrayBuffer()) {
   1.401 +
   1.402 +    // If you call this on a 0-length array, we'll set that array's mHdr to
   1.403 +    // sEmptyHdr, in flagrant violation of the nsAutoTArray invariants.  It's
   1.404 +    // up to you to set it back!  (If you don't, the nsAutoTArray will forget
   1.405 +    // that it has an auto buffer.)
   1.406 +    if (Length() == 0) {
   1.407 +      mHdr = EmptyHdr();
   1.408 +      return true;
   1.409 +    }
   1.410 +
   1.411 +    size_type size = sizeof(Header) + Length() * elemSize;
   1.412 +
   1.413 +    Header* header = static_cast<Header*>(Alloc::Malloc(size));
   1.414 +    if (!header)
   1.415 +      return false;
   1.416 +
   1.417 +    Copy::CopyHeaderAndElements(header, mHdr, Length(), elemSize);
   1.418 +    header->mCapacity = Length();
   1.419 +    mHdr = header;
   1.420 +  }
   1.421 +
   1.422 +  return true;
   1.423 +}

mercurial