xref: /petsc/src/sys/objects/device/interface/memory.cxx (revision 3398534bb31318b6370f438735dace49bef3ca56)
10e6b6b59SJacob Faibussowitsch #include <petsc/private/deviceimpl.h> /*I <petscdevice.h> I*/
20e6b6b59SJacob Faibussowitsch 
30e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/register_finalize.hpp>
46797ed33SJacob Faibussowitsch #include <petsc/private/cpp/type_traits.hpp> // integral_value
50e6b6b59SJacob Faibussowitsch 
60e6b6b59SJacob Faibussowitsch #include <unordered_map>
70e6b6b59SJacob Faibussowitsch #include <algorithm> // std::find_if
80e6b6b59SJacob Faibussowitsch #include <cstring>   // std::memset
90e6b6b59SJacob Faibussowitsch 
100e6b6b59SJacob Faibussowitsch const char *const PetscDeviceCopyModes[] = {"host_to_host", "device_to_host", "host_to_device", "device_to_device", "auto", "PetscDeviceCopyMode", "PETSC_DEVICE_COPY_", nullptr};
110e6b6b59SJacob Faibussowitsch static_assert(Petsc::util::integral_value(PETSC_DEVICE_COPY_HTOH) == 0, "");
120e6b6b59SJacob Faibussowitsch static_assert(Petsc::util::integral_value(PETSC_DEVICE_COPY_DTOH) == 1, "");
130e6b6b59SJacob Faibussowitsch static_assert(Petsc::util::integral_value(PETSC_DEVICE_COPY_HTOD) == 2, "");
140e6b6b59SJacob Faibussowitsch static_assert(Petsc::util::integral_value(PETSC_DEVICE_COPY_DTOD) == 3, "");
150e6b6b59SJacob Faibussowitsch static_assert(Petsc::util::integral_value(PETSC_DEVICE_COPY_AUTO) == 4, "");
160e6b6b59SJacob Faibussowitsch 
17*3398534bSJacob Faibussowitsch // GCC implementation for std::hash<T*>. LLVM's libc++ is almost 2x slower because they do all
18*3398534bSJacob Faibussowitsch // kinds of complicated murmur hashing, so we make sure to enforce GCC's version.
19*3398534bSJacob Faibussowitsch struct PointerHash {
20*3398534bSJacob Faibussowitsch   template <typename T>
21*3398534bSJacob Faibussowitsch   PETSC_NODISCARD std::size_t operator()(const T *ptr) const noexcept
22*3398534bSJacob Faibussowitsch   {
23*3398534bSJacob Faibussowitsch     return reinterpret_cast<std::size_t>(ptr);
24*3398534bSJacob Faibussowitsch   }
25*3398534bSJacob Faibussowitsch };
26*3398534bSJacob Faibussowitsch 
27*3398534bSJacob Faibussowitsch // ==========================================================================================
28*3398534bSJacob Faibussowitsch // PointerAttributes
29*3398534bSJacob Faibussowitsch // ==========================================================================================
30*3398534bSJacob Faibussowitsch 
31*3398534bSJacob Faibussowitsch struct PointerAttributes {
32*3398534bSJacob Faibussowitsch   PetscMemType  mtype = PETSC_MEMTYPE_HOST; // memtype of allocation
33*3398534bSJacob Faibussowitsch   PetscObjectId id    = 0;                  // id of allocation
34*3398534bSJacob Faibussowitsch   std::size_t   size  = 0;                  // size of allocation (bytes)
35*3398534bSJacob Faibussowitsch 
36*3398534bSJacob Faibussowitsch   // even though this is a POD and can be aggregate initialized, the STL uses () constructors
37*3398534bSJacob Faibussowitsch   // in unordered_map and so we need to provide a trivial contructor...
38*3398534bSJacob Faibussowitsch   constexpr PointerAttributes(PetscMemType, PetscObjectId, std::size_t) noexcept;
39*3398534bSJacob Faibussowitsch 
40*3398534bSJacob Faibussowitsch   bool operator==(const PointerAttributes &) const noexcept;
41*3398534bSJacob Faibussowitsch 
42*3398534bSJacob Faibussowitsch   PETSC_NODISCARD bool contains(const void *, const void *) const noexcept;
43*3398534bSJacob Faibussowitsch };
44*3398534bSJacob Faibussowitsch 
45*3398534bSJacob Faibussowitsch // ==========================================================================================
46*3398534bSJacob Faibussowitsch // PointerAttributes - Public API
47*3398534bSJacob Faibussowitsch // ==========================================================================================
48*3398534bSJacob Faibussowitsch 
49*3398534bSJacob Faibussowitsch inline constexpr PointerAttributes::PointerAttributes(PetscMemType mtype_, PetscObjectId id_, std::size_t size_) noexcept : mtype(mtype_), id(id_), size(size_) { }
50*3398534bSJacob Faibussowitsch 
51*3398534bSJacob Faibussowitsch inline bool PointerAttributes::operator==(const PointerAttributes &other) const noexcept
52*3398534bSJacob Faibussowitsch {
53*3398534bSJacob Faibussowitsch   return (mtype == other.mtype) && (id == other.id) && (size == other.size);
54*3398534bSJacob Faibussowitsch }
55*3398534bSJacob Faibussowitsch 
56*3398534bSJacob Faibussowitsch /*
57*3398534bSJacob Faibussowitsch   PointerAttributes::contains - asks and answers the question, does ptr_begin contain ptr
58*3398534bSJacob Faibussowitsch 
59*3398534bSJacob Faibussowitsch   Input Parameters:
60*3398534bSJacob Faibussowitsch + ptr_begin - pointer to the start of the range to check
61*3398534bSJacob Faibussowitsch - ptr       - the pointer to query
62*3398534bSJacob Faibussowitsch 
63*3398534bSJacob Faibussowitsch   Notes:
64*3398534bSJacob Faibussowitsch   Returns true if ptr falls within ptr_begins range, false otherwise.
65*3398534bSJacob Faibussowitsch */
66*3398534bSJacob Faibussowitsch inline bool PointerAttributes::contains(const void *ptr_begin, const void *ptr) const noexcept
67*3398534bSJacob Faibussowitsch {
68*3398534bSJacob Faibussowitsch   return (ptr >= ptr_begin) && (ptr < (static_cast<const char *>(ptr_begin) + size));
69*3398534bSJacob Faibussowitsch }
70*3398534bSJacob Faibussowitsch 
710e6b6b59SJacob Faibussowitsch // ==========================================================================================
720e6b6b59SJacob Faibussowitsch // MemoryMap
730e6b6b59SJacob Faibussowitsch //
746797ed33SJacob Faibussowitsch // Since the pointers allocated via PetscDeviceAllocate_Private() may be device pointers we
756797ed33SJacob Faibussowitsch // cannot just store meta-data within the pointer itself (as we can't dereference them). So
766797ed33SJacob Faibussowitsch // instead we need to keep an extra map to keep track of them
770e6b6b59SJacob Faibussowitsch //
786797ed33SJacob Faibussowitsch // Each entry maps pointer -> {
796797ed33SJacob Faibussowitsch //   PetscMemType  - The memtype of the pointer
806797ed33SJacob Faibussowitsch //   PetscObjectId - A unique ID assigned at allocation or registratrion so auto-dep can
816797ed33SJacob Faibussowitsch //                   identify the pointer
826797ed33SJacob Faibussowitsch //   size          - The size (in bytes) of the allocation
836797ed33SJacob Faibussowitsch // }
840e6b6b59SJacob Faibussowitsch // ==========================================================================================
850e6b6b59SJacob Faibussowitsch 
860e6b6b59SJacob Faibussowitsch class MemoryMap : public Petsc::RegisterFinalizeable<MemoryMap> {
870e6b6b59SJacob Faibussowitsch public:
880e6b6b59SJacob Faibussowitsch   using map_type = std::unordered_map<void *, PointerAttributes, PointerHash>;
890e6b6b59SJacob Faibussowitsch 
90*3398534bSJacob Faibussowitsch   map_type map{};
910e6b6b59SJacob Faibussowitsch 
926797ed33SJacob Faibussowitsch   PETSC_NODISCARD map_type::const_iterator search_for(const void *, bool = false) const noexcept;
930e6b6b59SJacob Faibussowitsch 
940e6b6b59SJacob Faibussowitsch private:
950e6b6b59SJacob Faibussowitsch   friend class Petsc::RegisterFinalizeable<MemoryMap>;
960e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode register_finalize_() noexcept;
970e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode finalize_() noexcept;
980e6b6b59SJacob Faibussowitsch };
990e6b6b59SJacob Faibussowitsch 
1000e6b6b59SJacob Faibussowitsch // ==========================================================================================
101*3398534bSJacob Faibussowitsch // MemoryMap - Private API
1020e6b6b59SJacob Faibussowitsch // ==========================================================================================
1030e6b6b59SJacob Faibussowitsch 
104d71ae5a4SJacob Faibussowitsch PetscErrorCode MemoryMap::register_finalize_() noexcept
105d71ae5a4SJacob Faibussowitsch {
1060e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
1070e6b6b59SJacob Faibussowitsch   // Preallocate, this does give a modest performance bump since unordered_map is so __dog__
1080e6b6b59SJacob Faibussowitsch   // slow if it needs to rehash. Experiments show that users tend not to have more than 5 or
1090e6b6b59SJacob Faibussowitsch   // so concurrently live pointers lying around. 10 at most.
1100e6b6b59SJacob Faibussowitsch   PetscCallCXX(map.reserve(16));
1110e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
1120e6b6b59SJacob Faibussowitsch }
1130e6b6b59SJacob Faibussowitsch 
114d71ae5a4SJacob Faibussowitsch PetscErrorCode MemoryMap::finalize_() noexcept
115d71ae5a4SJacob Faibussowitsch {
1160e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
1170e6b6b59SJacob Faibussowitsch   PetscCall(PetscInfo(nullptr, "Finalizing memory map\n"));
1180e6b6b59SJacob Faibussowitsch   PetscCallCXX(map = map_type{});
1190e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
1200e6b6b59SJacob Faibussowitsch }
1210e6b6b59SJacob Faibussowitsch 
1226797ed33SJacob Faibussowitsch // ==========================================================================================
123*3398534bSJacob Faibussowitsch // MemoryMap - Public API
1246797ed33SJacob Faibussowitsch // ==========================================================================================
1256797ed33SJacob Faibussowitsch 
1260e6b6b59SJacob Faibussowitsch /*
1270e6b6b59SJacob Faibussowitsch   MemoryMap::search_for - retrieve an iterator to the key-value pair for a pointer in the map
1280e6b6b59SJacob Faibussowitsch 
1296797ed33SJacob Faibussowitsch   Input Parameters:
1306797ed33SJacob Faibussowitsch + ptr       - pointer to search for
1316797ed33SJacob Faibussowitsch - must_find - true if an error is raised if the pointer is not found (default: false)
1320e6b6b59SJacob Faibussowitsch 
1330e6b6b59SJacob Faibussowitsch   Notes:
1340e6b6b59SJacob Faibussowitsch   Accounts for sub-regions, i.e. if ptr is contained within another pointers region, it returns
1350e6b6b59SJacob Faibussowitsch   the iterator to the super-pointers key-value pair.
1360e6b6b59SJacob Faibussowitsch 
1376797ed33SJacob Faibussowitsch   If ptr is not found and must_find is false returns map.end(), otherwise raises an error
1380e6b6b59SJacob Faibussowitsch */
139d71ae5a4SJacob Faibussowitsch MemoryMap::map_type::const_iterator MemoryMap::search_for(const void *ptr, bool must_find) const noexcept
140d71ae5a4SJacob Faibussowitsch {
1410e6b6b59SJacob Faibussowitsch   const auto end = map.end();
1426797ed33SJacob Faibussowitsch   auto       it  = map.find(const_cast<map_type::key_type>(ptr));
1430e6b6b59SJacob Faibussowitsch 
1440e6b6b59SJacob Faibussowitsch   // ptr was found, and points to an entire block
1456797ed33SJacob Faibussowitsch   PetscFunctionBegin;
1466797ed33SJacob Faibussowitsch   if (it != end) PetscFunctionReturn(it);
1470e6b6b59SJacob Faibussowitsch   // wasn't found, but maybe its part of a block. have to search every block for it
1480e6b6b59SJacob Faibussowitsch   // clang-format off
1496797ed33SJacob Faibussowitsch   it = std::find_if(map.begin(), end, [ptr](const map_type::const_iterator::value_type &map_it) {
1500e6b6b59SJacob Faibussowitsch     return map_it.second.contains(map_it.first, ptr);
1510e6b6b59SJacob Faibussowitsch   });
1526797ed33SJacob Faibussowitsch   PetscCheckAbort(!must_find || it != end, PETSC_COMM_SELF, PETSC_ERR_POINTER, "Pointer %p was not registered with the memory tracker, call PetscDeviceRegisterMemory() on it", ptr);
1536797ed33SJacob Faibussowitsch   PetscFunctionReturn(it);
1540e6b6b59SJacob Faibussowitsch   // clang-format on
1550e6b6b59SJacob Faibussowitsch }
1560e6b6b59SJacob Faibussowitsch 
1570e6b6b59SJacob Faibussowitsch static MemoryMap memory_map;
1580e6b6b59SJacob Faibussowitsch 
1590e6b6b59SJacob Faibussowitsch // ==========================================================================================
1600e6b6b59SJacob Faibussowitsch // Utility functions
1610e6b6b59SJacob Faibussowitsch // ==========================================================================================
1620e6b6b59SJacob Faibussowitsch 
163d71ae5a4SJacob Faibussowitsch static PetscErrorCode PetscDeviceCheckCapable_Private(PetscDeviceContext dctx, bool cond, const char descr[])
164d71ae5a4SJacob Faibussowitsch {
1650e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
1660e6b6b59SJacob Faibussowitsch   PetscCheck(cond, PETSC_COMM_SELF, PETSC_ERR_SUP, "Device context (id: %" PetscInt64_FMT ", name: %s, type: %s) can only handle %s host memory", PetscObjectCast(dctx)->id, PetscObjectCast(dctx)->name, dctx->device ? PetscDeviceTypes[dctx->device->type] : "unknown", descr);
1670e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
1680e6b6b59SJacob Faibussowitsch }
1690e6b6b59SJacob Faibussowitsch 
1700e6b6b59SJacob Faibussowitsch // A helper utility, since register is called from PetscDeviceRegisterMemory() and
1710e6b6b59SJacob Faibussowitsch // PetscDevicAllocate(). The latter also needs the generated id, so instead of making it search
1720e6b6b59SJacob Faibussowitsch // the map again we just return it here
173d71ae5a4SJacob Faibussowitsch static PetscErrorCode PetscDeviceRegisterMemory_Private(const void *PETSC_RESTRICT ptr, PetscMemType mtype, std::size_t size, PetscObjectId *PETSC_RESTRICT id = nullptr)
174d71ae5a4SJacob Faibussowitsch {
1750e6b6b59SJacob Faibussowitsch   auto      &map = memory_map.map;
1760e6b6b59SJacob Faibussowitsch   const auto it  = memory_map.search_for(ptr);
1770e6b6b59SJacob Faibussowitsch 
1780e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
1790e6b6b59SJacob Faibussowitsch   if (it == map.cend()) {
1800e6b6b59SJacob Faibussowitsch     // pointer was never registered with the map, insert it and bail
1810e6b6b59SJacob Faibussowitsch     const auto newid = PetscObjectNewId_Internal();
1820e6b6b59SJacob Faibussowitsch 
1830e6b6b59SJacob Faibussowitsch     if (PetscDefined(USE_DEBUG)) {
184*3398534bSJacob Faibussowitsch       const auto tmp = PointerAttributes(mtype, newid, size);
1850e6b6b59SJacob Faibussowitsch 
1860e6b6b59SJacob Faibussowitsch       for (const auto &entry : map) {
1870e6b6b59SJacob Faibussowitsch         // REVIEW ME: maybe this should just be handled...
1880e6b6b59SJacob Faibussowitsch         PetscCheck(!tmp.contains(ptr, entry.first), PETSC_COMM_SELF, PETSC_ERR_ORDER, "Trying to register pointer %p (memtype %s, size %zu) but it appears you have already registered a sub-region of it (pointer %p, memtype %s, size %zu). Must register the larger region first", ptr, PetscMemTypeToString(mtype), size,
1890e6b6b59SJacob Faibussowitsch                    entry.first, PetscMemTypeToString(entry.second.mtype), entry.second.size);
1900e6b6b59SJacob Faibussowitsch       }
1910e6b6b59SJacob Faibussowitsch     }
1920e6b6b59SJacob Faibussowitsch     // clang-format off
1930e6b6b59SJacob Faibussowitsch     if (id) *id = newid;
1940e6b6b59SJacob Faibussowitsch     PetscCallCXX(map.emplace(
1950e6b6b59SJacob Faibussowitsch       std::piecewise_construct,
1960e6b6b59SJacob Faibussowitsch       std::forward_as_tuple(const_cast<MemoryMap::map_type::key_type>(ptr)),
1970e6b6b59SJacob Faibussowitsch       std::forward_as_tuple(mtype, newid, size)
1980e6b6b59SJacob Faibussowitsch     ));
1990e6b6b59SJacob Faibussowitsch     // clang-format on
2000e6b6b59SJacob Faibussowitsch     PetscFunctionReturn(0);
2010e6b6b59SJacob Faibussowitsch   }
2020e6b6b59SJacob Faibussowitsch   if (PetscDefined(USE_DEBUG)) {
2030e6b6b59SJacob Faibussowitsch     const auto &old = it->second;
2040e6b6b59SJacob Faibussowitsch 
205*3398534bSJacob Faibussowitsch     PetscCheck(PointerAttributes(mtype, old.id, size) == old, PETSC_COMM_SELF, PETSC_ERR_LIB, "Pointer %p appears to have been previously allocated with memtype %s, size %zu and assigned id %" PetscInt64_FMT ", which does not match new values: (mtype %s, size %zu, id %" PetscInt64_FMT ")", it->first,
2060e6b6b59SJacob Faibussowitsch                PetscMemTypeToString(old.mtype), old.size, old.id, PetscMemTypeToString(mtype), size, old.id);
2070e6b6b59SJacob Faibussowitsch   }
2080e6b6b59SJacob Faibussowitsch   if (id) *id = it->second.id;
2090e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
2100e6b6b59SJacob Faibussowitsch }
2110e6b6b59SJacob Faibussowitsch 
2120e6b6b59SJacob Faibussowitsch /*@C
2130e6b6b59SJacob Faibussowitsch   PetscDeviceRegisterMemory - Register a pointer for use with device-aware memory system
2140e6b6b59SJacob Faibussowitsch 
2150e6b6b59SJacob Faibussowitsch   Not Collective
2160e6b6b59SJacob Faibussowitsch 
2170e6b6b59SJacob Faibussowitsch   Input Parameters:
2180e6b6b59SJacob Faibussowitsch + ptr   - The pointer to register
2190e6b6b59SJacob Faibussowitsch . mtype - The `PetscMemType` of the pointer
2200e6b6b59SJacob Faibussowitsch - size  - The size (in bytes) of the memory region
2210e6b6b59SJacob Faibussowitsch 
2220e6b6b59SJacob Faibussowitsch   Notes:
2230e6b6b59SJacob Faibussowitsch   `ptr` need not point to the beginning of the memory range, however the user should register
2240e6b6b59SJacob Faibussowitsch   the
2250e6b6b59SJacob Faibussowitsch 
2260e6b6b59SJacob Faibussowitsch   It's OK to re-register the same `ptr` repeatedly (subsequent registrations do nothing)
2270e6b6b59SJacob Faibussowitsch   however the given `mtype` and `size` must match the original registration.
2280e6b6b59SJacob Faibussowitsch 
2290e6b6b59SJacob Faibussowitsch   `size` may be 0 (in which case this routine does nothing).
2300e6b6b59SJacob Faibussowitsch 
2310e6b6b59SJacob Faibussowitsch   Level: intermediate
2320e6b6b59SJacob Faibussowitsch 
2330e6b6b59SJacob Faibussowitsch .seealso: `PetscDeviceMalloc()`, `PetscDeviceArrayCopy()`, `PetscDeviceFree()`,
2340e6b6b59SJacob Faibussowitsch `PetscDeviceArrayZero()`
2350e6b6b59SJacob Faibussowitsch @*/
236d71ae5a4SJacob Faibussowitsch PetscErrorCode PetscDeviceRegisterMemory(const void *PETSC_RESTRICT ptr, PetscMemType mtype, std::size_t size)
237d71ae5a4SJacob Faibussowitsch {
2380e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
2390e6b6b59SJacob Faibussowitsch   if (PetscMemTypeHost(mtype)) PetscValidPointer(ptr, 1);
2400e6b6b59SJacob Faibussowitsch   if (PetscUnlikely(!size)) PetscFunctionReturn(0); // there is no point registering empty range
2410e6b6b59SJacob Faibussowitsch   PetscCall(PetscDeviceRegisterMemory_Private(ptr, mtype, size));
2420e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
2430e6b6b59SJacob Faibussowitsch }
2440e6b6b59SJacob Faibussowitsch 
2456797ed33SJacob Faibussowitsch /*
2466797ed33SJacob Faibussowitsch   PetscDeviceAllocate_Private - Allocate device-aware memory
2470e6b6b59SJacob Faibussowitsch 
2480e6b6b59SJacob Faibussowitsch   Not Collective, Asynchronous, Auto-dependency aware
2490e6b6b59SJacob Faibussowitsch 
2500e6b6b59SJacob Faibussowitsch   Input Parameters:
2510e6b6b59SJacob Faibussowitsch + dctx      - The `PetscDeviceContext` used to allocate the memory
2520e6b6b59SJacob Faibussowitsch . clear     - Whether or not the memory should be zeroed
2530e6b6b59SJacob Faibussowitsch . mtype     - The type of memory to allocate
2546797ed33SJacob Faibussowitsch . n         - The amount (in bytes) to allocate
2556797ed33SJacob Faibussowitsch - alignment - The alignment requirement (in bytes) of the allocated pointer
2560e6b6b59SJacob Faibussowitsch 
2570e6b6b59SJacob Faibussowitsch   Output Parameter:
2580e6b6b59SJacob Faibussowitsch . ptr - The pointer to store the result in
2590e6b6b59SJacob Faibussowitsch 
2600e6b6b59SJacob Faibussowitsch   Notes:
2610e6b6b59SJacob Faibussowitsch   The user should prefer `PetscDeviceMalloc()` over this routine as it automatically computes
2626797ed33SJacob Faibussowitsch   the size of the allocation and alignment based on the size of the datatype.
2630e6b6b59SJacob Faibussowitsch 
2646797ed33SJacob Faibussowitsch   If the user is unsure about `alignment` -- or unable to compute it -- passing
2656797ed33SJacob Faibussowitsch   `PETSC_MEMALIGN` will always work, though the user should beware that this may be quite
2666797ed33SJacob Faibussowitsch   wasteful for very small allocations.
2676797ed33SJacob Faibussowitsch 
2686797ed33SJacob Faibussowitsch   Memory allocated with this function must be freed with `PetscDeviceFree()` (or
2696797ed33SJacob Faibussowitsch   `PetscDeviceDeallocate_Private()`).
2706797ed33SJacob Faibussowitsch 
2716797ed33SJacob Faibussowitsch   If `n` is zero, then `ptr` is set to `PETSC_NULLPTR`.
2726797ed33SJacob Faibussowitsch 
2736797ed33SJacob Faibussowitsch   This routine falls back to using `PetscMalloc1()` or `PetscCalloc1()` (depending on the value
2746797ed33SJacob Faibussowitsch   of `clear`) if PETSc was not configured with device support. The user should note that
2756797ed33SJacob Faibussowitsch   `mtype` and `alignment` are ignored in this case, as these routines allocate only host memory
2766797ed33SJacob Faibussowitsch   aligned to `PETSC_MEMALIGN`.
2770e6b6b59SJacob Faibussowitsch 
2780e6b6b59SJacob Faibussowitsch   Note result stored `ptr` is immediately valid and the user may freely inspect or manipulate
2790e6b6b59SJacob Faibussowitsch   its value on function return, i.e.\:
2800e6b6b59SJacob Faibussowitsch 
2810e6b6b59SJacob Faibussowitsch .vb
2820e6b6b59SJacob Faibussowitsch   PetscInt *ptr;
2830e6b6b59SJacob Faibussowitsch 
2846797ed33SJacob Faibussowitsch   PetscDeviceAllocate_Private(dctx, PETSC_FALSE, PETSC_MEMTYPE_DEVICE, 20, alignof(PetscInt), (void**)&ptr);
2850e6b6b59SJacob Faibussowitsch 
2860e6b6b59SJacob Faibussowitsch   PetscInt *sub_ptr = ptr + 10; // OK, no need to synchronize
2870e6b6b59SJacob Faibussowitsch 
2880e6b6b59SJacob Faibussowitsch   ptr[0] = 10; // ERROR, directly accessing contents of ptr is undefined until synchronization
2890e6b6b59SJacob Faibussowitsch .ve
2900e6b6b59SJacob Faibussowitsch 
2910e6b6b59SJacob Faibussowitsch   DAG representation:
2920e6b6b59SJacob Faibussowitsch .vb
2930e6b6b59SJacob Faibussowitsch   time ->
2940e6b6b59SJacob Faibussowitsch 
2950e6b6b59SJacob Faibussowitsch   -> dctx - |= CALL =| -\- dctx -->
2960e6b6b59SJacob Faibussowitsch                          \- ptr ->
2970e6b6b59SJacob Faibussowitsch .ve
2980e6b6b59SJacob Faibussowitsch 
2990e6b6b59SJacob Faibussowitsch   Level: intermediate
3000e6b6b59SJacob Faibussowitsch 
3010e6b6b59SJacob Faibussowitsch .N ASYNC_API
3020e6b6b59SJacob Faibussowitsch 
3036797ed33SJacob Faibussowitsch .seealso: `PetscDeviceMalloc()`, `PetscDeviceFree()`, `PetscDeviceDeallocate_Private()`,
3046797ed33SJacob Faibussowitsch `PetscDeviceArrayCopy()`, `PetscDeviceArrayZero()`, `PetscMemType`
3056797ed33SJacob Faibussowitsch */
306d71ae5a4SJacob Faibussowitsch PetscErrorCode PetscDeviceAllocate_Private(PetscDeviceContext dctx, PetscBool clear, PetscMemType mtype, std::size_t n, std::size_t alignment, void **PETSC_RESTRICT ptr)
307d71ae5a4SJacob Faibussowitsch {
3080e6b6b59SJacob Faibussowitsch   PetscObjectId id = 0;
3090e6b6b59SJacob Faibussowitsch 
3100e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
3116797ed33SJacob Faibussowitsch   if (PetscDefined(USE_DEBUG)) {
3126797ed33SJacob Faibussowitsch     const auto is_power_of_2 = [](std::size_t num) { return (num & (num - 1)) == 0; };
3136797ed33SJacob Faibussowitsch 
3146797ed33SJacob Faibussowitsch     PetscCheck(alignment != 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Requested alignment %zu cannot be 0", alignment);
3156797ed33SJacob Faibussowitsch     PetscCheck(is_power_of_2(alignment), PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Requested alignment %zu must be a power of 2", alignment);
3166797ed33SJacob Faibussowitsch   }
3176797ed33SJacob Faibussowitsch   PetscValidPointer(ptr, 6);
3180e6b6b59SJacob Faibussowitsch   *ptr = nullptr;
3190e6b6b59SJacob Faibussowitsch   if (PetscUnlikely(!n)) PetscFunctionReturn(0);
3200e6b6b59SJacob Faibussowitsch   PetscCall(memory_map.register_finalize());
3210e6b6b59SJacob Faibussowitsch   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
3220e6b6b59SJacob Faibussowitsch 
3230e6b6b59SJacob Faibussowitsch   // get our pointer here
3240e6b6b59SJacob Faibussowitsch   if (dctx->ops->memalloc) {
3256797ed33SJacob Faibussowitsch     PetscUseTypeMethod(dctx, memalloc, clear, mtype, n, alignment, ptr);
3260e6b6b59SJacob Faibussowitsch   } else {
3270e6b6b59SJacob Faibussowitsch     PetscCall(PetscDeviceCheckCapable_Private(dctx, PetscMemTypeHost(mtype), "allocating"));
3280e6b6b59SJacob Faibussowitsch     PetscCall(PetscMallocA(1, clear, __LINE__, PETSC_FUNCTION_NAME, __FILE__, n, ptr));
3290e6b6b59SJacob Faibussowitsch   }
3300e6b6b59SJacob Faibussowitsch   PetscCall(PetscDeviceRegisterMemory_Private(*ptr, mtype, n, &id));
3310e6b6b59SJacob Faibussowitsch   // Note this is a "write" so that the next dctx to try and read from the pointer has to wait
3320e6b6b59SJacob Faibussowitsch   // for the allocation to be ready
3330e6b6b59SJacob Faibussowitsch   PetscCall(PetscDeviceContextMarkIntentFromID(dctx, id, PETSC_MEMORY_ACCESS_WRITE, "memory allocation"));
3340e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
3350e6b6b59SJacob Faibussowitsch }
3360e6b6b59SJacob Faibussowitsch 
3376797ed33SJacob Faibussowitsch /*
3386797ed33SJacob Faibussowitsch   PetscDeviceDeallocate_Private - Free device-aware memory
3390e6b6b59SJacob Faibussowitsch 
3400e6b6b59SJacob Faibussowitsch   Not Collective, Asynchronous, Auto-dependency aware
3410e6b6b59SJacob Faibussowitsch 
3420e6b6b59SJacob Faibussowitsch   Input Parameters:
3430e6b6b59SJacob Faibussowitsch + dctx  - The `PetscDeviceContext` used to free the memory
3440e6b6b59SJacob Faibussowitsch - ptr   - The pointer to free
3450e6b6b59SJacob Faibussowitsch 
3460e6b6b59SJacob Faibussowitsch   Notes:
3470e6b6b59SJacob Faibussowitsch   `ptr` must have been allocated using any of `PetscDeviceMalloc()`, `PetscDeviceCalloc()` or
3486797ed33SJacob Faibussowitsch   `PetscDeviceAllocate_Private()`, or registered with the system via `PetscDeviceRegisterMemory()`.
3490e6b6b59SJacob Faibussowitsch 
3500e6b6b59SJacob Faibussowitsch   The user should prefer `PetscDeviceFree()` over this routine as it automatically sets `ptr`
3510e6b6b59SJacob Faibussowitsch   to `PETSC_NULLPTR` on successful deallocation.
3520e6b6b59SJacob Faibussowitsch 
3530e6b6b59SJacob Faibussowitsch   `ptr` may be `NULL`.
3540e6b6b59SJacob Faibussowitsch 
3556797ed33SJacob Faibussowitsch   This routine falls back to using `PetscFree()` if PETSc was not configured with device
3566797ed33SJacob Faibussowitsch   support. The user should note that `PetscFree()` frees only host memory.
3576797ed33SJacob Faibussowitsch 
3580e6b6b59SJacob Faibussowitsch   DAG representation:
3590e6b6b59SJacob Faibussowitsch .vb
3600e6b6b59SJacob Faibussowitsch   time ->
3610e6b6b59SJacob Faibussowitsch 
3620e6b6b59SJacob Faibussowitsch   -> dctx -/- |= CALL =| - dctx ->
3630e6b6b59SJacob Faibussowitsch   -> ptr -/
3640e6b6b59SJacob Faibussowitsch .ve
3650e6b6b59SJacob Faibussowitsch 
3660e6b6b59SJacob Faibussowitsch   Level: intermediate
3670e6b6b59SJacob Faibussowitsch 
3680e6b6b59SJacob Faibussowitsch .N ASYNC_API
3690e6b6b59SJacob Faibussowitsch 
3706797ed33SJacob Faibussowitsch .seealso: `PetscDeviceFree()`, `PetscDeviceAllocate_Private()`
3716797ed33SJacob Faibussowitsch */
372d71ae5a4SJacob Faibussowitsch PetscErrorCode PetscDeviceDeallocate_Private(PetscDeviceContext dctx, void *PETSC_RESTRICT ptr)
373d71ae5a4SJacob Faibussowitsch {
3740e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
3750e6b6b59SJacob Faibussowitsch   if (ptr) {
3760e6b6b59SJacob Faibussowitsch     auto      &map      = memory_map.map;
3770e6b6b59SJacob Faibussowitsch     const auto found_it = map.find(const_cast<MemoryMap::map_type::key_type>(ptr));
3780e6b6b59SJacob Faibussowitsch 
3790e6b6b59SJacob Faibussowitsch     if (PetscUnlikelyDebug(found_it == map.end())) {
3800e6b6b59SJacob Faibussowitsch       // OK this is a bad pointer, now determine why
3810e6b6b59SJacob Faibussowitsch       const auto it = memory_map.search_for(ptr);
3820e6b6b59SJacob Faibussowitsch 
3830e6b6b59SJacob Faibussowitsch       // if it is map.cend() then no allocation owns it, meaning it was not allocated by us!
3846797ed33SJacob Faibussowitsch       PetscCheck(it != map.cend(), PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Pointer %p was not allocated via PetscDeviceAllocate_Private()", ptr);
3850e6b6b59SJacob Faibussowitsch       // if we are here then we did allocate it but the user has tried to do something along
3860e6b6b59SJacob Faibussowitsch       // the lines of:
3870e6b6b59SJacob Faibussowitsch       //
3880e6b6b59SJacob Faibussowitsch       // allocate(&ptr, size);
3890e6b6b59SJacob Faibussowitsch       // deallocate(ptr+5);
3900e6b6b59SJacob Faibussowitsch       //
3910e6b6b59SJacob Faibussowitsch       SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Attempting to deallocate pointer %p which is a suballocation of %p (memtype %s, id %" PetscInt64_FMT ", size %zu bytes)", ptr, it->first, PetscMemTypeToString(it->second.mtype), it->second.id,
3920e6b6b59SJacob Faibussowitsch               it->second.size);
3930e6b6b59SJacob Faibussowitsch     }
3940e6b6b59SJacob Faibussowitsch 
3956797ed33SJacob Faibussowitsch     PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
3960e6b6b59SJacob Faibussowitsch     // mark intent BEFORE we free, note we mark as write so that we are made to wait on any
3970e6b6b59SJacob Faibussowitsch     // outstanding reads (don't want to kill the pointer before they are done)
3980e6b6b59SJacob Faibussowitsch     PetscCall(PetscDeviceContextMarkIntentFromID(dctx, found_it->second.id, PETSC_MEMORY_ACCESS_WRITE, "memory deallocation"));
3990e6b6b59SJacob Faibussowitsch     // do free
4000e6b6b59SJacob Faibussowitsch     if (dctx->ops->memfree) {
4010e6b6b59SJacob Faibussowitsch       PetscUseTypeMethod(dctx, memfree, found_it->second.mtype, (void **)&ptr);
4020e6b6b59SJacob Faibussowitsch     } else {
4030e6b6b59SJacob Faibussowitsch       PetscCall(PetscDeviceCheckCapable_Private(dctx, PetscMemTypeHost(found_it->second.mtype), "freeing"));
4040e6b6b59SJacob Faibussowitsch     }
4050e6b6b59SJacob Faibussowitsch     // if ptr still exists, then the device context could not handle it
4060e6b6b59SJacob Faibussowitsch     if (ptr) PetscCall(PetscFree(ptr));
4070e6b6b59SJacob Faibussowitsch     PetscCallCXX(map.erase(found_it));
4080e6b6b59SJacob Faibussowitsch   }
4090e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
4100e6b6b59SJacob Faibussowitsch }
4110e6b6b59SJacob Faibussowitsch 
4120e6b6b59SJacob Faibussowitsch /*@C
4130e6b6b59SJacob Faibussowitsch   PetscDeviceMemcpy - Copy memory in a device-aware manner
4140e6b6b59SJacob Faibussowitsch 
4150e6b6b59SJacob Faibussowitsch   Not Collective, Asynchronous, Auto-dependency aware
4160e6b6b59SJacob Faibussowitsch 
4170e6b6b59SJacob Faibussowitsch   Input Parameters:
4180e6b6b59SJacob Faibussowitsch + dctx - The `PetscDeviceContext` used to copy the memory
4190e6b6b59SJacob Faibussowitsch . dest - The pointer to copy to
4200e6b6b59SJacob Faibussowitsch . src  - The pointer to copy from
4210e6b6b59SJacob Faibussowitsch - n    - The amount (in bytes) to copy
4220e6b6b59SJacob Faibussowitsch 
4230e6b6b59SJacob Faibussowitsch   Notes:
4246797ed33SJacob Faibussowitsch   Both `dest` and `src` must have been allocated by `PetscDeviceMalloc()` or
4256797ed33SJacob Faibussowitsch   `PetscDeviceCalloc()`.
4260e6b6b59SJacob Faibussowitsch 
4270e6b6b59SJacob Faibussowitsch   `src` and `dest` cannot overlap.
4280e6b6b59SJacob Faibussowitsch 
4290e6b6b59SJacob Faibussowitsch   If both `src` and `dest` are on the host this routine is fully synchronous.
4300e6b6b59SJacob Faibussowitsch 
4310e6b6b59SJacob Faibussowitsch   The user should prefer `PetscDeviceArrayCopy()` over this routine as it automatically
4320e6b6b59SJacob Faibussowitsch   computes the number of bytes to copy from the size of the pointer types.
4330e6b6b59SJacob Faibussowitsch 
4340e6b6b59SJacob Faibussowitsch   DAG representation:
4350e6b6b59SJacob Faibussowitsch .vb
4360e6b6b59SJacob Faibussowitsch   time ->
4370e6b6b59SJacob Faibussowitsch 
4380e6b6b59SJacob Faibussowitsch   -> dctx - |= CALL =| - dctx ->
4390e6b6b59SJacob Faibussowitsch   -> dest --------------------->
4400e6b6b59SJacob Faibussowitsch   -> src ---------------------->
4410e6b6b59SJacob Faibussowitsch .ve
4420e6b6b59SJacob Faibussowitsch 
4430e6b6b59SJacob Faibussowitsch   Level: intermediate
4440e6b6b59SJacob Faibussowitsch 
4450e6b6b59SJacob Faibussowitsch .N ASYNC_API
4460e6b6b59SJacob Faibussowitsch 
4476797ed33SJacob Faibussowitsch .seealso: `PetscDeviceArrayCopy()`, `PetscDeviceMalloc()`, `PetscDeviceCalloc()`,
4486797ed33SJacob Faibussowitsch `PetscDeviceFree()`
4490e6b6b59SJacob Faibussowitsch @*/
450d71ae5a4SJacob Faibussowitsch PetscErrorCode PetscDeviceMemcpy(PetscDeviceContext dctx, void *PETSC_RESTRICT dest, const void *PETSC_RESTRICT src, std::size_t n)
451d71ae5a4SJacob Faibussowitsch {
4520e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
4530e6b6b59SJacob Faibussowitsch   if (!n) PetscFunctionReturn(0);
4540e6b6b59SJacob Faibussowitsch   PetscCheck(dest, PETSC_COMM_SELF, PETSC_ERR_POINTER, "Trying to copy to a NULL pointer");
4550e6b6b59SJacob Faibussowitsch   PetscCheck(src, PETSC_COMM_SELF, PETSC_ERR_POINTER, "Trying to copy from a NULL pointer");
4560e6b6b59SJacob Faibussowitsch   if (dest == src) PetscFunctionReturn(0);
4570e6b6b59SJacob Faibussowitsch   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
4580e6b6b59SJacob Faibussowitsch   {
4596797ed33SJacob Faibussowitsch     const auto dest_it = memory_map.search_for(dest, true);
4606797ed33SJacob Faibussowitsch     const auto src_it  = memory_map.search_for(src, true);
4616797ed33SJacob Faibussowitsch     const auto mode    = PetscMemTypeToDeviceCopyMode(dest_it->second.mtype, src_it->second.mtype);
4620e6b6b59SJacob Faibussowitsch 
4630e6b6b59SJacob Faibussowitsch     PetscCall(PetscDeviceContextMarkIntentFromID(dctx, src_it->second.id, PETSC_MEMORY_ACCESS_READ, "memory copy (src)"));
4640e6b6b59SJacob Faibussowitsch     PetscCall(PetscDeviceContextMarkIntentFromID(dctx, dest_it->second.id, PETSC_MEMORY_ACCESS_WRITE, "memory copy (dest)"));
4650e6b6b59SJacob Faibussowitsch     // perform the copy
4660e6b6b59SJacob Faibussowitsch     if (dctx->ops->memcopy) {
4670e6b6b59SJacob Faibussowitsch       PetscUseTypeMethod(dctx, memcopy, dest, src, n, mode);
4686797ed33SJacob Faibussowitsch       if (mode == PETSC_DEVICE_COPY_HTOD) {
4696797ed33SJacob Faibussowitsch         PetscCall(PetscLogCpuToGpu(n));
4706797ed33SJacob Faibussowitsch       } else if (mode == PETSC_DEVICE_COPY_DTOH) {
4716797ed33SJacob Faibussowitsch         PetscCall(PetscLogGpuToCpu(n));
4726797ed33SJacob Faibussowitsch       }
4730e6b6b59SJacob Faibussowitsch     } else {
4740e6b6b59SJacob Faibussowitsch       // REVIEW ME: we might potentially need to sync here if the memory is device-allocated
4750e6b6b59SJacob Faibussowitsch       // (pinned) but being copied by a host dctx
4760e6b6b59SJacob Faibussowitsch       PetscCall(PetscDeviceCheckCapable_Private(dctx, mode == PETSC_DEVICE_COPY_HTOH, "copying"));
4770e6b6b59SJacob Faibussowitsch       PetscCall(PetscMemcpy(dest, src, n));
4780e6b6b59SJacob Faibussowitsch     }
4790e6b6b59SJacob Faibussowitsch   }
4800e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
4810e6b6b59SJacob Faibussowitsch }
4820e6b6b59SJacob Faibussowitsch 
4830e6b6b59SJacob Faibussowitsch /*@C
4840e6b6b59SJacob Faibussowitsch   PetscDeviceMemset - Memset device-aware memory
4850e6b6b59SJacob Faibussowitsch 
4860e6b6b59SJacob Faibussowitsch   Not Collective, Asynchronous, Auto-dependency aware
4870e6b6b59SJacob Faibussowitsch 
4880e6b6b59SJacob Faibussowitsch   Input Parameters:
4890e6b6b59SJacob Faibussowitsch + dctx  - The `PetscDeviceContext` used to memset the memory
4900e6b6b59SJacob Faibussowitsch . ptr   - The pointer to the memory
4910e6b6b59SJacob Faibussowitsch . v     - The value to set
4920e6b6b59SJacob Faibussowitsch - n     - The amount (in bytes) to set
4930e6b6b59SJacob Faibussowitsch 
4940e6b6b59SJacob Faibussowitsch   Notes:
4956797ed33SJacob Faibussowitsch   `ptr` must have been allocated by `PetscDeviceMalloc()` or `PetscDeviceCalloc()`.
4960e6b6b59SJacob Faibussowitsch 
4970e6b6b59SJacob Faibussowitsch   The user should prefer `PetscDeviceArrayZero()` over this routine as it automatically
4980e6b6b59SJacob Faibussowitsch   computes the number of bytes to copy from the size of the pointer types, though they should
4990e6b6b59SJacob Faibussowitsch   note that it only zeros memory.
5000e6b6b59SJacob Faibussowitsch 
5010e6b6b59SJacob Faibussowitsch   This routine is analogous to `memset()`. That is, this routine copies the value
5020e6b6b59SJacob Faibussowitsch   `static_cast<unsigned char>(v)` into each of the first count characters of the object pointed
5030e6b6b59SJacob Faibussowitsch   to by `dest`.
5040e6b6b59SJacob Faibussowitsch 
5050e6b6b59SJacob Faibussowitsch   If `dest` is on device, this routine is asynchronous.
5060e6b6b59SJacob Faibussowitsch 
5070e6b6b59SJacob Faibussowitsch   DAG representation:
5080e6b6b59SJacob Faibussowitsch .vb
5090e6b6b59SJacob Faibussowitsch   time ->
5100e6b6b59SJacob Faibussowitsch 
5110e6b6b59SJacob Faibussowitsch   -> dctx - |= CALL =| - dctx ->
5120e6b6b59SJacob Faibussowitsch   -> dest --------------------->
5130e6b6b59SJacob Faibussowitsch .ve
5140e6b6b59SJacob Faibussowitsch 
5150e6b6b59SJacob Faibussowitsch   Level: intermediate
5160e6b6b59SJacob Faibussowitsch 
5170e6b6b59SJacob Faibussowitsch .N ASYNC_API
5180e6b6b59SJacob Faibussowitsch 
5196797ed33SJacob Faibussowitsch .seealso: `PetscDeviceArrayZero()`, `PetscDeviceMalloc()`, `PetscDeviceCalloc()`,
5206797ed33SJacob Faibussowitsch `PetscDeviceFree()`
5210e6b6b59SJacob Faibussowitsch @*/
522d71ae5a4SJacob Faibussowitsch PetscErrorCode PetscDeviceMemset(PetscDeviceContext dctx, void *ptr, PetscInt v, std::size_t n)
523d71ae5a4SJacob Faibussowitsch {
5240e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
5250e6b6b59SJacob Faibussowitsch   if (PetscUnlikely(!n)) PetscFunctionReturn(0);
5260e6b6b59SJacob Faibussowitsch   PetscCheck(ptr, PETSC_COMM_SELF, PETSC_ERR_POINTER, "Trying to memset a NULL pointer");
5270e6b6b59SJacob Faibussowitsch   PetscCall(PetscDeviceContextGetOptionalNullContext_Internal(&dctx));
5280e6b6b59SJacob Faibussowitsch   {
5296797ed33SJacob Faibussowitsch     const auto ptr_it = memory_map.search_for(ptr, true);
5306797ed33SJacob Faibussowitsch     const auto mtype  = ptr_it->second.mtype;
5310e6b6b59SJacob Faibussowitsch 
5320e6b6b59SJacob Faibussowitsch     PetscCall(PetscDeviceContextMarkIntentFromID(dctx, ptr_it->second.id, PETSC_MEMORY_ACCESS_WRITE, "memory set"));
5330e6b6b59SJacob Faibussowitsch     if (dctx->ops->memset) {
5346797ed33SJacob Faibussowitsch       PetscUseTypeMethod(dctx, memset, mtype, ptr, v, n);
5350e6b6b59SJacob Faibussowitsch     } else {
5360e6b6b59SJacob Faibussowitsch       // REVIEW ME: we might potentially need to sync here if the memory is device-allocated
5370e6b6b59SJacob Faibussowitsch       // (pinned) but being memset by a host dctx
5386797ed33SJacob Faibussowitsch       PetscCall(PetscDeviceCheckCapable_Private(dctx, PetscMemTypeHost(mtype), "memsetting"));
5390e6b6b59SJacob Faibussowitsch       std::memset(ptr, static_cast<int>(v), n);
5400e6b6b59SJacob Faibussowitsch     }
5410e6b6b59SJacob Faibussowitsch   }
5420e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
5430e6b6b59SJacob Faibussowitsch }
544