1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5#include "jitpch.h"
6
7#if defined(_MSC_VER)
8#pragma hdrstop
9#endif // defined(_MSC_VER)
10
11//------------------------------------------------------------------------
12// ArenaAllocator::bypassHostAllocator:
13// Indicates whether or not the ArenaAllocator should bypass the JIT
14// host when allocating memory for arena pages.
15//
16// Return Value:
17// True if the JIT should bypass the JIT host; false otherwise.
18bool ArenaAllocator::bypassHostAllocator()
19{
20#if defined(DEBUG)
21 // When JitDirectAlloc is set, all JIT allocations requests are forwarded
22 // directly to the OS. This allows taking advantage of pageheap and other gflag
23 // knobs for ensuring that we do not have buffer overruns in the JIT.
24
25 return JitConfig.JitDirectAlloc() != 0;
26#else // defined(DEBUG)
27 return false;
28#endif // !defined(DEBUG)
29}
30
31//------------------------------------------------------------------------
32// ArenaAllocator::getDefaultPageSize:
33// Returns the default size of an arena page.
34//
35// Return Value:
36// The default size of an arena page.
37size_t ArenaAllocator::getDefaultPageSize()
38{
39 return DEFAULT_PAGE_SIZE;
40}
41
42//------------------------------------------------------------------------
43// ArenaAllocator::ArenaAllocator:
44// Default-constructs an arena allocator.
45ArenaAllocator::ArenaAllocator()
46 : m_firstPage(nullptr), m_lastPage(nullptr), m_nextFreeByte(nullptr), m_lastFreeByte(nullptr)
47{
48#if MEASURE_MEM_ALLOC
49 memset(&m_stats, 0, sizeof(m_stats));
50 memset(&m_statsAllocators, 0, sizeof(m_statsAllocators));
51#endif // MEASURE_MEM_ALLOC
52}
53
54//------------------------------------------------------------------------
55// ArenaAllocator::allocateNewPage:
56// Allocates a new arena page.
57//
58// Arguments:
59// size - The number of bytes that were requested by the allocation
60// that triggered this request to allocate a new arena page.
61//
62// Return Value:
63// A pointer to the first usable byte of the newly allocated page.
64void* ArenaAllocator::allocateNewPage(size_t size)
65{
66 size_t pageSize = sizeof(PageDescriptor) + size;
67
68 // Check for integer overflow
69 if (pageSize < size)
70 {
71 NOMEM();
72 }
73
74 // If the current page is now full, update a few statistics
75 if (m_lastPage != nullptr)
76 {
77 // Undo the "+=" done in allocateMemory()
78 m_nextFreeByte -= size;
79
80 // Save the actual used size of the page
81 m_lastPage->m_usedBytes = m_nextFreeByte - m_lastPage->m_contents;
82 }
83
84 PageDescriptor* newPage = nullptr;
85
86 if (!bypassHostAllocator())
87 {
88 // Round to the nearest multiple of default page size
89 pageSize = roundUp(pageSize, DEFAULT_PAGE_SIZE);
90 }
91
92 if (newPage == nullptr)
93 {
94 // Allocate the new page
95 newPage = static_cast<PageDescriptor*>(allocateHostMemory(pageSize, &pageSize));
96
97 if (newPage == nullptr)
98 {
99 NOMEM();
100 }
101 }
102
103 // Append the new page to the end of the list
104 newPage->m_next = nullptr;
105 newPage->m_pageBytes = pageSize;
106 newPage->m_usedBytes = 0; // m_usedBytes is meaningless until a new page is allocated.
107 // Instead of letting it contain garbage (so to confuse us),
108 // set it to zero.
109
110 if (m_lastPage != nullptr)
111 {
112 m_lastPage->m_next = newPage;
113 }
114 else
115 {
116 m_firstPage = newPage;
117 }
118
119 m_lastPage = newPage;
120
121 // Adjust the next/last free byte pointers
122 m_nextFreeByte = newPage->m_contents + size;
123 m_lastFreeByte = (BYTE*)newPage + pageSize;
124 assert((m_lastFreeByte - m_nextFreeByte) >= 0);
125
126 return newPage->m_contents;
127}
128
129//------------------------------------------------------------------------
130// ArenaAllocator::destroy:
131// Performs any necessary teardown for an `ArenaAllocator`.
132void ArenaAllocator::destroy()
133{
134 PageDescriptor* page = m_firstPage;
135
136 // Free all of the allocated pages
137 for (PageDescriptor* next; page != nullptr; page = next)
138 {
139 next = page->m_next;
140 freeHostMemory(page, page->m_pageBytes);
141 }
142
143 // Clear out the allocator's fields
144 m_firstPage = nullptr;
145 m_lastPage = nullptr;
146 m_nextFreeByte = nullptr;
147 m_lastFreeByte = nullptr;
148}
149
150// The debug version of the allocator may allocate directly from the
151// OS rather than going through the hosting APIs. In order to do so,
152// it must undef the macros that are usually in place to prevent
153// accidental uses of the OS allocator.
154#if defined(DEBUG)
155#undef GetProcessHeap
156#undef HeapAlloc
157#undef HeapFree
158#endif
159
160//------------------------------------------------------------------------
161// ArenaAllocator::allocateHostMemory:
162// Allocates memory from the host (or the OS if `bypassHostAllocator()`
163// returns `true`).
164//
165// Arguments:
166// size - The number of bytes to allocate.
167// pActualSize - The number of byte actually allocated.
168//
169// Return Value:
170// A pointer to the allocated memory.
171void* ArenaAllocator::allocateHostMemory(size_t size, size_t* pActualSize)
172{
173#if defined(DEBUG)
174 if (bypassHostAllocator())
175 {
176 *pActualSize = size;
177 return ::HeapAlloc(GetProcessHeap(), 0, size);
178 }
179#endif // !defined(DEBUG)
180
181 return g_jitHost->allocateSlab(size, pActualSize);
182}
183
184//------------------------------------------------------------------------
185// ArenaAllocator::freeHostMemory:
186// Frees memory allocated by a previous call to `allocateHostMemory`.
187//
188// Arguments:
189// block - A pointer to the memory to free.
190void ArenaAllocator::freeHostMemory(void* block, size_t size)
191{
192#if defined(DEBUG)
193 if (bypassHostAllocator())
194 {
195 ::HeapFree(GetProcessHeap(), 0, block);
196 return;
197 }
198#endif // !defined(DEBUG)
199
200 g_jitHost->freeSlab(block, size);
201}
202
203//------------------------------------------------------------------------
204// ArenaAllocator::getTotalBytesAllocated:
205// Gets the total number of bytes allocated for all of the arena pages
206// for an `ArenaAllocator`.
207//
208// Return Value:
209// See above.
210size_t ArenaAllocator::getTotalBytesAllocated()
211{
212 size_t bytes = 0;
213 for (PageDescriptor* page = m_firstPage; page != nullptr; page = page->m_next)
214 {
215 bytes += page->m_pageBytes;
216 }
217
218 return bytes;
219}
220
221//------------------------------------------------------------------------
222// ArenaAllocator::getTotalBytesAllocated:
223// Gets the total number of bytes used in all of the arena pages for
224// an `ArenaAllocator`.
225//
226// Return Value:
227// See above.
228//
229// Notes:
230// An arena page may have unused space at the very end. This happens
231// when an allocation request comes in (via a call to `allocateMemory`)
232// that will not fit in the remaining bytes for the current page.
233// Another way to understand this method is as returning the total
234// number of bytes allocated for arena pages minus the number of bytes
235// that are unused across all area pages.
236size_t ArenaAllocator::getTotalBytesUsed()
237{
238 if (m_lastPage != nullptr)
239 {
240 m_lastPage->m_usedBytes = m_nextFreeByte - m_lastPage->m_contents;
241 }
242
243 size_t bytes = 0;
244 for (PageDescriptor* page = m_firstPage; page != nullptr; page = page->m_next)
245 {
246 bytes += page->m_usedBytes;
247 }
248
249 return bytes;
250}
251
252#if MEASURE_MEM_ALLOC
253CritSecObject ArenaAllocator::s_statsLock;
254ArenaAllocator::AggregateMemStats ArenaAllocator::s_aggStats;
255ArenaAllocator::MemStats ArenaAllocator::s_maxStats;
256
257const char* ArenaAllocator::MemStats::s_CompMemKindNames[] = {
258#define CompMemKindMacro(kind) #kind,
259#include "compmemkind.h"
260};
261
262void ArenaAllocator::MemStats::Print(FILE* f)
263{
264 fprintf(f, "count: %10u, size: %10llu, max = %10llu\n", allocCnt, allocSz, allocSzMax);
265 fprintf(f, "allocateMemory: %10llu, nraUsed: %10llu\n", nraTotalSizeAlloc, nraTotalSizeUsed);
266 PrintByKind(f);
267}
268
269void ArenaAllocator::MemStats::PrintByKind(FILE* f)
270{
271 fprintf(f, "\nAlloc'd bytes by kind:\n %20s | %10s | %7s\n", "kind", "size", "pct");
272 fprintf(f, " %20s-+-%10s-+-%7s\n", "--------------------", "----------", "-------");
273 float allocSzF = static_cast<float>(allocSz);
274 for (int cmk = 0; cmk < CMK_Count; cmk++)
275 {
276 float pct = 100.0f * static_cast<float>(allocSzByKind[cmk]) / allocSzF;
277 fprintf(f, " %20s | %10llu | %6.2f%%\n", s_CompMemKindNames[cmk], allocSzByKind[cmk], pct);
278 }
279 fprintf(f, "\n");
280}
281
282void ArenaAllocator::AggregateMemStats::Print(FILE* f)
283{
284 fprintf(f, "For %9u methods:\n", nMethods);
285 if (nMethods == 0)
286 {
287 return;
288 }
289 fprintf(f, " count: %12u (avg %7u per method)\n", allocCnt, allocCnt / nMethods);
290 fprintf(f, " alloc size : %12llu (avg %7llu per method)\n", allocSz, allocSz / nMethods);
291 fprintf(f, " max alloc : %12llu\n", allocSzMax);
292 fprintf(f, "\n");
293 fprintf(f, " allocateMemory : %12llu (avg %7llu per method)\n", nraTotalSizeAlloc, nraTotalSizeAlloc / nMethods);
294 fprintf(f, " nraUsed : %12llu (avg %7llu per method)\n", nraTotalSizeUsed, nraTotalSizeUsed / nMethods);
295 PrintByKind(f);
296}
297
298ArenaAllocator::MemStatsAllocator* ArenaAllocator::getMemStatsAllocator(CompMemKind kind)
299{
300 assert(kind < CMK_Count);
301
302 if (m_statsAllocators[kind].m_arena == nullptr)
303 {
304 m_statsAllocators[kind].m_arena = this;
305 m_statsAllocators[kind].m_kind = kind;
306 }
307
308 return &m_statsAllocators[kind];
309}
310
311void ArenaAllocator::finishMemStats()
312{
313 m_stats.nraTotalSizeAlloc = getTotalBytesAllocated();
314 m_stats.nraTotalSizeUsed = getTotalBytesUsed();
315
316 CritSecHolder statsLock(s_statsLock);
317 s_aggStats.Add(m_stats);
318 if (m_stats.allocSz > s_maxStats.allocSz)
319 {
320 s_maxStats = m_stats;
321 }
322}
323
324void ArenaAllocator::dumpMemStats(FILE* file)
325{
326 m_stats.Print(file);
327}
328
329void ArenaAllocator::dumpAggregateMemStats(FILE* file)
330{
331 s_aggStats.Print(file);
332}
333
334void ArenaAllocator::dumpMaxMemStats(FILE* file)
335{
336 s_maxStats.Print(file);
337}
338#endif // MEASURE_MEM_ALLOC
339