1 module libd.memory.allocator.pageallocator; 2 import libd.threading.locks, libd.util.errorhandling; 3 import libd.datastructures.bitkeeper, libd.datastructures.linkedlist; 4 5 struct PageAllocation 6 { 7 ubyte[] memory; // Does NOT include guard pages. 8 size_t pageCount; // ^^ 9 bool hasGuardPage; 10 BitKeeperSlice bitKeepSlice; 11 } 12 13 // This allocator doesn't follow the standard allocator API, since it's intended to be used for more 14 // specialised purposes rather than general allocations. 15 shared struct PageAllocator 16 { 17 __gshared @nogc nothrow: 18 19 private LockBusyCas _regionLock; // Only really needed for inserts 20 private LinkedList!PageRegion _regions; 21 22 private static void newRegion() 23 { 24 auto region = PageRegion("dummy"); 25 _regions.moveTail(region); 26 } 27 28 static shared(PageAllocation) allocInPages(size_t pageCount, bool allocGuardPage = true) 29 { 30 _regionLock.lock(); 31 { 32 scope(exit) _regionLock.unlock(); 33 if(_regions.length == 0) 34 newRegion(); 35 } 36 37 while(true) 38 { 39 foreach(ref region; _regions.range) 40 { 41 auto result = region.allocInPages(pageCount, allocGuardPage); 42 if(!result.isValid) 43 continue; 44 return result.value; 45 } 46 newRegion(); 47 } 48 } 49 50 static shared(PageAllocation) allocInBytesToPages(size_t minByteCount, bool allocGuardPage = true) 51 { 52 import libd.util.maths : alignTo; 53 // TEMP, fix this later when I can be arsed. 54 return allocInPages(minByteCount.alignTo!0x1000 / 0x1000, allocGuardPage); 55 } 56 57 static void free(ref shared(PageAllocation) alloc) 58 { 59 // TODO: determine which region alloc belongs to. 60 // this is safe enough for now. 61 if(_regions.length > 1) 62 assert(false); 63 _regions.getAt(0).free(alloc); 64 alloc = PageAllocation.init; 65 } 66 } 67 68 unittest 69 { 70 auto alloc = PageAllocator.allocInPages(2); 71 PageAllocator.free(alloc); 72 } 73 74 private: 75 76 version(Windows) 77 struct PageRegion 78 { 79 import runtime.system.windows; 80 81 @nogc nothrow: 82 83 ubyte[] memoryRange; 84 LockBusyCas bitKeepLock; 85 BitKeeper bitKeep; 86 uint pageSize; 87 88 this(string _) 89 { 90 SYSTEM_INFO sysInfo; 91 GetSystemInfo(&sysInfo); 92 93 const pageSize = sysInfo.dwPageSize; // Size of each page 94 const pageGran = sysInfo.dwAllocationGranularity; // Virtual memory granularity 95 const pagesPerGran = pageGran / pageSize; // How many pages fit into each granularity 96 const trackablePages = pageSize * 8; // How many pages in total we can track using a single page as the bitkeep 97 const totalPages = (trackablePages / pagesPerGran) * pagesPerGran; // How many pages we should actually get under the memory granularity. 98 99 auto ptr = cast(ubyte*)VirtualAlloc(null, totalPages * pageSize, MEM_RESERVE, PAGE_READWRITE); 100 if(!ptr) 101 onOutOfMemoryError(ptr); 102 this.memoryRange = ptr[0..totalPages * pageSize]; 103 104 if(!VirtualAlloc(ptr, pageSize, MEM_COMMIT, PAGE_READWRITE)) 105 assert(false, "Did I fuck up the params?"); 106 107 this.bitKeep = BitKeeper(memoryRange[0..pageSize], pageSize*8); 108 this.bitKeep.alloc(1); // Always keep the first page allocated 109 this.pageSize = pageSize; 110 } 111 112 SimpleResult!(shared PageAllocation) allocInPages(size_t pageCount, bool allocGuardPage) 113 { 114 PageAllocation alloc; 115 116 this.bitKeepLock.lock(); 117 { 118 scope(exit) this.bitKeepLock.unlock(); 119 auto result = this.bitKeep.alloc(pageCount + allocGuardPage); 120 if(!result.isValid) 121 return typeof(return)(result.error); 122 alloc.bitKeepSlice = result.value; 123 } 124 125 const start = (this.pageSize * alloc.bitKeepSlice.bitIndex); 126 const size = (this.pageSize * pageCount); 127 auto ptr = this.memoryRange.ptr + start; 128 if(!VirtualAlloc(ptr, size + (this.pageSize * allocGuardPage), MEM_COMMIT, PAGE_READWRITE)) 129 assert(false, "Did I fuck up the params/maths?"); 130 131 if(allocGuardPage) 132 { 133 DWORD _1; 134 if(!VirtualProtect(ptr, this.pageSize, PAGE_READONLY | PAGE_GUARD, &_1)) 135 assert(false, "Que?"); 136 alloc.memory = ptr[this.pageSize..this.pageSize+size]; 137 } 138 else 139 alloc.memory = ptr[0..size]; 140 alloc.hasGuardPage = allocGuardPage; 141 alloc.pageCount = pageCount; 142 return typeof(return)(cast(shared)alloc); 143 } 144 145 void free(shared PageAllocation alloc) 146 { 147 assert(alloc.bitKeepSlice != (shared BitKeeperSlice).init); 148 149 this.bitKeepLock.lock(); 150 { 151 scope(exit) this.bitKeepLock.unlock(); 152 this.bitKeep.free(alloc.bitKeepSlice); 153 } 154 155 if(!VirtualFree(cast(ubyte*)alloc.memory.ptr, alloc.memory.length, MEM_DECOMMIT)) 156 assert(false, "Could not free pages?"); 157 } 158 } 159 160 version(Posix) 161 struct PageRegion // Technically speaking I could DRY this, since I only need to replace bits and bobs. 162 // But I've been programming for about 14 hours and my mind is mush. 163 // TODO: 164 { 165 import runtime.system.posix; 166 167 @nogc nothrow: 168 169 ubyte[] memoryRange; 170 LockBusyCas bitKeepLock; 171 BitKeeper bitKeep; 172 uint pageSize; 173 174 this(string _) 175 { 176 const pageSize = g_posixPageSize; // Size of each page 177 const pageGran = g_posixPageSize; // Virtual memory granularity 178 const pagesPerGran = pageGran / pageSize; // How many pages fit into each granularity 179 const trackablePages = pageSize * 8; // How many pages in total we can track using a single page as the bitkeep 180 const totalPages = (trackablePages / pagesPerGran) * pagesPerGran; // How many pages we should actually get under the memory granularity. 181 182 auto ptr = cast(ubyte*)mmap(null, totalPages * pageSize); 183 if(!ptr) 184 onOutOfMemoryError(ptr); 185 this.memoryRange = ptr[0..totalPages * pageSize]; 186 187 this.bitKeep = BitKeeper(memoryRange[0..pageSize], pageSize*8); 188 this.bitKeep.alloc(1); // Always keep the first page allocated 189 this.pageSize = pageSize; 190 } 191 192 SimpleResult!(shared PageAllocation) allocInPages(size_t pageCount, bool allocGuardPage) 193 { 194 PageAllocation alloc; 195 196 this.bitKeepLock.lock(); 197 { 198 scope(exit) this.bitKeepLock.unlock(); 199 auto result = this.bitKeep.alloc(pageCount + allocGuardPage); 200 if(!result.isValid) 201 return typeof(return)(result.error); 202 alloc.bitKeepSlice = result.value; 203 } 204 205 const start = (this.pageSize * alloc.bitKeepSlice.bitIndex); 206 const size = (this.pageSize * pageCount); 207 auto ptr = this.memoryRange.ptr + start; 208 209 if(allocGuardPage) 210 { 211 // TODO: 212 // DWORD _1; 213 // if(!VirtualProtect(ptr, this.pageSize, PAGE_READONLY | PAGE_GUARD, &_1)) 214 // assert(false, "Que?"); 215 // alloc.memory = ptr[this.pageSize..this.pageSize+size]; 216 } 217 else 218 alloc.memory = ptr[0..size]; 219 alloc.hasGuardPage = allocGuardPage; 220 alloc.pageCount = pageCount; 221 return typeof(return)(cast(shared)alloc); 222 } 223 224 void free(shared PageAllocation alloc) 225 { 226 assert(alloc.bitKeepSlice != (shared BitKeeperSlice).init); 227 228 this.bitKeepLock.lock(); 229 { 230 scope(exit) this.bitKeepLock.unlock(); 231 this.bitKeep.free(alloc.bitKeepSlice); 232 } 233 } 234 }