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