tracking_allocator.odin 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. #+build !freestanding, wasm32, wasm64p32
  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. Callback type for when tracking allocator runs into a bad free.
  34. */
  35. Tracking_Allocator_Bad_Free_Callback :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location)
  36. /*
  37. Tracking allocator data.
  38. */
  39. Tracking_Allocator :: struct {
  40. backing: Allocator,
  41. allocation_map: map[rawptr]Tracking_Allocator_Entry,
  42. bad_free_callback: Tracking_Allocator_Bad_Free_Callback,
  43. bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry,
  44. mutex: sync.Mutex,
  45. clear_on_free_all: bool,
  46. total_memory_allocated: i64,
  47. total_allocation_count: i64,
  48. total_memory_freed: i64,
  49. total_free_count: i64,
  50. peak_memory_allocated: i64,
  51. current_memory_allocated: i64,
  52. }
  53. /*
  54. Initialize the tracking allocator.
  55. This procedure initializes the tracking allocator `t` with a backing allocator
  56. specified with `backing_allocator`. The `internals_allocator` will used to
  57. allocate the tracked data.
  58. */
  59. @(no_sanitize_address)
  60. tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
  61. t.backing = backing_allocator
  62. t.allocation_map.allocator = internals_allocator
  63. t.bad_free_callback = tracking_allocator_bad_free_callback_panic
  64. t.bad_free_array.allocator = internals_allocator
  65. if .Free_All in query_features(t.backing) {
  66. t.clear_on_free_all = true
  67. }
  68. }
  69. /*
  70. Destroy the tracking allocator.
  71. */
  72. @(no_sanitize_address)
  73. tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
  74. delete(t.allocation_map)
  75. delete(t.bad_free_array)
  76. }
  77. /*
  78. Clear the tracking allocator.
  79. This procedure clears the tracked data from a tracking allocator.
  80. **Note**: This procedure clears only the current allocation data while keeping
  81. the totals intact.
  82. */
  83. @(no_sanitize_address)
  84. tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
  85. sync.mutex_lock(&t.mutex)
  86. clear(&t.allocation_map)
  87. clear(&t.bad_free_array)
  88. t.current_memory_allocated = 0
  89. sync.mutex_unlock(&t.mutex)
  90. }
  91. /*
  92. Reset the tracking allocator.
  93. Reset all of a Tracking Allocator's allocation data back to zero.
  94. */
  95. @(no_sanitize_address)
  96. tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
  97. sync.mutex_lock(&t.mutex)
  98. clear(&t.allocation_map)
  99. clear(&t.bad_free_array)
  100. t.total_memory_allocated = 0
  101. t.total_allocation_count = 0
  102. t.total_memory_freed = 0
  103. t.total_free_count = 0
  104. t.peak_memory_allocated = 0
  105. t.current_memory_allocated = 0
  106. sync.mutex_unlock(&t.mutex)
  107. }
  108. /*
  109. Default behavior for a bad free: Crash with error message that says where the
  110. bad free happened.
  111. Override Tracking_Allocator.bad_free_callback to have something else happen. For
  112. example, you can use tracking_allocator_bad_free_callback_add_to_array to return
  113. the tracking allocator to the old behavior, where the bad_free_array was used.
  114. */
  115. @(no_sanitize_address)
  116. tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
  117. runtime.print_caller_location(location)
  118. runtime.print_string(" Tracking allocator error: Bad free of pointer ")
  119. runtime.print_uintptr(uintptr(memory))
  120. runtime.print_string("\n")
  121. runtime.trap()
  122. }
  123. /*
  124. Alternative behavior for a bad free: Store in `bad_free_array`. If you use this,
  125. then you must make sure to check Tracking_Allocator.bad_free_array at some point.
  126. */
  127. @(no_sanitize_address)
  128. tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
  129. append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry {
  130. memory = memory,
  131. location = location,
  132. })
  133. }
  134. /*
  135. Tracking allocator.
  136. The tracking allocator is an allocator wrapper that tracks memory allocations.
  137. This allocator stores all the allocations in a map. Whenever a pointer that's
  138. not inside of the map is freed, the `bad_free_array` entry is added.
  139. Here follows an example of how to use the `Tracking_Allocator` to track
  140. subsequent allocations in your program and report leaks. By default, the
  141. tracking allocator will crash on bad frees. You can override that behavior by
  142. overriding `track.bad_free_callback`.
  143. Example:
  144. package foo
  145. import "core:mem"
  146. import "core:fmt"
  147. main :: proc() {
  148. track: mem.Tracking_Allocator
  149. mem.tracking_allocator_init(&track, context.allocator)
  150. defer mem.tracking_allocator_destroy(&track)
  151. context.allocator = mem.tracking_allocator(&track)
  152. do_stuff()
  153. for _, leak in track.allocation_map {
  154. fmt.printf("%v leaked %m\n", leak.location, leak.size)
  155. }
  156. }
  157. */
  158. @(require_results, no_sanitize_address)
  159. tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
  160. return Allocator{
  161. data = data,
  162. procedure = tracking_allocator_proc,
  163. }
  164. }
  165. @(no_sanitize_address)
  166. tracking_allocator_proc :: proc(
  167. allocator_data: rawptr,
  168. mode: Allocator_Mode,
  169. size, alignment: int,
  170. old_memory: rawptr,
  171. old_size: int,
  172. loc := #caller_location,
  173. ) -> (result: []byte, err: Allocator_Error) {
  174. @(no_sanitize_address)
  175. track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
  176. data.total_memory_allocated += i64(entry.size)
  177. data.total_allocation_count += 1
  178. data.current_memory_allocated += i64(entry.size)
  179. if data.current_memory_allocated > data.peak_memory_allocated {
  180. data.peak_memory_allocated = data.current_memory_allocated
  181. }
  182. }
  183. @(no_sanitize_address)
  184. track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
  185. data.total_memory_freed += i64(entry.size)
  186. data.total_free_count += 1
  187. data.current_memory_allocated -= i64(entry.size)
  188. }
  189. data := (^Tracking_Allocator)(allocator_data)
  190. sync.mutex_guard(&data.mutex)
  191. if mode == .Query_Info {
  192. info := (^Allocator_Query_Info)(old_memory)
  193. if info != nil && info.pointer != nil {
  194. if entry, ok := data.allocation_map[info.pointer]; ok {
  195. info.size = entry.size
  196. info.alignment = entry.alignment
  197. }
  198. info.pointer = nil
  199. }
  200. return
  201. }
  202. if mode == .Free && old_memory != nil && old_memory not_in data.allocation_map {
  203. if data.bad_free_callback != nil {
  204. data.bad_free_callback(data, old_memory, loc)
  205. }
  206. } else {
  207. result = data.backing.procedure(data.backing.data, mode, size, alignment, old_memory, old_size, loc) or_return
  208. }
  209. result_ptr := raw_data(result)
  210. if data.allocation_map.allocator.procedure == nil {
  211. data.allocation_map.allocator = context.allocator
  212. }
  213. switch mode {
  214. case .Alloc, .Alloc_Non_Zeroed:
  215. data.allocation_map[result_ptr] = Tracking_Allocator_Entry{
  216. memory = result_ptr,
  217. size = size,
  218. mode = mode,
  219. alignment = alignment,
  220. err = err,
  221. location = loc,
  222. }
  223. track_alloc(data, &data.allocation_map[result_ptr])
  224. case .Free:
  225. if old_memory != nil && old_memory in data.allocation_map {
  226. track_free(data, &data.allocation_map[old_memory])
  227. }
  228. delete_key(&data.allocation_map, old_memory)
  229. case .Free_All:
  230. if data.clear_on_free_all {
  231. clear_map(&data.allocation_map)
  232. data.current_memory_allocated = 0
  233. }
  234. case .Resize, .Resize_Non_Zeroed:
  235. if old_memory != nil && old_memory in data.allocation_map {
  236. track_free(data, &data.allocation_map[old_memory])
  237. }
  238. if old_memory != result_ptr {
  239. delete_key(&data.allocation_map, old_memory)
  240. }
  241. data.allocation_map[result_ptr] = Tracking_Allocator_Entry{
  242. memory = result_ptr,
  243. size = size,
  244. mode = mode,
  245. alignment = alignment,
  246. err = err,
  247. location = loc,
  248. }
  249. track_alloc(data, &data.allocation_map[result_ptr])
  250. case .Query_Features:
  251. set := (^Allocator_Mode_Set)(old_memory)
  252. if set != nil {
  253. set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Query_Features, .Query_Info}
  254. }
  255. return nil, nil
  256. case .Query_Info:
  257. unreachable()
  258. }
  259. return
  260. }