tlsf.odin 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. // Two Level Segregated Fit memory allocator.
  2. package mem_tlsf
  3. /*
  4. Copyright 2024 Jeroen van Rijn <[email protected]>.
  5. Made available under Odin's BSD-3 license.
  6. List of contributors:
  7. Matt Conte: Original C implementation, see LICENSE file in this package
  8. Jeroen van Rijn: Source port
  9. */
  10. import "base:intrinsics"
  11. import "base:runtime"
  12. Error :: enum byte {
  13. None = 0,
  14. Invalid_Backing_Allocator = 1,
  15. Invalid_Alignment = 2,
  16. Backing_Buffer_Too_Small = 3,
  17. Backing_Buffer_Too_Large = 4,
  18. Backing_Allocator_Error = 5,
  19. }
  20. Allocator :: struct {
  21. // Empty lists point at this block to indicate they are free.
  22. block_null: Block_Header,
  23. // Bitmaps for free lists.
  24. fl_bitmap: u32 `fmt:"-"`,
  25. sl_bitmap: [FL_INDEX_COUNT]u32 `fmt:"-"`,
  26. // Head of free lists.
  27. blocks: [FL_INDEX_COUNT][SL_INDEX_COUNT]^Block_Header `fmt:"-"`,
  28. // Keep track of pools so we can deallocate them.
  29. // If `pool.allocator` is blank, we don't do anything.
  30. // We also use this linked list of pools to report
  31. // statistics like how much memory is still available,
  32. // fragmentation, etc.
  33. pool: Pool,
  34. // If we're expected to grow when we run out of memory,
  35. // how much should we ask the backing allocator for?
  36. new_pool_size: uint,
  37. }
  38. #assert(size_of(Allocator) % ALIGN_SIZE == 0)
  39. @(require_results)
  40. allocator :: proc(t: ^Allocator) -> runtime.Allocator {
  41. return runtime.Allocator{
  42. procedure = allocator_proc,
  43. data = t,
  44. }
  45. }
  46. // Tries to estimate a pool size sufficient for `count` allocations, each of `size` and with `alignment`.
  47. estimate_pool_from_size_alignment :: proc(count: int, size: int, alignment: int) -> (pool_size: int) {
  48. per_allocation := align_up(uint(size + alignment) + BLOCK_HEADER_OVERHEAD, ALIGN_SIZE)
  49. return count * int(per_allocation) + int(INITIAL_POOL_OVERHEAD)
  50. }
  51. // Tries to estimate a pool size sufficient for `count` allocations of `type`.
  52. estimate_pool_from_typeid :: proc(count: int, type: typeid) -> (pool_size: int) {
  53. ti := type_info_of(type)
  54. return estimate_pool_size(count, ti.size, ti.align)
  55. }
  56. estimate_pool_size :: proc{estimate_pool_from_size_alignment, estimate_pool_from_typeid}
  57. @(require_results)
  58. init_from_buffer :: proc(control: ^Allocator, buf: []byte) -> Error {
  59. assert(control != nil)
  60. if uintptr(raw_data(buf)) % ALIGN_SIZE != 0 {
  61. return .Invalid_Alignment
  62. }
  63. pool_bytes := align_down(len(buf) - INITIAL_POOL_OVERHEAD, ALIGN_SIZE)
  64. if pool_bytes < BLOCK_SIZE_MIN {
  65. return .Backing_Buffer_Too_Small
  66. } else if pool_bytes > BLOCK_SIZE_MAX {
  67. return .Backing_Buffer_Too_Large
  68. }
  69. control.pool = Pool{
  70. data = buf,
  71. allocator = {},
  72. }
  73. return free_all(control)
  74. }
  75. @(require_results)
  76. init_from_allocator :: proc(control: ^Allocator, backing: runtime.Allocator, initial_pool_size: int, new_pool_size := 0) -> Error {
  77. assert(control != nil)
  78. pool_bytes := uint(estimate_pool_size(1, initial_pool_size, ALIGN_SIZE))
  79. if pool_bytes < BLOCK_SIZE_MIN {
  80. return .Backing_Buffer_Too_Small
  81. } else if pool_bytes > BLOCK_SIZE_MAX {
  82. return .Backing_Buffer_Too_Large
  83. }
  84. buf, backing_err := runtime.make_aligned([]byte, pool_bytes, ALIGN_SIZE, backing)
  85. if backing_err != nil {
  86. return .Backing_Allocator_Error
  87. }
  88. control.pool = Pool{
  89. data = buf,
  90. allocator = backing,
  91. }
  92. control.new_pool_size = uint(new_pool_size)
  93. return free_all(control)
  94. }
  95. init :: proc{init_from_buffer, init_from_allocator}
  96. destroy :: proc(control: ^Allocator) {
  97. if control == nil { return }
  98. if control.pool.allocator.procedure != nil {
  99. runtime.delete(control.pool.data, control.pool.allocator)
  100. }
  101. // No need to call `pool_remove` or anything, as they're they're embedded in the backing memory.
  102. // We do however need to free the `Pool` tracking entities and the backing memory itself.
  103. for p := control.pool.next; p != nil; {
  104. next := p.next
  105. // Free the allocation on the backing allocator
  106. runtime.delete(p.data, p.allocator)
  107. free(p, p.allocator)
  108. p = next
  109. }
  110. }
  111. allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
  112. size, alignment: int,
  113. old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, runtime.Allocator_Error) {
  114. control := (^Allocator)(allocator_data)
  115. if control == nil {
  116. return nil, .Invalid_Argument
  117. }
  118. switch mode {
  119. case .Alloc:
  120. return alloc_bytes(control, uint(size), uint(alignment))
  121. case .Alloc_Non_Zeroed:
  122. return alloc_bytes_non_zeroed(control, uint(size), uint(alignment))
  123. case .Free:
  124. free_with_size(control, old_memory, uint(old_size))
  125. return nil, nil
  126. case .Free_All:
  127. free_all(control)
  128. return nil, nil
  129. case .Resize:
  130. return resize(control, old_memory, uint(old_size), uint(size), uint(alignment))
  131. case .Resize_Non_Zeroed:
  132. return resize_non_zeroed(control, old_memory, uint(old_size), uint(size), uint(alignment))
  133. case .Query_Features:
  134. set := (^runtime.Allocator_Mode_Set)(old_memory)
  135. if set != nil {
  136. set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, /* .Free_All, */ .Resize, .Resize_Non_Zeroed, .Query_Features}
  137. }
  138. return nil, nil
  139. case .Query_Info:
  140. return nil, .Mode_Not_Implemented
  141. }
  142. return nil, nil
  143. }
  144. // Exported solely to facilitate testing
  145. @(require_results)
  146. ffs :: proc "contextless" (word: u32) -> (bit: i32) {
  147. return -1 if word == 0 else i32(intrinsics.count_trailing_zeros(word))
  148. }
  149. // Exported solely to facilitate testing
  150. @(require_results)
  151. fls :: proc "contextless" (word: u32) -> (bit: i32) {
  152. N :: (size_of(u32) * 8) - 1
  153. return i32(N - intrinsics.count_leading_zeros(word))
  154. }
  155. // Exported solely to facilitate testing
  156. @(require_results)
  157. fls_uint :: proc "contextless" (size: uint) -> (bit: i32) {
  158. N :: (size_of(uint) * 8) - 1
  159. return i32(N - intrinsics.count_leading_zeros(size))
  160. }