1 /* Alloc.c -- Memory allocation functions
2 2023-04-02 : Igor Pavlov : Public domain */
3
4 #include "Precomp.h"
5
6 #ifdef _WIN32
7 #include "7zWindows.h"
8 #endif
9 #include <stdlib.h>
10
11 #include "Alloc.h"
12
13 #ifdef _WIN32
14 #ifdef Z7_LARGE_PAGES
15 #if defined(__clang__) || defined(__GNUC__)
16 typedef void (*Z7_voidFunction)(void);
17 #define MY_CAST_FUNC (Z7_voidFunction)
18 #elif defined(_MSC_VER) && _MSC_VER > 1920
19 #define MY_CAST_FUNC (void *)
20 // #pragma warning(disable : 4191) // 'type cast': unsafe conversion from 'FARPROC' to 'void (__cdecl *)()'
21 #else
22 #define MY_CAST_FUNC
23 #endif
24 #endif // Z7_LARGE_PAGES
25 #endif // _WIN32
26
27 // #define SZ_ALLOC_DEBUG
28 /* #define SZ_ALLOC_DEBUG */
29
30 /* use SZ_ALLOC_DEBUG to debug alloc/free operations */
31 #ifdef SZ_ALLOC_DEBUG
32
33 #include <string.h>
34 #include <stdio.h>
35 static int g_allocCount = 0;
36 #ifdef _WIN32
37 static int g_allocCountMid = 0;
38 static int g_allocCountBig = 0;
39 #endif
40
41
42 #define CONVERT_INT_TO_STR(charType, tempSize) \
43 char temp[tempSize]; unsigned i = 0; \
44 while (val >= 10) { temp[i++] = (char)('0' + (unsigned)(val % 10)); val /= 10; } \
45 *s++ = (charType)('0' + (unsigned)val); \
46 while (i != 0) { i--; *s++ = temp[i]; } \
47 *s = 0;
48
ConvertUInt64ToString(UInt64 val,char * s)49 static void ConvertUInt64ToString(UInt64 val, char *s)
50 {
51 CONVERT_INT_TO_STR(char, 24)
52 }
53
54 #define GET_HEX_CHAR(t) ((char)(((t < 10) ? ('0' + t) : ('A' + (t - 10)))))
55
ConvertUInt64ToHex(UInt64 val,char * s)56 static void ConvertUInt64ToHex(UInt64 val, char *s)
57 {
58 UInt64 v = val;
59 unsigned i;
60 for (i = 1;; i++)
61 {
62 v >>= 4;
63 if (v == 0)
64 break;
65 }
66 s[i] = 0;
67 do
68 {
69 unsigned t = (unsigned)(val & 0xF);
70 val >>= 4;
71 s[--i] = GET_HEX_CHAR(t);
72 }
73 while (i);
74 }
75
76 #define DEBUG_OUT_STREAM stderr
77
Print(const char * s)78 static void Print(const char *s)
79 {
80 fputs(s, DEBUG_OUT_STREAM);
81 }
82
PrintAligned(const char * s,size_t align)83 static void PrintAligned(const char *s, size_t align)
84 {
85 size_t len = strlen(s);
86 for(;;)
87 {
88 fputc(' ', DEBUG_OUT_STREAM);
89 if (len >= align)
90 break;
91 ++len;
92 }
93 Print(s);
94 }
95
PrintLn(void)96 static void PrintLn(void)
97 {
98 Print("\n");
99 }
100
PrintHex(UInt64 v,size_t align)101 static void PrintHex(UInt64 v, size_t align)
102 {
103 char s[32];
104 ConvertUInt64ToHex(v, s);
105 PrintAligned(s, align);
106 }
107
PrintDec(int v,size_t align)108 static void PrintDec(int v, size_t align)
109 {
110 char s[32];
111 ConvertUInt64ToString((unsigned)v, s);
112 PrintAligned(s, align);
113 }
114
PrintAddr(void * p)115 static void PrintAddr(void *p)
116 {
117 PrintHex((UInt64)(size_t)(ptrdiff_t)p, 12);
118 }
119
120
121 #define PRINT_REALLOC(name, cnt, size, ptr) { \
122 Print(name " "); \
123 if (!ptr) PrintDec(cnt++, 10); \
124 PrintHex(size, 10); \
125 PrintAddr(ptr); \
126 PrintLn(); }
127
128 #define PRINT_ALLOC(name, cnt, size, ptr) { \
129 Print(name " "); \
130 PrintDec(cnt++, 10); \
131 PrintHex(size, 10); \
132 PrintAddr(ptr); \
133 PrintLn(); }
134
135 #define PRINT_FREE(name, cnt, ptr) if (ptr) { \
136 Print(name " "); \
137 PrintDec(--cnt, 10); \
138 PrintAddr(ptr); \
139 PrintLn(); }
140
141 #else
142
143 #ifdef _WIN32
144 #define PRINT_ALLOC(name, cnt, size, ptr)
145 #endif
146 #define PRINT_FREE(name, cnt, ptr)
147 #define Print(s)
148 #define PrintLn()
149 #define PrintHex(v, align)
150 #define PrintAddr(p)
151
152 #endif
153
154
155 /*
156 by specification:
157 malloc(non_NULL, 0) : returns NULL or a unique pointer value that can later be successfully passed to free()
158 realloc(NULL, size) : the call is equivalent to malloc(size)
159 realloc(non_NULL, 0) : the call is equivalent to free(ptr)
160
161 in main compilers:
162 malloc(0) : returns non_NULL
163 realloc(NULL, 0) : returns non_NULL
164 realloc(non_NULL, 0) : returns NULL
165 */
166
167
MyAlloc(size_t size)168 void *MyAlloc(size_t size)
169 {
170 if (size == 0)
171 return NULL;
172 // PRINT_ALLOC("Alloc ", g_allocCount, size, NULL)
173 #ifdef SZ_ALLOC_DEBUG
174 {
175 void *p = malloc(size);
176 if (p)
177 {
178 PRINT_ALLOC("Alloc ", g_allocCount, size, p)
179 }
180 return p;
181 }
182 #else
183 return malloc(size);
184 #endif
185 }
186
MyFree(void * address)187 void MyFree(void *address)
188 {
189 PRINT_FREE("Free ", g_allocCount, address)
190
191 free(address);
192 }
193
MyRealloc(void * address,size_t size)194 void *MyRealloc(void *address, size_t size)
195 {
196 if (size == 0)
197 {
198 MyFree(address);
199 return NULL;
200 }
201 // PRINT_REALLOC("Realloc ", g_allocCount, size, address)
202 #ifdef SZ_ALLOC_DEBUG
203 {
204 void *p = realloc(address, size);
205 if (p)
206 {
207 PRINT_REALLOC("Realloc ", g_allocCount, size, address)
208 }
209 return p;
210 }
211 #else
212 return realloc(address, size);
213 #endif
214 }
215
216
217 #ifdef _WIN32
218
MidAlloc(size_t size)219 void *MidAlloc(size_t size)
220 {
221 if (size == 0)
222 return NULL;
223 #ifdef SZ_ALLOC_DEBUG
224 {
225 void *p = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
226 if (p)
227 {
228 PRINT_ALLOC("Alloc-Mid", g_allocCountMid, size, p)
229 }
230 return p;
231 }
232 #else
233 return VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
234 #endif
235 }
236
MidFree(void * address)237 void MidFree(void *address)
238 {
239 PRINT_FREE("Free-Mid", g_allocCountMid, address)
240
241 if (!address)
242 return;
243 VirtualFree(address, 0, MEM_RELEASE);
244 }
245
246 #ifdef Z7_LARGE_PAGES
247
248 #ifdef MEM_LARGE_PAGES
249 #define MY__MEM_LARGE_PAGES MEM_LARGE_PAGES
250 #else
251 #define MY__MEM_LARGE_PAGES 0x20000000
252 #endif
253
254 extern
255 SIZE_T g_LargePageSize;
256 SIZE_T g_LargePageSize = 0;
257 typedef SIZE_T (WINAPI *Func_GetLargePageMinimum)(VOID);
258
SetLargePageSize(void)259 void SetLargePageSize(void)
260 {
261 #ifdef Z7_LARGE_PAGES
262 SIZE_T size;
263 const
264 Func_GetLargePageMinimum fn =
265 (Func_GetLargePageMinimum) MY_CAST_FUNC GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")),
266 "GetLargePageMinimum");
267 if (!fn)
268 return;
269 size = fn();
270 if (size == 0 || (size & (size - 1)) != 0)
271 return;
272 g_LargePageSize = size;
273 #endif
274 }
275
276 #endif // Z7_LARGE_PAGES
277
BigAlloc(size_t size)278 void *BigAlloc(size_t size)
279 {
280 if (size == 0)
281 return NULL;
282
283 PRINT_ALLOC("Alloc-Big", g_allocCountBig, size, NULL)
284
285 #ifdef Z7_LARGE_PAGES
286 {
287 SIZE_T ps = g_LargePageSize;
288 if (ps != 0 && ps <= (1 << 30) && size > (ps / 2))
289 {
290 size_t size2;
291 ps--;
292 size2 = (size + ps) & ~ps;
293 if (size2 >= size)
294 {
295 void *p = VirtualAlloc(NULL, size2, MEM_COMMIT | MY__MEM_LARGE_PAGES, PAGE_READWRITE);
296 if (p)
297 {
298 PRINT_ALLOC("Alloc-BM ", g_allocCountMid, size2, p)
299 return p;
300 }
301 }
302 }
303 }
304 #endif
305
306 return MidAlloc(size);
307 }
308
BigFree(void * address)309 void BigFree(void *address)
310 {
311 PRINT_FREE("Free-Big", g_allocCountBig, address)
312 MidFree(address);
313 }
314
315 #endif // _WIN32
316
317
SzAlloc(ISzAllocPtr p,size_t size)318 static void *SzAlloc(ISzAllocPtr p, size_t size) { UNUSED_VAR(p) return MyAlloc(size); }
SzFree(ISzAllocPtr p,void * address)319 static void SzFree(ISzAllocPtr p, void *address) { UNUSED_VAR(p) MyFree(address); }
320 const ISzAlloc g_Alloc = { SzAlloc, SzFree };
321
322 #ifdef _WIN32
SzMidAlloc(ISzAllocPtr p,size_t size)323 static void *SzMidAlloc(ISzAllocPtr p, size_t size) { UNUSED_VAR(p) return MidAlloc(size); }
SzMidFree(ISzAllocPtr p,void * address)324 static void SzMidFree(ISzAllocPtr p, void *address) { UNUSED_VAR(p) MidFree(address); }
SzBigAlloc(ISzAllocPtr p,size_t size)325 static void *SzBigAlloc(ISzAllocPtr p, size_t size) { UNUSED_VAR(p) return BigAlloc(size); }
SzBigFree(ISzAllocPtr p,void * address)326 static void SzBigFree(ISzAllocPtr p, void *address) { UNUSED_VAR(p) BigFree(address); }
327 const ISzAlloc g_MidAlloc = { SzMidAlloc, SzMidFree };
328 const ISzAlloc g_BigAlloc = { SzBigAlloc, SzBigFree };
329 #endif
330
331 /*
332 uintptr_t : <stdint.h> C99 (optional)
333 : unsupported in VS6
334 */
335
336 #ifdef _WIN32
337 typedef UINT_PTR UIntPtr;
338 #else
339 /*
340 typedef uintptr_t UIntPtr;
341 */
342 typedef ptrdiff_t UIntPtr;
343 #endif
344
345
346 #define ADJUST_ALLOC_SIZE 0
347 /*
348 #define ADJUST_ALLOC_SIZE (sizeof(void *) - 1)
349 */
350 /*
351 Use (ADJUST_ALLOC_SIZE = (sizeof(void *) - 1)), if
352 MyAlloc() can return address that is NOT multiple of sizeof(void *).
353 */
354
355
356 /*
357 #define MY_ALIGN_PTR_DOWN(p, align) ((void *)((char *)(p) - ((size_t)(UIntPtr)(p) & ((align) - 1))))
358 */
359 #define MY_ALIGN_PTR_DOWN(p, align) ((void *)((((UIntPtr)(p)) & ~((UIntPtr)(align) - 1))))
360
361
362 #if !defined(_WIN32) && defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L)
363 #define USE_posix_memalign
364 #endif
365
366 #ifndef USE_posix_memalign
367 #define MY_ALIGN_PTR_UP_PLUS(p, align) MY_ALIGN_PTR_DOWN(((char *)(p) + (align) + ADJUST_ALLOC_SIZE), align)
368 #endif
369
370 /*
371 This posix_memalign() is for test purposes only.
372 We also need special Free() function instead of free(),
373 if this posix_memalign() is used.
374 */
375
376 /*
377 static int posix_memalign(void **ptr, size_t align, size_t size)
378 {
379 size_t newSize = size + align;
380 void *p;
381 void *pAligned;
382 *ptr = NULL;
383 if (newSize < size)
384 return 12; // ENOMEM
385 p = MyAlloc(newSize);
386 if (!p)
387 return 12; // ENOMEM
388 pAligned = MY_ALIGN_PTR_UP_PLUS(p, align);
389 ((void **)pAligned)[-1] = p;
390 *ptr = pAligned;
391 return 0;
392 }
393 */
394
395 /*
396 ALLOC_ALIGN_SIZE >= sizeof(void *)
397 ALLOC_ALIGN_SIZE >= cache_line_size
398 */
399
400 #define ALLOC_ALIGN_SIZE ((size_t)1 << 7)
401
SzAlignedAlloc(ISzAllocPtr pp,size_t size)402 static void *SzAlignedAlloc(ISzAllocPtr pp, size_t size)
403 {
404 #ifndef USE_posix_memalign
405
406 void *p;
407 void *pAligned;
408 size_t newSize;
409 UNUSED_VAR(pp)
410
411 /* also we can allocate additional dummy ALLOC_ALIGN_SIZE bytes after aligned
412 block to prevent cache line sharing with another allocated blocks */
413
414 newSize = size + ALLOC_ALIGN_SIZE * 1 + ADJUST_ALLOC_SIZE;
415 if (newSize < size)
416 return NULL;
417
418 p = MyAlloc(newSize);
419
420 if (!p)
421 return NULL;
422 pAligned = MY_ALIGN_PTR_UP_PLUS(p, ALLOC_ALIGN_SIZE);
423
424 Print(" size="); PrintHex(size, 8);
425 Print(" a_size="); PrintHex(newSize, 8);
426 Print(" ptr="); PrintAddr(p);
427 Print(" a_ptr="); PrintAddr(pAligned);
428 PrintLn();
429
430 ((void **)pAligned)[-1] = p;
431
432 return pAligned;
433
434 #else
435
436 void *p;
437 UNUSED_VAR(pp)
438 if (posix_memalign(&p, ALLOC_ALIGN_SIZE, size))
439 return NULL;
440
441 Print(" posix_memalign="); PrintAddr(p);
442 PrintLn();
443
444 return p;
445
446 #endif
447 }
448
449
SzAlignedFree(ISzAllocPtr pp,void * address)450 static void SzAlignedFree(ISzAllocPtr pp, void *address)
451 {
452 UNUSED_VAR(pp)
453 #ifndef USE_posix_memalign
454 if (address)
455 MyFree(((void **)address)[-1]);
456 #else
457 free(address);
458 #endif
459 }
460
461
462 const ISzAlloc g_AlignedAlloc = { SzAlignedAlloc, SzAlignedFree };
463
464
465
466 #define MY_ALIGN_PTR_DOWN_1(p) MY_ALIGN_PTR_DOWN(p, sizeof(void *))
467
468 /* we align ptr to support cases where CAlignOffsetAlloc::offset is not multiply of sizeof(void *) */
469 #define REAL_BLOCK_PTR_VAR(p) ((void **)MY_ALIGN_PTR_DOWN_1(p))[-1]
470 /*
471 #define REAL_BLOCK_PTR_VAR(p) ((void **)(p))[-1]
472 */
473
AlignOffsetAlloc_Alloc(ISzAllocPtr pp,size_t size)474 static void *AlignOffsetAlloc_Alloc(ISzAllocPtr pp, size_t size)
475 {
476 const CAlignOffsetAlloc *p = Z7_CONTAINER_FROM_VTBL_CONST(pp, CAlignOffsetAlloc, vt);
477 void *adr;
478 void *pAligned;
479 size_t newSize;
480 size_t extra;
481 size_t alignSize = (size_t)1 << p->numAlignBits;
482
483 if (alignSize < sizeof(void *))
484 alignSize = sizeof(void *);
485
486 if (p->offset >= alignSize)
487 return NULL;
488
489 /* also we can allocate additional dummy ALLOC_ALIGN_SIZE bytes after aligned
490 block to prevent cache line sharing with another allocated blocks */
491 extra = p->offset & (sizeof(void *) - 1);
492 newSize = size + alignSize + extra + ADJUST_ALLOC_SIZE;
493 if (newSize < size)
494 return NULL;
495
496 adr = ISzAlloc_Alloc(p->baseAlloc, newSize);
497
498 if (!adr)
499 return NULL;
500
501 pAligned = (char *)MY_ALIGN_PTR_DOWN((char *)adr +
502 alignSize - p->offset + extra + ADJUST_ALLOC_SIZE, alignSize) + p->offset;
503
504 PrintLn();
505 Print("- Aligned: ");
506 Print(" size="); PrintHex(size, 8);
507 Print(" a_size="); PrintHex(newSize, 8);
508 Print(" ptr="); PrintAddr(adr);
509 Print(" a_ptr="); PrintAddr(pAligned);
510 PrintLn();
511
512 REAL_BLOCK_PTR_VAR(pAligned) = adr;
513
514 return pAligned;
515 }
516
517
AlignOffsetAlloc_Free(ISzAllocPtr pp,void * address)518 static void AlignOffsetAlloc_Free(ISzAllocPtr pp, void *address)
519 {
520 if (address)
521 {
522 const CAlignOffsetAlloc *p = Z7_CONTAINER_FROM_VTBL_CONST(pp, CAlignOffsetAlloc, vt);
523 PrintLn();
524 Print("- Aligned Free: ");
525 PrintLn();
526 ISzAlloc_Free(p->baseAlloc, REAL_BLOCK_PTR_VAR(address));
527 }
528 }
529
530
AlignOffsetAlloc_CreateVTable(CAlignOffsetAlloc * p)531 void AlignOffsetAlloc_CreateVTable(CAlignOffsetAlloc *p)
532 {
533 p->vt.Alloc = AlignOffsetAlloc_Alloc;
534 p->vt.Free = AlignOffsetAlloc_Free;
535 }
536