/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef js_GCArray_h
#define js_GCArray_h

#include "mozilla/Assertions.h"

#include "gc/Barrier.h"
#include "gc/Tracer.h"
#include "js/Value.h"

namespace js {

/*
 * A fixed size array of |T| for use with GC things.
 *
 * Must be allocated manually to allow space for the trailing elements
 * data. Call bytesRequired to get the required allocation size.
 *
 * Does not provide any barriers by default.
 */
template <typename T>
class GCArray {
  const uint32_t size_;
  T elements_[1];

 public:
  explicit GCArray(uint32_t size) : size_(size) {
    // The array contents following the first element are not initialized.
  }

  uint32_t size() const { return size_; }

  const T& operator[](uint32_t i) const {
    MOZ_ASSERT(i < size_);
    return elements_[i];
  }
  T& operator[](uint32_t i) {
    MOZ_ASSERT(i < size_);
    return elements_[i];
  }

  const T* begin() const { return elements_; }
  T* begin() { return elements_; }
  const T* end() const { return elements_ + size_; }
  T* end() { return elements_ + size_; }

  void trace(JSTracer* trc) {
    TraceRange(trc, size(), begin(), "array element");
  }

  static constexpr ptrdiff_t offsetOfElements() {
    return offsetof(GCArray, elements_);
  }

  static size_t bytesRequired(size_t size) {
    return offsetOfElements() + std::max(size, size_t(1)) * sizeof(T);
  }
};

/*
 * A fixed size array of GC things owned by a GC thing.
 *
 * Uses the appropriate barriers depending on whether the owner is in the
 * nursery or the tenured heap. If the owner cannot be allocated in the nursery
 * then this class is not required.
 */
template <typename T>
class GCOwnedArray {
  using StorageType = GCArray<PreAndPostBarrierWrapper<T>>;
  using TenuredInterface = StorageType;
  using NurseryInterface = GCArray<PreBarrierWrapper<T>>;

  StorageType array;

 public:
  explicit GCOwnedArray(uint32_t size) : array(size) {}

  uint32_t size() const { return array.size(); }
  const T& operator[](uint32_t i) const { return array[i].get(); }

  // Apply |f| to a view of the data with appropriate barriers given |owner|.
  template <typename F>
  void withOwner(gc::Cell* owner, F&& f) {
    if (gc::IsInsideNursery(owner)) {
      f(nurseryOwned());
    } else {
      f(tenuredOwned());
    }
  }

  // For convenience, special case setElement.
  void setElement(gc::Cell* owner, uint32_t i, const T& newValue) {
    withOwner(owner, [&](auto& self) { self[i] = newValue; });
  }

  void trace(JSTracer* trc) { array.trace(trc); }

  static constexpr ptrdiff_t offsetOfElements() {
    return offsetof(GCOwnedArray, array) + StorageType::offsetOfElements();
  }

  static size_t bytesRequired(size_t size) {
    return offsetof(GCOwnedArray, array) + StorageType::bytesRequired(size);
  }

 private:
  TenuredInterface& tenuredOwned() { return array; }
  NurseryInterface& nurseryOwned() {
    return reinterpret_cast<NurseryInterface&>(array);
  }
};

}  // namespace js

#endif  // js_GCArray_h
