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 : }
|