tracking_allocator.odin 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. #+build !freestanding
  2. package mem
  3. import "base:runtime"
  4. import "core:sync"
  5. /*
  6. Allocation entry for the tracking allocator.
  7. This structure stores the data related to an allocation.
  8. */
  9. Tracking_Allocator_Entry :: struct {
  10. // Pointer to an allocated region.
  11. memory: rawptr,
  12. // Size of the allocated memory region.
  13. size: int,
  14. // Requested alignment.
  15. alignment: int,
  16. // Mode of the operation.
  17. mode: Allocator_Mode,
  18. // Error.
  19. err: Allocator_Error,
  20. // Location of the allocation.
  21. location: runtime.Source_Code_Location,
  22. }
  23. /*
  24. Bad free entry for a tracking allocator.
  25. */
  26. Tracking_Allocator_Bad_Free_Entry :: struct {
  27. // Pointer, on which free operation was called.
  28. memory: rawptr,
  29. // The source location of where the operation was called.
  30. location: runtime.Source_Code_Location,
  31. }
  32. /*
  33. Tracking allocator data.
  34. */
  35. Tracking_Allocator :: struct {
  36. backing: Allocator,
  37. allocation_map: map[rawptr]Tracking_Allocator_Entry,
  38. bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry,
  39. mutex: sync.Mutex,
  40. clear_on_free_all: bool,
  41. total_memory_allocated: i64,
  42. total_allocation_count: i64,
  43. total_memory_freed: i64,
  44. total_free_count: i64,
  45. peak_memory_allocated: i64,
  46. current_memory_allocated: i64,
  47. }
  48. /*
  49. Initialize the tracking allocator.
  50. This procedure initializes the tracking allocator `t` with a backing allocator
  51. specified with `backing_allocator`. The `internals_allocator` will used to
  52. allocate the tracked data.
  53. */
  54. tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
  55. t.backing = backing_allocator
  56. t.allocation_map.allocator = internals_allocator
  57. t.bad_free_array.allocator = internals_allocator
  58. if .Free_All in query_features(t.backing) {
  59. t.clear_on_free_all = true
  60. }
  61. }
  62. /*
  63. Destroy the tracking allocator.
  64. */
  65. tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
  66. delete(t.allocation_map)
  67. delete(t.bad_free_array)
  68. }
  69. /*
  70. Clear the tracking allocator.
  71. This procedure clears the tracked data from a tracking allocator.
  72. **Note**: This procedure clears only the current allocation data while keeping
  73. the totals intact.
  74. */
  75. tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
  76. sync.mutex_lock(&t.mutex)
  77. clear(&t.allocation_map)
  78. clear(&t.bad_free_array)
  79. t.current_memory_allocated = 0
  80. sync.mutex_unlock(&t.mutex)
  81. }
  82. /*
  83. Reset the tracking allocator.
  84. Reset all of a Tracking Allocator's allocation data back to zero.
  85. */
  86. tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
  87. sync.mutex_lock(&t.mutex)
  88. clear(&t.allocation_map)
  89. clear(&t.bad_free_array)
  90. t.total_memory_allocated = 0
  91. t.total_allocation_count = 0
  92. t.total_memory_freed = 0
  93. t.total_free_count = 0
  94. t.peak_memory_allocated = 0
  95. t.current_memory_allocated = 0
  96. sync.mutex_unlock(&t.mutex)
  97. }
  98. /*
  99. Tracking allocator.
  100. The tracking allocator is an allocator wrapper that tracks memory allocations.
  101. This allocator stores all the allocations in a map. Whenever a pointer that's
  102. not inside of the map is freed, the `bad_free_array` entry is added.
  103. An example of how to use the `Tracking_Allocator` to track subsequent allocations
  104. in your program and report leaks and bad frees:
  105. Example:
  106. package foo
  107. import "core:mem"
  108. import "core:fmt"
  109. main :: proc() {
  110. track: mem.Tracking_Allocator
  111. mem.tracking_allocator_init(&track, context.allocator)
  112. defer mem.tracking_allocator_destroy(&track)
  113. context.allocator = mem.tracking_allocator(&track)
  114. do_stuff()
  115. for _, leak in track.allocation_map {
  116. fmt.printf("%v leaked %m\n", leak.location, leak.size)
  117. }
  118. for bad_free in track.bad_free_array {
  119. fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)
  120. }
  121. }
  122. */
  123. @(require_results)
  124. tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
  125. return Allocator{
  126. data = data,
  127. procedure = tracking_allocator_proc,
  128. }
  129. }
  130. tracking_allocator_proc :: proc(
  131. allocator_data: rawptr,
  132. mode: Allocator_Mode,
  133. size, alignment: int,
  134. old_memory: rawptr,
  135. old_size: int,
  136. loc := #caller_location,
  137. ) -> (result: []byte, err: Allocator_Error) {
  138. track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
  139. data.total_memory_allocated += i64(entry.size)
  140. data.total_allocation_count += 1
  141. data.current_memory_allocated += i64(entry.size)
  142. if data.current_memory_allocated > data.peak_memory_allocated {
  143. data.peak_memory_allocated = data.current_memory_allocated
  144. }
  145. }
  146. track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
  147. data.total_memory_freed += i64(entry.size)
  148. data.total_free_count += 1
  149. data.current_memory_allocated -= i64(entry.size)
  150. }
  151. data := (^Tracking_Allocator)(allocator_data)
  152. sync.mutex_guard(&data.mutex)
  153. if mode == .Query_Info {
  154. info := (^Allocator_Query_Info)(old_memory)
  155. if info != nil && info.pointer != nil {
  156. if entry, ok := data.allocation_map[info.pointer]; ok {
  157. info.size = entry.size
  158. info.alignment = entry.alignment
  159. }
  160. info.pointer = nil
  161. }
  162. return
  163. }
  164. if mode == .Free && old_memory != nil && old_memory not_in data.allocation_map {
  165. append(&data.bad_free_array, Tracking_Allocator_Bad_Free_Entry{
  166. memory = old_memory,
  167. location = loc,
  168. })
  169. } else {
  170. result = data.backing.procedure(data.backing.data, mode, size, alignment, old_memory, old_size, loc) or_return
  171. }
  172. result_ptr := raw_data(result)
  173. if data.allocation_map.allocator.procedure == nil {
  174. data.allocation_map.allocator = context.allocator
  175. }
  176. switch mode {
  177. case .Alloc, .Alloc_Non_Zeroed:
  178. data.allocation_map[result_ptr] = Tracking_Allocator_Entry{
  179. memory = result_ptr,
  180. size = size,
  181. mode = mode,
  182. alignment = alignment,
  183. err = err,
  184. location = loc,
  185. }
  186. track_alloc(data, &data.allocation_map[result_ptr])
  187. case .Free:
  188. if old_memory != nil && old_memory in data.allocation_map {
  189. track_free(data, &data.allocation_map[old_memory])
  190. }
  191. delete_key(&data.allocation_map, old_memory)
  192. case .Free_All:
  193. if data.clear_on_free_all {
  194. clear_map(&data.allocation_map)
  195. data.current_memory_allocated = 0
  196. }
  197. case .Resize, .Resize_Non_Zeroed:
  198. if old_memory != nil && old_memory in data.allocation_map {
  199. track_free(data, &data.allocation_map[old_memory])
  200. }
  201. if old_memory != result_ptr {
  202. delete_key(&data.allocation_map, old_memory)
  203. }
  204. data.allocation_map[result_ptr] = Tracking_Allocator_Entry{
  205. memory = result_ptr,
  206. size = size,
  207. mode = mode,
  208. alignment = alignment,
  209. err = err,
  210. location = loc,
  211. }
  212. track_alloc(data, &data.allocation_map[result_ptr])
  213. case .Query_Features:
  214. set := (^Allocator_Mode_Set)(old_memory)
  215. if set != nil {
  216. set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Query_Features, .Query_Info}
  217. }
  218. return nil, nil
  219. case .Query_Info:
  220. unreachable()
  221. }
  222. return
  223. }