LCOV - code coverage report
Current view: top level - src/util - memory.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 75 84 89.3 %
Date: 2025-03-25 01:19:55 Functions: 13 14 92.9 %
Branches: 24 56 42.9 %

           Branch data     Line data    Source code
       1                 :            : #include <mutable/util/memory.hpp>
       2                 :            : 
       3                 :            : #include <mutable/util/macro.hpp>
       4                 :            : #include <cerrno>
       5                 :            : #include <climits>
       6                 :            : #include <cstring>
       7                 :            : #include <exception>
       8                 :            : #include <stdexcept>
       9                 :            : 
      10                 :            : #if __linux
      11                 :            : #include <sys/mman.h>
      12                 :            : #include <sys/stat.h>
      13                 :            : #include <sys/types.h>
      14                 :            : #include <unistd.h>
      15                 :            : #elif __APPLE__
      16                 :            : #include <fcntl.h>
      17                 :            : #include <sys/mman.h>
      18                 :            : #include <sys/stat.h>
      19                 :            : #include <unistd.h>
      20                 :            : #endif
      21                 :            : 
      22                 :            : 
      23                 :            : using namespace m;
      24                 :            : using namespace m::memory;
      25                 :            : 
      26                 :            : 
      27                 :            : /*======================================================================================================================
      28                 :            :  * Allocator
      29                 :            :  *====================================================================================================================*/
      30                 :            : 
      31                 :        363 : Allocator::Allocator()
      32                 :        363 : {
      33                 :            : #if __linux
      34                 :        363 :     fd_ = memfd_create("rewire_allocator", MFD_CLOEXEC);
      35                 :            : #elif __APPLE__
      36                 :            :     auto name = std::to_string(getpid());
      37                 :            :     fd_ = shm_open(name.c_str(), O_RDWR | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
      38                 :            :     shm_unlink(name.c_str());
      39                 :            :     ftruncate(fd_, 1UL << 44); // preallocate memory because resizing with `ftruncate()` is not supported on macOS
      40                 :            : #endif
      41         [ +  - ]:        363 :     if (fd_ == -1)
      42         [ #  # ]:          0 :         throw std::runtime_error(strerror(errno));
      43                 :        363 : }
      44                 :            : 
      45                 :        363 : Allocator::~Allocator()
      46                 :        363 : {
      47         [ +  - ]:        363 :     close(fd_);
      48                 :        363 : }
      49                 :            : 
      50                 :       2016 : Memory Allocator::create_memory(void *addr, std::size_t size, std::size_t offset)
      51                 :            : {
      52                 :       2016 :     return Memory(*this, addr, size, offset);
      53                 :            : }
      54                 :            : 
      55                 :            : 
      56                 :            : /*======================================================================================================================
      57                 :            :  * AddressSpace
      58                 :            :  *====================================================================================================================*/
      59                 :            : 
      60                 :       1658 : AddressSpace::AddressSpace(std::size_t size)
      61                 :            : {
      62         [ -  + ]:       1658 :     if (size != 0) {
      63                 :       1658 :         auto aligned_size = Ceil_To_Next_Page(size);
      64                 :       1658 :         addr_ = mmap(nullptr, aligned_size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, /* fd= */ -1, /* offset= */ 0);
      65         [ +  - ]:       1658 :         if (addr_ == MAP_FAILED)
      66         [ #  # ]:          0 :             throw std::runtime_error(strerror(errno));
      67                 :       1658 :         size_ = aligned_size;
      68                 :       1658 :     }
      69                 :       1658 : }
      70                 :            : 
      71         [ +  + ]:       3180 : AddressSpace::~AddressSpace() { if (addr_) munmap(addr_, size_); }
      72                 :            : 
      73                 :            : 
      74                 :          1 : /*======================================================================================================================
      75                 :            :  * Memory
      76                 :            :  *====================================================================================================================*/
      77                 :            : 
      78                 :       2016 : Memory::Memory(Allocator &allocator, void *addr, std::size_t size, std::size_t offset)
      79                 :       2016 :     : allocator_(&allocator)
      80                 :       2016 :     , addr_(addr)
      81                 :       2016 :     , size_(size)
      82                 :       2016 :     , offset_(offset)
      83                 :       2016 : { }
      84                 :            : 
      85                 :       1663 : void Memory::map(std::size_t size, std::size_t offset_src, const AddressSpace &vm, std::size_t offset_dst) const
      86                 :            : {
      87                 :       1663 :     M_insist(size <= this->size(), "size exceeds memory size");
      88                 :       1663 :     M_insist(offset_src < this->size(), "source offset out of bounds");
      89                 :       1663 :     M_insist(Is_Page_Aligned(offset_src), "source offset is not page aligned");
      90                 :       1663 :     M_insist(offset_src + size <= this->size(), "source range out of bounds");
      91                 :            : 
      92                 :       1663 :     M_insist(size <= vm.size(), "size exceeds address space");
      93                 :       1663 :     M_insist(offset_dst < vm.size(), "destination offset out of bounds");
      94                 :       1663 :     M_insist(Is_Page_Aligned(offset_dst), "destination offset is not page aligned");
      95                 :       1663 :     M_insist(offset_dst + size <= vm.size(), "destination range out of bounds");
      96                 :            : 
      97                 :       1663 :     void *dst_addr = vm.as<uint8_t*>() + offset_dst;
      98                 :       3326 :     void *addr = mmap(dst_addr, size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, allocator().fd(),
      99                 :       1663 :                       this->offset() + offset_src);
     100         [ +  - ]:       1663 :     if (addr == MAP_FAILED)
     101         [ #  # ]:          0 :         throw std::runtime_error(strerror(errno));
     102         [ +  - ]:       1663 :     if (addr != dst_addr)
     103         [ #  # ]:          0 :         throw std::runtime_error("MAP_FIXED failed");
     104                 :       1663 : }
     105                 :            : 
     106                 :            : M_LCOV_EXCL_START
     107                 :            : void Memory::dump(std::ostream &out) const
     108                 :            : {
     109                 :            :     out << "Memory at virtual address " << addr() << " of size " << size() << " bytes mapped to offset " << offset()
     110                 :            :         << " of file descriptor " << allocator().fd() << std::endl;
     111                 :            : }
     112                 :            : void Memory::dump() const { dump(std::cerr); }
     113                 :            : M_LCOV_EXCL_STOP
     114                 :            : 
     115                 :            : 
     116                 :            : /*======================================================================================================================
     117                 :            :  * LinearAllocator
     118                 :            :  *====================================================================================================================*/
     119                 :            : 
     120                 :       2016 : Memory LinearAllocator::allocate(std::size_t size)
     121                 :            : {
     122         [ +  - ]:       2016 :     if (size == 0) return Memory();
     123                 :            : 
     124                 :       2016 :     const std::size_t aligned_size = Ceil_To_Next_Page(size);
     125                 :       2016 :     M_insist(aligned_size >= size, "size must be ceiled");
     126                 :       2016 :     M_insist(Is_Page_Aligned(aligned_size), "not page aligned");
     127                 :            : #if __linux
     128         [ +  - ]:       2016 :     if (ftruncate(fd(), offset_ + aligned_size))
     129         [ #  # ]:          0 :         throw std::runtime_error(strerror(errno));
     130                 :            : #elif __APPLE__
     131                 :            :     /* Nothing to be done.
     132                 :            :      * Memory has been preallocated because resizing with `ftruncate()` is not supported on macOS.  */
     133                 :            : #endif
     134                 :            : 
     135                 :       2016 :     void *addr = mmap(nullptr, aligned_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd(), offset_);
     136         [ +  - ]:       2016 :     if (addr == MAP_FAILED)
     137         [ #  # ]:          0 :         throw std::runtime_error(strerror(errno));
     138                 :            : 
     139                 :       2016 :     auto mem = create_memory(addr, aligned_size, offset_);
     140         [ +  - ]:       2016 :     allocations_.push_back(offset_);
     141                 :       2016 :     offset_ += aligned_size;
     142                 :       2016 :     return mem;
     143         [ +  - ]:       4032 : }
     144                 :            : 
     145                 :            : namespace {
     146                 :            : 
     147                 :            : /** Set MSB of a std::size_t. */
     148                 :            : constexpr std::size_t MSB = std::size_t(1UL) << (sizeof(MSB) * CHAR_BIT - 1U);
     149                 :            : 
     150                 :            : /** Mark offset for deallocation by setting MSB.  This is safe as offsets with set MSB would exceed maximum file
     151                 :            :  * size. */
     152                 :       2016 : std::size_t mark_for_deallocation(std::size_t offset) { return offset | MSB; }
     153                 :            : 
     154                 :            : /** Check whether MSB is set, i.e. offset marked for deallocation. */
     155                 :          5 : bool is_marked_for_deallocation(uintptr_t offset) { return offset & MSB; }
     156                 :            : 
     157                 :            : /** Clear MSB. */
     158                 :       2016 : std::size_t unmarked(std::size_t offset) { return offset & ~MSB; }
     159                 :            : 
     160                 :            : }
     161                 :            : 
     162                 :       2016 : void LinearAllocator::deallocate(Memory &&mem)
     163                 :            : {
     164         [ +  - ]:       2016 :     if (&mem.allocator() != this)
     165         [ #  # ]:          0 :         throw std::invalid_argument("memory has not been allocated by this allocator");
     166                 :            : 
     167                 :            :     /* Find the allocation. */
     168                 :       2016 :     auto it = std::find(allocations_.rbegin(), allocations_.rend(), mem.offset());
     169         [ +  - ]:       2016 :     if (it == allocations_.rend())
     170         [ #  # ]:          0 :         throw std::invalid_argument("memory has not been allocated by this allocator or has already been deallocated");
     171                 :            : 
     172                 :            :     /* Unmap the mapped memory. */
     173                 :       2016 :     munmap(mem.addr(), mem.size());
     174                 :            : 
     175                 :            : #if __linux
     176                 :       2016 :     *it = mark_for_deallocation(mem.offset());
     177                 :            : 
     178                 :            :     /* Reclaim memory if this is the last allocation. */
     179         [ +  + ]:       2016 :     if (it == allocations_.rbegin()) {
     180                 :            :         /* This is the last allocation.  Check how much memory we can reclaim. */
     181                 :            :         std::size_t new_size_of_file;
     182                 :       2015 :         do {
     183                 :       2016 :             new_size_of_file = unmarked(*it);
     184                 :       2016 :             ++it;
     185   [ +  +  +  + ]:       2016 :         } while (it != allocations_.rend() and is_marked_for_deallocation(*it));
     186         [ +  + ]:       2015 :         M_insist(it == allocations_.rend() or not is_marked_for_deallocation(*it));
     187                 :            : 
     188                 :            :         /* Truncate file to reclaim memory. */
     189         [ +  - ]:       2015 :         if (ftruncate(fd(), new_size_of_file))
     190         [ #  # ]:          0 :             throw std::runtime_error(strerror(errno));
     191                 :       2015 :         offset_ = new_size_of_file;
     192                 :            : 
     193                 :            :         /* Remove reclaimed allocations. */
     194                 :       2015 :         allocations_.resize(std::distance(it, allocations_.rend()));
     195                 :       2015 :     }
     196                 :            : #elif __APPLE__
     197                 :            :     /* Nothing to be done.
     198                 :            :      * Memory has been preallocated because resizing with `ftruncate()` is not supported on macOS.  */
     199                 :            : #endif
     200                 :       2016 : }

Generated by: LCOV version 1.16