diff --git a/core/debug/trace/allocator.odin b/core/debug/trace/allocator.odin index 7ea191fa8..642561bac 100644 --- a/core/debug/trace/allocator.odin +++ b/core/debug/trace/allocator.odin @@ -7,8 +7,6 @@ import "core:fmt" import "core:mem" import "core:sync" -// TODO: should this just be added to `core:mem.Tracking_Allocator`? - /* The backtrace tracking allocator is a similar allocator as the `core:mem` tracking allocator but keeps backtraces for each allocation. @@ -39,12 +37,18 @@ Example: } */ Tracking_Allocator :: struct { - backing: mem.Allocator, - internals_allocator: mem.Allocator, - allocation_map: map[rawptr]Tracking_Allocator_Entry, - bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry, - mutex: sync.Mutex, - clear_on_free_all: bool, + backing: mem.Allocator, + allocation_map: map[rawptr]Tracking_Allocator_Entry, + bad_free_callback: Tracking_Allocator_Bad_Free_Callback, + bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry, + mutex: sync.Mutex, + clear_on_free_all: bool, + total_memory_allocated: i64, + total_allocation_count: i64, + total_memory_freed: i64, + total_free_count: i64, + peak_memory_allocated: i64, + current_memory_allocated: i64, } Tracking_Allocator_Entry :: struct { @@ -63,14 +67,25 @@ Tracking_Allocator_Bad_Free_Entry :: struct { backtrace: Capture_Const, } +/* +Callback type for when tracking allocator runs into a bad free. +*/ +Tracking_Allocator_Bad_Free_Callback :: #type proc( + t: ^Tracking_Allocator, + memory: rawptr, + backtrace: Capture_Const, + location: runtime.Source_Code_Location, +) + +@(no_sanitize_address) tracking_allocator_init :: proc( t: ^Tracking_Allocator, backing_allocator: mem.Allocator, internals_allocator := context.allocator, ) { t.backing = backing_allocator - t.internals_allocator = internals_allocator t.allocation_map.allocator = internals_allocator + t.bad_free_callback = tracking_allocator_bad_free_callback_panic t.bad_free_array.allocator = internals_allocator if .Free_All in mem.query_features(t.backing) { @@ -78,23 +93,68 @@ tracking_allocator_init :: proc( } } +@(no_sanitize_address) tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) { delete(t.allocation_map) delete(t.bad_free_array) } +@(no_sanitize_address) tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { sync.guard(&t.mutex) clear(&t.allocation_map) clear(&t.bad_free_array) + t.current_memory_allocated = 0 } -@(require_results) +@(no_sanitize_address) +tracking_allocator_reset :: proc(t: ^Tracking_Allocator) { + sync.guard(&t.mutex) + clear(&t.allocation_map) + clear(&t.bad_free_array) + t.total_memory_allocated = 0 + t.total_allocation_count = 0 + t.total_memory_freed = 0 + t.total_free_count = 0 + t.peak_memory_allocated = 0 + t.current_memory_allocated = 0 +} + +@(no_sanitize_address) +tracking_allocator_bad_free_callback_panic :: proc( + t: ^Tracking_Allocator, + memory: rawptr, + backtrace: Capture_Const, + location: runtime.Source_Code_Location, +) { + buf: [256]byte + offset := 0 + _ = runtime.write_string(&offset, buf[:], "Tracking allocator error: Bad free of pointer ") + _ = runtime.write_u64(&offset, buf[:], u64(uintptr(memory))) + panic(string(buf[:offset]), loc = location) +} + +@(no_sanitize_address) +tracking_allocator_bad_free_callback_add_to_array :: proc( + t: ^Tracking_Allocator, + memory: rawptr, + backtrace: Capture_Const, + location: runtime.Source_Code_Location, +) { + append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry { + memory = memory, + location = location, + backtrace = backtrace, + }) +} + +@(require_results, no_sanitize_address) tracking_allocator :: proc(data: ^Tracking_Allocator) -> mem.Allocator { return mem.Allocator{data = data, procedure = tracking_allocator_proc} } +@(no_sanitize_address) tracking_allocator_proc :: proc( allocator_data: rawptr, mode: mem.Allocator_Mode, @@ -106,6 +166,23 @@ tracking_allocator_proc :: proc( result: []byte, err: mem.Allocator_Error, ) { + @(no_sanitize_address) + track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { + data.total_memory_allocated += i64(entry.size) + data.total_allocation_count += 1 + data.current_memory_allocated += i64(entry.size) + if data.current_memory_allocated > data.peak_memory_allocated { + data.peak_memory_allocated = data.current_memory_allocated + } + } + + @(no_sanitize_address) + track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { + data.total_memory_freed += i64(entry.size) + data.total_free_count += 1 + data.current_memory_allocated -= i64(entry.size) + } + data := (^Tracking_Allocator)(allocator_data) sync.mutex_guard(&data.mutex) @@ -124,14 +201,9 @@ tracking_allocator_proc :: proc( } if mode == .Free && old_memory != nil && old_memory not_in data.allocation_map { - append( - &data.bad_free_array, - Tracking_Allocator_Bad_Free_Entry{ - memory = old_memory, - location = loc, - backtrace = capture(skip=1), - }, - ) + if data.bad_free_callback != nil { + data.bad_free_callback(data, old_memory, capture(skip=1), loc) + } } else { result = data.backing.procedure( data.backing.data, @@ -160,13 +232,21 @@ tracking_allocator_proc :: proc( location = loc, backtrace = capture(skip=1), } + track_alloc(data, &data.allocation_map[result_ptr]) case .Free: + if old_memory != nil && old_memory in data.allocation_map { + track_free(data, &data.allocation_map[old_memory]) + } delete_key(&data.allocation_map, old_memory) case .Free_All: if data.clear_on_free_all { clear_map(&data.allocation_map) + data.current_memory_allocated = 0 } case .Resize, .Resize_Non_Zeroed: + if old_memory != nil && old_memory in data.allocation_map { + track_free(data, &data.allocation_map[old_memory]) + } if old_memory != result_ptr { delete_key(&data.allocation_map, old_memory) } @@ -179,6 +259,7 @@ tracking_allocator_proc :: proc( location = loc, backtrace = capture(skip=1), } + track_alloc(data, &data.allocation_map[result_ptr]) case .Query_Features: set := (^mem.Allocator_Mode_Set)(old_memory)