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 }