v2 / vlib / builtin / allocation.c.v
582 lines · 561 sloc · 16.58 KB · a7912e4258c4c5888503bda40e924a89ac7c21c3
Raw
1@[has_globals]
2module builtin
3
4// v_memory_panic will be true, *only* when a call to malloc/realloc/vcalloc etc could not succeed.
5// In that situation, functions that are registered with at_exit(), should be able to limit their
6// activity accordingly, by checking this flag.
7// The V compiler itself for example registers a function with at_exit(), for showing timers.
8// Without a way to distinguish, that we are in a memory panic, that would just display a second panic,
9// which would be less clear to the user.
10__global v_memory_panic = false
11
12@[noreturn]
13fn _memory_panic(fname string, size isize) {
14 v_memory_panic = true
15 // Note: do not use string interpolation here at all, since string interpolation itself allocates
16 eprint(fname)
17 eprint('(')
18 $if freestanding || vinix {
19 eprint('size') // TODO: use something more informative here
20 } $else {
21 C.fprintf(C.stderr, c'%p', voidptr(size))
22 }
23 if size < 0 {
24 eprint(' < 0')
25 }
26 eprintln(')')
27 panic('memory allocation failure')
28}
29
30__global total_m = i64(0)
31// malloc dynamically allocates a `n` bytes block of memory on the heap.
32// malloc returns a `byteptr` pointing to the memory address of the allocated space.
33// unlike the `calloc` family of functions - malloc will not zero the memory block.
34@[unsafe]
35pub fn malloc(n isize) &u8 {
36 $if trace_malloc ? {
37 total_m += n
38 C.fprintf(C.stderr, c'_v_malloc %6d total %10d\n', n, total_m)
39 // print_backtrace()
40 }
41 if n < 0 {
42 _memory_panic(@FN, n)
43 } else if n == 0 {
44 return &u8(unsafe { nil })
45 }
46 mut res := &u8(unsafe { nil })
47 $if prealloc {
48 return unsafe { prealloc_malloc(n) }
49 } $else $if vgc ? {
50 unsafe {
51 res = &u8(vgc_malloc(usize(n)))
52 }
53 } $else $if gcboehm ? {
54 unsafe {
55 res = C.GC_MALLOC(n)
56 }
57 } $else $if freestanding {
58 // todo: is this safe to call malloc there? We export __malloc as malloc and it uses dlmalloc behind the scenes
59 // so theoretically it is safe
60 res = unsafe { __malloc(usize(n)) }
61 } $else {
62 $if windows {
63 // Warning! On windows, we always use _aligned_malloc to allocate memory.
64 // This ensures that we can later free the memory with _aligned_free
65 // without needing to track whether the memory was originally allocated
66 // by malloc or _aligned_malloc.
67 res = unsafe { C._aligned_malloc(n, 1) }
68 } $else {
69 res = unsafe { C.malloc(n) }
70 }
71 }
72 if res == 0 {
73 _memory_panic(@FN, n)
74 }
75 $if debug_malloc ? {
76 // Fill in the memory with something != 0 i.e. `M`, so it is easier to spot
77 // when the calling code wrongly relies on it being zeroed.
78 unsafe { C.memset(res, 0x4D, n) }
79 }
80 return res
81}
82
83@[unsafe]
84pub fn malloc_noscan(n isize) &u8 {
85 $if trace_malloc ? {
86 total_m += n
87 C.fprintf(C.stderr, c'malloc_noscan %6d total %10d\n', n, total_m)
88 // print_backtrace()
89 }
90 if n < 0 {
91 _memory_panic(@FN, n)
92 }
93 mut res := &u8(unsafe { nil })
94 $if prealloc {
95 return unsafe { prealloc_malloc(n) }
96 } $else $if vgc ? {
97 unsafe {
98 res = &u8(vgc_malloc_noscan(usize(n)))
99 }
100 } $else $if gcboehm ? {
101 $if gcboehm_opt ? {
102 unsafe {
103 res = C.GC_MALLOC_ATOMIC(n)
104 }
105 } $else {
106 unsafe {
107 res = C.GC_MALLOC(n)
108 }
109 }
110 } $else $if freestanding {
111 res = unsafe { __malloc(usize(n)) }
112 } $else {
113 $if windows {
114 // Warning! On windows, we always use _aligned_malloc to allocate memory.
115 // This ensures that we can later free the memory with _aligned_free
116 // without needing to track whether the memory was originally allocated
117 // by malloc or _aligned_malloc.
118 res = unsafe { C._aligned_malloc(n, 1) }
119 } $else {
120 res = unsafe { C.malloc(n) }
121 }
122 }
123 if res == 0 {
124 _memory_panic(@FN, n)
125 }
126 $if debug_malloc ? {
127 // Fill in the memory with something != 0 i.e. `M`, so it is easier to spot
128 // when the calling code wrongly relies on it being zeroed.
129 unsafe { C.memset(res, 0x4D, n) }
130 }
131 return res
132}
133
134@[unsafe]
135fn malloc_uninit(n isize) &u8 {
136 if n < 0 {
137 _memory_panic(@FN, n)
138 } else if n == 0 {
139 return &u8(unsafe { nil })
140 }
141 $if vgc ? {
142 return unsafe { &u8(vgc_malloc_typed_opts(usize(n), 0, 0, false)) }
143 }
144 return malloc(n)
145}
146
147@[unsafe]
148fn malloc_noscan_uninit(n isize) &u8 {
149 if n < 0 {
150 _memory_panic(@FN, n)
151 } else if n == 0 {
152 return &u8(unsafe { nil })
153 }
154 $if vgc ? {
155 return unsafe { &u8(vgc_malloc_noscan_opts(usize(n), false)) }
156 }
157 return malloc_noscan(n)
158}
159
160@[inline]
161fn __at_least_one(how_many u64) u64 {
162 // handle the case for allocating memory for empty structs, which have sizeof(EmptyStruct) == 0
163 // in this case, just allocate a single byte, avoiding the panic for malloc(0)
164 if how_many == 0 {
165 return 1
166 }
167 return how_many
168}
169
170// malloc_uncollectable dynamically allocates a `n` bytes block of memory
171// on the heap, which will NOT be garbage-collected (but its contents will).
172@[unsafe]
173pub fn malloc_uncollectable(n isize) &u8 {
174 $if trace_malloc ? {
175 total_m += n
176 C.fprintf(C.stderr, c'malloc_uncollectable %6d total %10d\n', n, total_m)
177 // print_backtrace()
178 }
179 if n < 0 {
180 _memory_panic(@FN, n)
181 }
182
183 mut res := &u8(unsafe { nil })
184 $if prealloc {
185 return unsafe { prealloc_malloc(n) }
186 } $else $if vgc ? {
187 unsafe {
188 res = &u8(vgc_malloc(usize(n)))
189 }
190 } $else $if gcboehm ? {
191 unsafe {
192 res = C.GC_MALLOC_UNCOLLECTABLE(n)
193 }
194 } $else $if freestanding {
195 res = unsafe { __malloc(usize(n)) }
196 } $else {
197 $if windows {
198 // Warning! On windows, we always use _aligned_malloc to allocate memory.
199 // This ensures that we can later free the memory with _aligned_free
200 // without needing to track whether the memory was originally allocated
201 // by malloc or _aligned_malloc.
202 res = unsafe { C._aligned_malloc(n, 1) }
203 } $else {
204 res = unsafe { C.malloc(n) }
205 }
206 }
207 if res == 0 {
208 _memory_panic(@FN, n)
209 }
210 $if debug_malloc ? {
211 // Fill in the memory with something != 0 i.e. `M`, so it is easier to spot
212 // when the calling code wrongly relies on it being zeroed.
213 unsafe { C.memset(res, 0x4D, n) }
214 }
215 return res
216}
217
218// v_realloc resizes the memory block `b` with `n` bytes.
219// The `b byteptr` must be a pointer to an existing memory block
220// previously allocated with `malloc` or `vcalloc`.
221// Please, see also realloc_data, and use it instead if possible.
222@[unsafe]
223pub fn v_realloc(b &u8, n isize) &u8 {
224 $if trace_realloc ? {
225 C.fprintf(C.stderr, c'v_realloc %6d\n', n)
226 }
227 mut new_ptr := &u8(unsafe { nil })
228 $if prealloc {
229 unsafe {
230 new_ptr = malloc(n)
231 C.memcpy(new_ptr, b, n)
232 }
233 return new_ptr
234 } $else $if vgc ? {
235 new_ptr = unsafe { &u8(vgc_realloc(b, usize(n))) }
236 } $else $if gcboehm ? {
237 new_ptr = unsafe { C.GC_REALLOC(b, n) }
238 } $else {
239 $if windows {
240 // Warning! On windows, we always use _aligned_realloc to reallocate memory.
241 // This ensures that we can later free the memory with _aligned_free
242 // without needing to track whether the memory was originally allocated
243 // by malloc or _aligned_malloc/_aligned_realloc.
244 new_ptr = unsafe { C._aligned_realloc(b, n, 1) }
245 } $else {
246 new_ptr = unsafe { C.realloc(b, n) }
247 }
248 }
249 if new_ptr == 0 {
250 _memory_panic(@FN, n)
251 }
252 return new_ptr
253}
254
255// realloc_data resizes the memory block pointed by `old_data` to `new_size`
256// bytes. `old_data` must be a pointer to an existing memory block, previously
257// allocated with `malloc` or `vcalloc`, of size `old_data`.
258// realloc_data returns a pointer to the new location of the block.
259// Note: if you know the old data size, it is preferable to call `realloc_data`,
260// instead of `v_realloc`, at least during development, because `realloc_data`
261// can make debugging easier, when you compile your program with
262// `-d debug_realloc`.
263@[unsafe]
264pub fn realloc_data(old_data &u8, old_size int, new_size int) &u8 {
265 $if trace_realloc ? {
266 C.fprintf(C.stderr, c'realloc_data old_size: %6d new_size: %6d\n', old_size, new_size)
267 }
268 $if prealloc {
269 return unsafe { prealloc_realloc(old_data, old_size, new_size) }
270 }
271 $if debug_realloc ? {
272 // Note: this is slower, but helps debugging memory problems.
273 // The main idea is to always force reallocating:
274 // 1) allocate a new memory block
275 // 2) copy the old to the new
276 // 3) fill the old with 0x57 (`W`)
277 // 4) free the old block
278 // => if there is still a pointer to the old block somewhere
279 // it will point to memory that is now filled with 0x57.
280 unsafe {
281 new_ptr := malloc(new_size)
282 min_size := if old_size < new_size { old_size } else { new_size }
283 C.memcpy(new_ptr, old_data, min_size)
284 C.memset(old_data, 0x57, old_size)
285 free(old_data)
286 return new_ptr
287 }
288 }
289 mut nptr := &u8(unsafe { nil })
290 $if vgc ? {
291 nptr = unsafe { &u8(vgc_realloc(old_data, usize(new_size))) }
292 } $else $if gcboehm ? {
293 nptr = unsafe { C.GC_REALLOC(old_data, new_size) }
294 } $else {
295 $if windows {
296 // Warning! On windows, we always use _aligned_realloc to reallocate memory.
297 // This ensures that we can later free the memory with _aligned_free
298 // without needing to track whether the memory was originally allocated
299 // by malloc or _aligned_malloc/_aligned_realloc.
300 nptr = unsafe { C._aligned_realloc(old_data, new_size, 1) }
301 } $else {
302 nptr = unsafe { C.realloc(old_data, new_size) }
303 }
304 }
305 if nptr == 0 {
306 _memory_panic(@FN, isize(new_size))
307 }
308 return nptr
309}
310
311// vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap.
312// vcalloc returns a `byteptr` pointing to the memory address of the allocated space.
313// vcalloc checks for negative values given in `n`.
314pub fn vcalloc(n isize) &u8 {
315 $if trace_vcalloc ? {
316 total_m += n
317 C.fprintf(C.stderr, c'vcalloc %6d total %10d\n', n, total_m)
318 }
319 if n < 0 {
320 _memory_panic(@FN, n)
321 } else if n == 0 {
322 return &u8(unsafe { nil })
323 }
324 $if prealloc {
325 return unsafe { prealloc_calloc(n) }
326 } $else $if vgc ? {
327 return unsafe { &u8(vgc_calloc(usize(n))) }
328 } $else $if gcboehm ? {
329 return unsafe { &u8(C.GC_MALLOC(n)) }
330 } $else {
331 $if windows {
332 // Warning! On windows, we always use _aligned_malloc to allocate memory.
333 // This ensures that we can later free the memory with _aligned_free
334 // without needing to track whether the memory was originally allocated
335 // by malloc or _aligned_malloc/_aligned_realloc/_aligned_recalloc.
336 ptr := unsafe { C._aligned_malloc(n, 1) }
337 if ptr != &u8(unsafe { nil }) {
338 unsafe { C.memset(ptr, 0, n) }
339 }
340 return ptr
341 } $else {
342 return unsafe { C.calloc(1, n) }
343 }
344 }
345 return &u8(unsafe { nil }) // not reached, TODO: remove when V's checker is improved
346}
347
348// special versions of the above that allocate memory which is not scanned
349// for pointers (but is collected) when the Boehm garbage collection is used
350pub fn vcalloc_noscan(n isize) &u8 {
351 $if trace_vcalloc ? {
352 total_m += n
353 C.fprintf(C.stderr, c'vcalloc_noscan %6d total %10d\n', n, total_m)
354 }
355 $if prealloc {
356 return unsafe { prealloc_calloc(n) }
357 } $else $if vgc ? {
358 if n < 0 {
359 _memory_panic(@FN, n)
360 }
361 return unsafe { &u8(vgc_calloc(usize(n))) }
362 } $else $if gcboehm ? {
363 if n < 0 {
364 _memory_panic(@FN, n)
365 }
366 $if gcboehm_opt ? {
367 res := unsafe { C.GC_MALLOC_ATOMIC(n) }
368 unsafe { C.memset(res, 0, n) }
369 return &u8(res)
370 } $else {
371 res := unsafe { C.GC_MALLOC(n) }
372 return &u8(res)
373 }
374 } $else {
375 return unsafe { vcalloc(n) }
376 }
377 return &u8(unsafe { nil }) // not reached, TODO: remove when V's checker is improved
378}
379
380// free allows for manually freeing memory allocated at the address `ptr`.
381@[unsafe]
382pub fn free(ptr voidptr) {
383 $if trace_free ? {
384 C.fprintf(C.stderr, c'free ptr: %p\n', ptr)
385 }
386 $if builtin_free_nop ? {
387 return
388 }
389 if ptr == unsafe { 0 } {
390 $if trace_free_nulls ? {
391 C.fprintf(C.stderr, c'free null ptr\n', ptr)
392 }
393 $if trace_free_nulls_break ? {
394 break_if_debugger_attached()
395 }
396 return
397 }
398 // `none__` is a process-wide singleton IError used by option/results to
399 // represent "no error object". Self-hosted builds can still route that
400 // sentinel through explicit cleanup paths under `-gc none`, so ignore it.
401 none_err := &C.IError(&none__)
402 if ptr == none_err._object {
403 return
404 }
405 $if prealloc {
406 return
407 } $else $if vgc ? {
408 // VGC: explicit free is optional (GC will collect unreachable objects).
409 // But hint the allocator for faster reuse.
410 vgc_free(ptr)
411 } $else $if gcboehm ? {
412 // It is generally better to leave it to Boehm's gc to free things.
413 // Calling C.GC_FREE(ptr) was tried initially, but does not work
414 // well with programs that do manual management themselves.
415 //
416 // The exception is doing leak detection for manual memory management:
417 $if gcboehm_leak ? {
418 unsafe { C.GC_FREE(ptr) }
419 }
420 } $else {
421 $if windows {
422 // Warning! On windows, we always use _aligned_free to free memory.
423 unsafe { C._aligned_free(ptr) }
424 } $else {
425 C.free(ptr)
426 }
427 }
428}
429
430// memdup dynamically allocates a `sz` bytes block of memory on the heap
431// memdup then copies the contents of `src` into the allocated space and
432// returns a pointer to the newly allocated space.
433@[unsafe]
434pub fn memdup(src voidptr, sz isize) voidptr {
435 $if trace_memdup ? {
436 C.fprintf(C.stderr, c'memdup size: %10d\n', sz)
437 }
438 if sz == 0 {
439 return vcalloc(1)
440 }
441 $if vgc ? {
442 return vgc_memdup(src, sz)
443 }
444 unsafe {
445 mem := malloc(sz)
446 return C.memcpy(mem, src, sz)
447 }
448}
449
450@[unsafe]
451pub fn memdup_noscan(src voidptr, sz isize) voidptr {
452 $if trace_memdup ? {
453 C.fprintf(C.stderr, c'memdup_noscan size: %10d\n', sz)
454 }
455 if sz == 0 {
456 return vcalloc_noscan(1)
457 }
458 $if vgc ? {
459 return vgc_memdup_noscan(src, sz)
460 }
461 unsafe {
462 mem := malloc_noscan(sz)
463 return C.memcpy(mem, src, sz)
464 }
465}
466
467// memdup_uncollectable dynamically allocates a `sz` bytes block of memory
468// on the heap, which will NOT be garbage-collected (but its contents will).
469// memdup_uncollectable then copies the contents of `src` into the allocated
470// space and returns a pointer to the newly allocated space.
471@[unsafe]
472pub fn memdup_uncollectable(src voidptr, sz isize) voidptr {
473 $if trace_memdup ? {
474 C.fprintf(C.stderr, c'memdup_uncollectable size: %10d\n', sz)
475 }
476 if sz == 0 {
477 return vcalloc(1)
478 }
479 unsafe {
480 mem := malloc_uncollectable(sz)
481 return C.memcpy(mem, src, sz)
482 }
483}
484
485// memdup_align dynamically allocates a memory block of `sz` bytes on the heap,
486// copies the contents from `src` into the allocated space, and returns a pointer
487// to the newly allocated memory. The returned pointer is aligned to the specified `align` boundary.
488// - `align` must be a power of two and at least 1
489// - `sz` must be non-negative
490// - The memory regions should not overlap
491@[unsafe]
492pub fn memdup_align(src voidptr, sz isize, align isize) voidptr {
493 $if trace_memdup ? {
494 C.fprintf(C.stderr, c'memdup_align size: %10d align: %10d\n', sz, align)
495 }
496 if sz == 0 {
497 return vcalloc(1)
498 }
499 n := sz
500 $if trace_malloc ? {
501 total_m += n
502 C.fprintf(C.stderr, c'_v_memdup_align %6d total %10d\n', n, total_m)
503 // print_backtrace()
504 }
505 if n < 0 {
506 _memory_panic(@FN, n)
507 }
508 mut res := &u8(unsafe { nil })
509 $if prealloc {
510 res = prealloc_malloc_align(n, align)
511 } $else $if gcboehm ? {
512 unsafe {
513 res = C.GC_memalign(align, n)
514 }
515 } $else $if freestanding {
516 // todo: is this safe to call malloc there? We export __malloc as malloc and it uses dlmalloc behind the scenes
517 // so theoretically it is safe
518 panic('memdup_align is not implemented with -freestanding')
519 res = unsafe { __malloc(usize(n)) }
520 } $else {
521 $if windows {
522 // Warning! On windows, we always use _aligned_malloc to allocate memory.
523 // This ensures that we can later free the memory with _aligned_free
524 // without needing to track whether the memory was originally allocated
525 // by malloc or _aligned_malloc.
526 res = unsafe { C._aligned_malloc(n, align) }
527 } $else {
528 res = unsafe { C.aligned_alloc(align, n) }
529 }
530 }
531 if res == 0 {
532 _memory_panic(@FN, n)
533 }
534 $if debug_malloc ? {
535 // Fill in the memory with something != 0 i.e. `M`, so it is easier to spot
536 // when the calling code wrongly relies on it being zeroed.
537 unsafe { C.memset(res, 0x4D, n) }
538 }
539 return C.memcpy(res, src, sz)
540}
541
542// GCHeapUsage contains stats about the current heap usage of your program.
543pub struct GCHeapUsage {
544pub:
545 heap_size usize
546 free_bytes usize
547 total_bytes usize
548 unmapped_bytes usize
549 bytes_since_gc usize
550}
551
552// gc_heap_usage returns the info about heap usage.
553pub fn gc_heap_usage() GCHeapUsage {
554 $if vgc ? {
555 heap_size, free_bytes, total_bytes, unmapped, bytes_since := vgc_heap_usage()
556 return GCHeapUsage{
557 heap_size: heap_size
558 free_bytes: free_bytes
559 total_bytes: total_bytes
560 unmapped_bytes: unmapped
561 bytes_since_gc: bytes_since
562 }
563 } $else $if gcboehm ? {
564 mut res := GCHeapUsage{}
565 C.GC_get_heap_usage_safe(&res.heap_size, &res.free_bytes, &res.unmapped_bytes,
566 &res.bytes_since_gc, &res.total_bytes)
567 return res
568 } $else {
569 return GCHeapUsage{}
570 }
571}
572
573// gc_memory_use returns the total memory use in bytes by all allocated blocks.
574pub fn gc_memory_use() usize {
575 $if vgc ? {
576 return vgc_memory_use()
577 } $else $if gcboehm ? {
578 return C.GC_get_memory_use()
579 } $else {
580 return 0
581 }
582}
583