Skip to content

File memoryblock.h

File List > code_source > templat > include > TempLat > lattice > memory > memoryblock.h

Go to the documentation of this file

#ifndef TEMPLAT_LATTICE_MEMORY_MEMORYBLOCK_H
#define TEMPLAT_LATTICE_MEMORY_MEMORYBLOCK_H

/* This file is part of TempLat, available at https://cosmolattice.github.io/templat .
   Copyright 2021-2026 The TempLat authors, see AUTHORS.md.
   Released under the MIT license, see LICENSE.md. */

// File info: Main contributor(s): Wessel Valkenburg, Franz R. Sattler, Year: 2025

#include "TempLat/util/exception.h"
#include "TempLat/util/log/log.h"

#include "TempLat/parallel/device_memory.h"

namespace TempLat
{
  MakeException(MemoryBlockOutOfBoundsException);

  template <typename T, size_t NDim> class MemoryBlock
  {
  public:
    // Put public methods here. These should change very little over time.

    MemoryBlock() : mSize(0u), mHostMirrorOutdated(true) {}

    MemoryBlock(size_t size) : mSize(size), mHostMirrorOutdated(true)
    {
      mData = device::memory::NDView<T, 1>("MemoryBlock", mSize);
      zero();
    }

    void allocate(size_t size)
    {
      this->mSize = size;
      if (mData.size() != size) mData = device::memory::NDView<T, 1>("MemoryBlock", mSize);
      zero();
    }

    // Don't allow copy construction or copy assignment, to avoid accidental copies of large memory blocks.
    MemoryBlock(const MemoryBlock &) = delete;
    MemoryBlock &operator=(const MemoryBlock &) = delete;

    size_t size() const { return mSize; }

    void zero() { fill(T{0}); }

    void fill(const T &value)
    {
      device::memory::fill(mData, value);
      mHostMirrorOutdated = true; // mark host mirror as outdated
    }

    DEVICE_INLINE_FUNCTION
    T &operator[](device::Idx i) const
    {
      checkBounds(i);
      return mData(i);
    }

    template <typename R = T> auto getNDView(const device::IdxArray<NDim> &localSizes) const
    {
#ifdef CHECKBOUNDS
      size_t total_size = 1;
      for (size_t i = 0; i < NDim; ++i)
        total_size *= localSizes[i];
      if (total_size > mSize)
        throw MemoryBlockOutOfBoundsException("Accessing memory block out of bounds: total size ", total_size,
                                              " is larger than allocated size ", mSize);
#endif

      return device::apply(
          [&](auto &&...args) {
            return device::memory::NDViewUnmanaged<R, NDim>(reinterpret_cast<R *>(mData.data()), args...);
          },
          localSizes);
    }

    template <typename R = T> auto getNDHostView(const device::IdxArray<NDim> &localSizes) const
    {
#ifdef CHECKBOUNDS
      size_t total_size = 1;
      for (size_t i = 0; i < NDim; ++i)
        total_size *= localSizes[i];
      if (total_size > mSize)
        throw MemoryBlockOutOfBoundsException("Accessing memory block out of bounds: total size ", total_size,
                                              " is larger than allocated size ", mSize);
#endif
      pullHostView(); // ensure host mirror is up to date

      return device::apply(
          [&](auto &&...args) {
            return device::memory::NDViewUnmanagedHost<R, NDim>(reinterpret_cast<R *>(mHostMirror.data()), args...);
          },
          localSizes);
    }

    void flagHostMirrorOutdated() const { mHostMirrorOutdated = true; }

    void pullHostView() const
    {
      if (mHostMirrorOutdated) {
        if (!mHostMirror.is_allocated()) mHostMirror = device::memory::createMirrorView(mData);
        device::memory::copyDeviceToHost(mData, mHostMirror.data());
      }
      mHostMirrorOutdated = false;
    }

    void pushHostView()
    {
      if (!mHostMirror.is_allocated())
        throw MemoryBlockOutOfBoundsException(
            "Cannot push host view: host mirror is not allocated. Call getRawHostView() or getNDHostView() first.");
      device::memory::copyHostToDevice(mHostMirror.data(), mData);
      mHostMirrorOutdated = false;
    }

    void deallocateHostView()
    {
      mHostMirror = HostView();
      mHostMirrorOutdated = true; // mark as outdated
    }

    bool isHostViewAllocated() const { return mHostMirror.is_allocated(); }

    template <typename R = T> auto getRawView() const
    {
      if constexpr (std::is_same_v<R, T>)
        return mData;
      else {
        const size_t size = mSize * sizeof(T) / sizeof(R);
        return device::memory::NDViewUnmanaged<R, 1>(reinterpret_cast<R *>(mData.data()), size);
      }
    }

    template <typename R = T> auto getRawHostView() const
    {
      pullHostView(); // ensure host mirror is up to date
      if constexpr (std::is_same_v<R, T>)
        return mHostMirror;
      else {
        const size_t size = mSize * sizeof(T) / sizeof(R);
        return device::memory::NDViewUnmanagedHost<R, 1>(reinterpret_cast<R *>(mHostMirror.data()), size);
      }
    }

    DEVICE_INLINE_FUNCTION
    operator T *() const { return mData.data(); }

    DEVICE_INLINE_FUNCTION
    T *data() const { return mData.data(); }

    friend std::ostream &operator<<(std::ostream &ostream, const MemoryBlock &mb)
    {
      ostream << "Memory Block. Size: " << mb.mSize << ", allocated: " << mb.mData.is_allocated() << "\n";
      if (mb.mSize < 64)
        ostream << "Data: \n";
      else
        ostream << "Data (first 64 elements): \n";
      auto data = mb.getRawHostView();
      mb.flagHostMirrorOutdated();
      for (size_t i = 0; i < std::min((size_t)64, mb.mSize); ++i) {
        if (i > 0) ostream << ", ";
        ostream << data(i);
      }
      ostream << "\n";
      return ostream;
    }

    friend bool operator==(const MemoryBlock &a, const MemoryBlock &b) { return a.getRawView() == b.getRawView(); }

  private:
    /* Put all member variables and private methods here. These may change arbitrarily. */
    size_t mSize;
    using DeviceView = device::memory::NDView<T, 1>;
    using HostView = typename DeviceView::host_mirror_type;
    DeviceView mData;
    mutable HostView mHostMirror;
    static constexpr size_t TSIZE = sizeof(T);
    mutable bool mHostMirrorOutdated = true;

    DEVICE_INLINE_FUNCTION
    void checkBounds(device::Idx i) const
    {
#ifdef CHECKBOUNDS
#ifdef DEVICE_HAS_EXCEPTIONS
      if (i < 0 || i >= (device::Idx)mSize)
        throw MemoryBlockOutOfBoundsException("Accessing memory block out of bounds:", i, "not in 0 -- ", mSize);
#endif
#endif
    }
  };

} // namespace TempLat

#endif