heap_linux.odin 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. #+private
  2. package os2
  3. import "core:sys/linux"
  4. import "core:sync"
  5. import "core:mem"
  6. // NOTEs
  7. //
  8. // All allocations below DIRECT_MMAP_THRESHOLD exist inside of memory "Regions." A region
  9. // consists of a Region_Header and the memory that will be divided into allocations to
  10. // send to the user. The memory is an array of "Allocation_Headers" which are 8 bytes.
  11. // Allocation_Headers are used to navigate the memory in the region. The "next" member of
  12. // the Allocation_Header points to the next header, and the space between the headers
  13. // can be used to send to the user. This space between is referred to as "blocks" in the
  14. // code. The indexes in the header refer to these blocks instead of bytes. This allows us
  15. // to index all the memory in the region with a u16.
  16. //
  17. // When an allocation request is made, it will use the first free block that can contain
  18. // the entire block. If there is an excess number of blocks (as specified by the constant
  19. // BLOCK_SEGMENT_THRESHOLD), this extra space will be segmented and left in the free_list.
  20. //
  21. // To keep the implementation simple, there can never exist 2 free blocks adjacent to each
  22. // other. Any freeing will result in attempting to merge the blocks before and after the
  23. // newly free'd blocks.
  24. //
  25. // Any request for size above the DIRECT_MMAP_THRESHOLD will result in the allocation
  26. // getting its own individual mmap. Individual mmaps will still get an Allocation_Header
  27. // that contains the size with the last bit set to 1 to indicate it is indeed a direct
  28. // mmap allocation.
  29. // Why not brk?
  30. // glibc's malloc utilizes a mix of the brk and mmap system calls. This implementation
  31. // does *not* utilize the brk system call to avoid possible conflicts with foreign C
  32. // code. Just because we aren't directly using libc, there is nothing stopping the user
  33. // from doing it.
  34. // What's with all the #no_bounds_check?
  35. // When memory is returned from mmap, it technically doesn't get written ... well ... anywhere
  36. // until that region is written to by *you*. So, when a new region is created, we call mmap
  37. // to get a pointer to some memory, and we claim that memory is a ^Region. Therefor, the
  38. // region itself is never formally initialized by the compiler as this would result in writing
  39. // zeros to memory that we can already assume are 0. This would also have the effect of
  40. // actually commiting this data to memory whether it gets used or not.
  41. //
  42. // Some variables to play with
  43. //
  44. // Minimum blocks used for any one allocation
  45. MINIMUM_BLOCK_COUNT :: 2
  46. // Number of extra blocks beyond the requested amount where we would segment.
  47. // E.g. (blocks) |H0123456| 7 available
  48. // |H01H0123| Ask for 2, now 4 available
  49. BLOCK_SEGMENT_THRESHOLD :: 4
  50. // Anything above this threshold will get its own memory map. Since regions
  51. // are indexed by 16 bit integers, this value should not surpass max(u16) * 6
  52. DIRECT_MMAP_THRESHOLD_USER :: int(max(u16))
  53. // The point at which we convert direct mmap to region. This should be a decent
  54. // amount less than DIRECT_MMAP_THRESHOLD to avoid jumping in and out of regions.
  55. MMAP_TO_REGION_SHRINK_THRESHOLD :: DIRECT_MMAP_THRESHOLD - PAGE_SIZE * 4
  56. // free_list is dynamic and is initialized in the begining of the region memory
  57. // when the region is initialized. Once resized, it can be moved anywhere.
  58. FREE_LIST_DEFAULT_CAP :: 32
  59. //
  60. // Other constants that should not be touched
  61. //
  62. // This universally seems to be 4096 outside of uncommon archs.
  63. PAGE_SIZE :: 4096
  64. // just rounding up to nearest PAGE_SIZE
  65. DIRECT_MMAP_THRESHOLD :: (DIRECT_MMAP_THRESHOLD_USER-1) + PAGE_SIZE - (DIRECT_MMAP_THRESHOLD_USER-1) % PAGE_SIZE
  66. // Regions must be big enough to hold DIRECT_MMAP_THRESHOLD - 1 as well
  67. // as end right on a page boundary as to not waste space.
  68. SIZE_OF_REGION :: DIRECT_MMAP_THRESHOLD + 4 * int(PAGE_SIZE)
  69. // size of user memory blocks
  70. BLOCK_SIZE :: size_of(Allocation_Header)
  71. // number of allocation sections (call them blocks) of the region used for allocations
  72. BLOCKS_PER_REGION :: u16((SIZE_OF_REGION - size_of(Region_Header)) / BLOCK_SIZE)
  73. // minimum amount of space that can used by any individual allocation (includes header)
  74. MINIMUM_ALLOCATION :: (MINIMUM_BLOCK_COUNT * BLOCK_SIZE) + BLOCK_SIZE
  75. // This is used as a boolean value for Region_Header.local_addr.
  76. CURRENTLY_ACTIVE :: (^^Region)(~uintptr(0))
  77. FREE_LIST_ENTRIES_PER_BLOCK :: BLOCK_SIZE / size_of(u16)
  78. MMAP_FLAGS : linux.Map_Flags : {.ANONYMOUS, .PRIVATE}
  79. MMAP_PROT : linux.Mem_Protection : {.READ, .WRITE}
  80. @thread_local _local_region: ^Region
  81. global_regions: ^Region
  82. // There is no way of correctly setting the last bit of free_idx or
  83. // the last bit of requested, so we can safely use it as a flag to
  84. // determine if we are interacting with a direct mmap.
  85. REQUESTED_MASK :: 0x7FFFFFFFFFFFFFFF
  86. IS_DIRECT_MMAP :: 0x8000000000000000
  87. // Special free_idx value that does not index the free_list.
  88. NOT_FREE :: 0x7FFF
  89. Allocation_Header :: struct #raw_union {
  90. using _: struct {
  91. // Block indicies
  92. idx: u16,
  93. prev: u16,
  94. next: u16,
  95. free_idx: u16,
  96. },
  97. requested: u64,
  98. }
  99. Region_Header :: struct #align(16) {
  100. next_region: ^Region, // points to next region in global_heap (linked list)
  101. local_addr: ^^Region, // tracks region ownership via address of _local_region
  102. reset_addr: ^^Region, // tracks old local addr for reset
  103. free_list: []u16,
  104. free_list_len: u16,
  105. free_blocks: u16, // number of free blocks in region (includes headers)
  106. last_used: u16, // farthest back block that has been used (need zeroing?)
  107. _reserved: u16,
  108. }
  109. Region :: struct {
  110. hdr: Region_Header,
  111. memory: [BLOCKS_PER_REGION]Allocation_Header,
  112. }
  113. _heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
  114. size, alignment: int,
  115. old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) {
  116. //
  117. // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment.
  118. // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert
  119. // padding. We also store the original pointer returned by heap_alloc right before
  120. // the pointer we return to the user.
  121. //
  122. aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) {
  123. a := max(alignment, align_of(rawptr))
  124. space := size + a - 1
  125. allocated_mem: rawptr
  126. if old_ptr != nil {
  127. original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^
  128. allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr))
  129. } else {
  130. allocated_mem = heap_alloc(space+size_of(rawptr))
  131. }
  132. aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr)))
  133. ptr := uintptr(aligned_mem)
  134. aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a)
  135. diff := int(aligned_ptr - ptr)
  136. if (size + diff) > space || allocated_mem == nil {
  137. return nil, .Out_Of_Memory
  138. }
  139. aligned_mem = rawptr(aligned_ptr)
  140. mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem
  141. return mem.byte_slice(aligned_mem, size), nil
  142. }
  143. aligned_free :: proc(p: rawptr) {
  144. if p != nil {
  145. heap_free(mem.ptr_offset((^rawptr)(p), -1)^)
  146. }
  147. }
  148. aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> (new_memory: []byte, err: mem.Allocator_Error) {
  149. if p == nil {
  150. return nil, nil
  151. }
  152. return aligned_alloc(new_size, new_alignment, p)
  153. }
  154. switch mode {
  155. case .Alloc, .Alloc_Non_Zeroed:
  156. return aligned_alloc(size, alignment)
  157. case .Free:
  158. aligned_free(old_memory)
  159. case .Free_All:
  160. return nil, .Mode_Not_Implemented
  161. case .Resize, .Resize_Non_Zeroed:
  162. if old_memory == nil {
  163. return aligned_alloc(size, alignment)
  164. }
  165. return aligned_resize(old_memory, old_size, size, alignment)
  166. case .Query_Features:
  167. set := (^mem.Allocator_Mode_Set)(old_memory)
  168. if set != nil {
  169. set^ = {.Alloc, .Free, .Resize, .Query_Features}
  170. }
  171. return nil, nil
  172. case .Query_Info:
  173. return nil, .Mode_Not_Implemented
  174. }
  175. return nil, nil
  176. }
  177. heap_alloc :: proc(size: int) -> rawptr {
  178. if size >= DIRECT_MMAP_THRESHOLD {
  179. return _direct_mmap_alloc(size)
  180. }
  181. // atomically check if the local region has been stolen
  182. if _local_region != nil {
  183. res := sync.atomic_compare_exchange_strong_explicit(
  184. &_local_region.hdr.local_addr,
  185. &_local_region,
  186. CURRENTLY_ACTIVE,
  187. .Acquire,
  188. .Relaxed,
  189. )
  190. if res != &_local_region {
  191. // At this point, the region has been stolen and res contains the unexpected value
  192. expected := res
  193. if res != CURRENTLY_ACTIVE {
  194. expected = res
  195. res = sync.atomic_compare_exchange_strong_explicit(
  196. &_local_region.hdr.local_addr,
  197. expected,
  198. CURRENTLY_ACTIVE,
  199. .Acquire,
  200. .Relaxed,
  201. )
  202. }
  203. if res != expected {
  204. _local_region = nil
  205. }
  206. }
  207. }
  208. size := size
  209. size = _round_up_to_nearest(size, BLOCK_SIZE)
  210. blocks_needed := u16(max(MINIMUM_BLOCK_COUNT, size / BLOCK_SIZE))
  211. // retrieve a region if new thread or stolen
  212. if _local_region == nil {
  213. _local_region, _ = _region_retrieve_with_space(blocks_needed)
  214. if _local_region == nil {
  215. return nil
  216. }
  217. }
  218. defer sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
  219. // At this point we have a usable region. Let's find the user some memory
  220. idx: u16
  221. local_region_idx := _region_get_local_idx()
  222. back_idx := -1
  223. infinite: for {
  224. for i := 0; i < int(_local_region.hdr.free_list_len); i += 1 {
  225. idx = _local_region.hdr.free_list[i]
  226. #no_bounds_check if _get_block_count(_local_region.memory[idx]) >= blocks_needed {
  227. break infinite
  228. }
  229. }
  230. sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
  231. _local_region, back_idx = _region_retrieve_with_space(blocks_needed, local_region_idx, back_idx)
  232. }
  233. user_ptr, used := _region_get_block(_local_region, idx, blocks_needed)
  234. sync.atomic_sub_explicit(&_local_region.hdr.free_blocks, used + 1, .Release)
  235. // If this memory was ever used before, it now needs to be zero'd.
  236. if idx < _local_region.hdr.last_used {
  237. mem.zero(user_ptr, int(used) * BLOCK_SIZE)
  238. } else {
  239. _local_region.hdr.last_used = idx + used
  240. }
  241. return user_ptr
  242. }
  243. heap_resize :: proc(old_memory: rawptr, new_size: int) -> rawptr #no_bounds_check {
  244. alloc := _get_allocation_header(old_memory)
  245. if alloc.requested & IS_DIRECT_MMAP > 0 {
  246. return _direct_mmap_resize(alloc, new_size)
  247. }
  248. if new_size > DIRECT_MMAP_THRESHOLD {
  249. return _direct_mmap_from_region(alloc, new_size)
  250. }
  251. return _region_resize(alloc, new_size)
  252. }
  253. heap_free :: proc(memory: rawptr) {
  254. alloc := _get_allocation_header(memory)
  255. if sync.atomic_load(&alloc.requested) & IS_DIRECT_MMAP == IS_DIRECT_MMAP {
  256. _direct_mmap_free(alloc)
  257. return
  258. }
  259. assert(alloc.free_idx == NOT_FREE)
  260. _region_find_and_assign_local(alloc)
  261. _region_local_free(alloc)
  262. sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
  263. }
  264. //
  265. // Regions
  266. //
  267. _new_region :: proc() -> ^Region #no_bounds_check {
  268. ptr, errno := linux.mmap(0, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0)
  269. if errno != .NONE {
  270. return nil
  271. }
  272. new_region := (^Region)(ptr)
  273. new_region.hdr.local_addr = CURRENTLY_ACTIVE
  274. new_region.hdr.reset_addr = &_local_region
  275. free_list_blocks := _round_up_to_nearest(FREE_LIST_DEFAULT_CAP, FREE_LIST_ENTRIES_PER_BLOCK)
  276. _region_assign_free_list(new_region, &new_region.memory[1], u16(free_list_blocks) * FREE_LIST_ENTRIES_PER_BLOCK)
  277. // + 2 to account for free_list's allocation header
  278. first_user_block := len(new_region.hdr.free_list) / FREE_LIST_ENTRIES_PER_BLOCK + 2
  279. // first allocation header (this is a free list)
  280. new_region.memory[0].next = u16(first_user_block)
  281. new_region.memory[0].free_idx = NOT_FREE
  282. new_region.memory[first_user_block].idx = u16(first_user_block)
  283. new_region.memory[first_user_block].next = BLOCKS_PER_REGION - 1
  284. // add the first user block to the free list
  285. new_region.hdr.free_list[0] = u16(first_user_block)
  286. new_region.hdr.free_list_len = 1
  287. new_region.hdr.free_blocks = _get_block_count(new_region.memory[first_user_block]) + 1
  288. for r := sync.atomic_compare_exchange_strong(&global_regions, nil, new_region);
  289. r != nil;
  290. r = sync.atomic_compare_exchange_strong(&r.hdr.next_region, nil, new_region) {}
  291. return new_region
  292. }
  293. _region_resize :: proc(alloc: ^Allocation_Header, new_size: int, alloc_is_free_list: bool = false) -> rawptr #no_bounds_check {
  294. assert(alloc.free_idx == NOT_FREE)
  295. old_memory := mem.ptr_offset(alloc, 1)
  296. old_block_count := _get_block_count(alloc^)
  297. new_block_count := u16(
  298. max(MINIMUM_BLOCK_COUNT, _round_up_to_nearest(new_size, BLOCK_SIZE) / BLOCK_SIZE),
  299. )
  300. if new_block_count < old_block_count {
  301. if new_block_count - old_block_count >= MINIMUM_BLOCK_COUNT {
  302. _region_find_and_assign_local(alloc)
  303. _region_segment(_local_region, alloc, new_block_count, alloc.free_idx)
  304. new_block_count = _get_block_count(alloc^)
  305. sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
  306. }
  307. // need to zero anything within the new block that that lies beyond new_size
  308. extra_bytes := int(new_block_count * BLOCK_SIZE) - new_size
  309. extra_bytes_ptr := mem.ptr_offset((^u8)(alloc), new_size + BLOCK_SIZE)
  310. mem.zero(extra_bytes_ptr, extra_bytes)
  311. return old_memory
  312. }
  313. if !alloc_is_free_list {
  314. _region_find_and_assign_local(alloc)
  315. }
  316. defer if !alloc_is_free_list {
  317. sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
  318. }
  319. // First, let's see if we can grow in place.
  320. if alloc.next != BLOCKS_PER_REGION - 1 && _local_region.memory[alloc.next].free_idx != NOT_FREE {
  321. next_alloc := _local_region.memory[alloc.next]
  322. total_available := old_block_count + _get_block_count(next_alloc) + 1
  323. if total_available >= new_block_count {
  324. alloc.next = next_alloc.next
  325. _local_region.memory[alloc.next].prev = alloc.idx
  326. if total_available - new_block_count > BLOCK_SEGMENT_THRESHOLD {
  327. _region_segment(_local_region, alloc, new_block_count, next_alloc.free_idx)
  328. } else {
  329. _region_free_list_remove(_local_region, next_alloc.free_idx)
  330. }
  331. mem.zero(&_local_region.memory[next_alloc.idx], int(alloc.next - next_alloc.idx) * BLOCK_SIZE)
  332. _local_region.hdr.last_used = max(alloc.next, _local_region.hdr.last_used)
  333. _local_region.hdr.free_blocks -= (_get_block_count(alloc^) - old_block_count)
  334. if alloc_is_free_list {
  335. _region_assign_free_list(_local_region, old_memory, _get_block_count(alloc^))
  336. }
  337. return old_memory
  338. }
  339. }
  340. // If we made it this far, we need to resize, copy, zero and free.
  341. region_iter := _local_region
  342. local_region_idx := _region_get_local_idx()
  343. back_idx := -1
  344. idx: u16
  345. infinite: for {
  346. for i := 0; i < len(region_iter.hdr.free_list); i += 1 {
  347. idx = region_iter.hdr.free_list[i]
  348. if _get_block_count(region_iter.memory[idx]) >= new_block_count {
  349. break infinite
  350. }
  351. }
  352. if region_iter != _local_region {
  353. sync.atomic_store_explicit(
  354. &region_iter.hdr.local_addr,
  355. region_iter.hdr.reset_addr,
  356. .Release,
  357. )
  358. }
  359. region_iter, back_idx = _region_retrieve_with_space(new_block_count, local_region_idx, back_idx)
  360. }
  361. if region_iter != _local_region {
  362. sync.atomic_store_explicit(
  363. &region_iter.hdr.local_addr,
  364. region_iter.hdr.reset_addr,
  365. .Release,
  366. )
  367. }
  368. // copy from old memory
  369. new_memory, used_blocks := _region_get_block(region_iter, idx, new_block_count)
  370. mem.copy(new_memory, old_memory, int(old_block_count * BLOCK_SIZE))
  371. // zero any new memory
  372. addon_section := mem.ptr_offset((^Allocation_Header)(new_memory), old_block_count)
  373. new_blocks := used_blocks - old_block_count
  374. mem.zero(addon_section, int(new_blocks) * BLOCK_SIZE)
  375. region_iter.hdr.free_blocks -= (used_blocks + 1)
  376. // Set free_list before freeing.
  377. if alloc_is_free_list {
  378. _region_assign_free_list(_local_region, new_memory, used_blocks)
  379. }
  380. // free old memory
  381. _region_local_free(alloc)
  382. return new_memory
  383. }
  384. _region_local_free :: proc(alloc: ^Allocation_Header) #no_bounds_check {
  385. alloc := alloc
  386. add_to_free_list := true
  387. idx := sync.atomic_load(&alloc.idx)
  388. prev := sync.atomic_load(&alloc.prev)
  389. next := sync.atomic_load(&alloc.next)
  390. block_count := next - idx - 1
  391. free_blocks := sync.atomic_load(&_local_region.hdr.free_blocks) + block_count + 1
  392. sync.atomic_store_explicit(&_local_region.hdr.free_blocks, free_blocks, .Release)
  393. // try to merge with prev
  394. if idx > 0 && sync.atomic_load(&_local_region.memory[prev].free_idx) != NOT_FREE {
  395. sync.atomic_store_explicit(&_local_region.memory[prev].next, next, .Release)
  396. _local_region.memory[next].prev = prev
  397. alloc = &_local_region.memory[prev]
  398. add_to_free_list = false
  399. }
  400. // try to merge with next
  401. if next < BLOCKS_PER_REGION - 1 && sync.atomic_load(&_local_region.memory[next].free_idx) != NOT_FREE {
  402. old_next := next
  403. sync.atomic_store_explicit(&alloc.next, sync.atomic_load(&_local_region.memory[old_next].next), .Release)
  404. sync.atomic_store_explicit(&_local_region.memory[next].prev, idx, .Release)
  405. if add_to_free_list {
  406. sync.atomic_store_explicit(&_local_region.hdr.free_list[_local_region.memory[old_next].free_idx], idx, .Release)
  407. sync.atomic_store_explicit(&alloc.free_idx, _local_region.memory[old_next].free_idx, .Release)
  408. } else {
  409. // NOTE: We have aleady merged with prev, and now merged with next.
  410. // Now, we are actually going to remove from the free_list.
  411. _region_free_list_remove(_local_region, _local_region.memory[old_next].free_idx)
  412. }
  413. add_to_free_list = false
  414. }
  415. // This is the only place where anything is appended to the free list.
  416. if add_to_free_list {
  417. fl := _local_region.hdr.free_list
  418. fl_len := sync.atomic_load(&_local_region.hdr.free_list_len)
  419. sync.atomic_store_explicit(&alloc.free_idx, fl_len, .Release)
  420. fl[alloc.free_idx] = idx
  421. sync.atomic_store_explicit(&_local_region.hdr.free_list_len, fl_len + 1, .Release)
  422. if int(fl_len + 1) == len(fl) {
  423. free_alloc := _get_allocation_header(mem.raw_data(_local_region.hdr.free_list))
  424. _region_resize(free_alloc, len(fl) * 2 * size_of(fl[0]), true)
  425. }
  426. }
  427. }
  428. _region_assign_free_list :: proc(region: ^Region, memory: rawptr, blocks: u16) {
  429. raw_free_list := transmute(mem.Raw_Slice)region.hdr.free_list
  430. raw_free_list.len = int(blocks) * FREE_LIST_ENTRIES_PER_BLOCK
  431. raw_free_list.data = memory
  432. region.hdr.free_list = transmute([]u16)(raw_free_list)
  433. }
  434. _region_retrieve_with_space :: proc(blocks: u16, local_idx: int = -1, back_idx: int = -1) -> (^Region, int) {
  435. r: ^Region
  436. idx: int
  437. for r = sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region {
  438. if idx == local_idx || idx < back_idx || sync.atomic_load(&r.hdr.free_blocks) < blocks {
  439. idx += 1
  440. continue
  441. }
  442. idx += 1
  443. local_addr: ^^Region = sync.atomic_load(&r.hdr.local_addr)
  444. if local_addr != CURRENTLY_ACTIVE {
  445. res := sync.atomic_compare_exchange_strong_explicit(
  446. &r.hdr.local_addr,
  447. local_addr,
  448. CURRENTLY_ACTIVE,
  449. .Acquire,
  450. .Relaxed,
  451. )
  452. if res == local_addr {
  453. r.hdr.reset_addr = local_addr
  454. return r, idx
  455. }
  456. }
  457. }
  458. return _new_region(), idx
  459. }
  460. _region_retrieve_from_addr :: proc(addr: rawptr) -> ^Region {
  461. r: ^Region
  462. for r = global_regions; r != nil; r = r.hdr.next_region {
  463. if _region_contains_mem(r, addr) {
  464. return r
  465. }
  466. }
  467. unreachable()
  468. }
  469. _region_get_block :: proc(region: ^Region, idx, blocks_needed: u16) -> (rawptr, u16) #no_bounds_check {
  470. alloc := &region.memory[idx]
  471. assert(alloc.free_idx != NOT_FREE)
  472. assert(alloc.next > 0)
  473. block_count := _get_block_count(alloc^)
  474. if block_count - blocks_needed > BLOCK_SEGMENT_THRESHOLD {
  475. _region_segment(region, alloc, blocks_needed, alloc.free_idx)
  476. } else {
  477. _region_free_list_remove(region, alloc.free_idx)
  478. }
  479. alloc.free_idx = NOT_FREE
  480. return mem.ptr_offset(alloc, 1), _get_block_count(alloc^)
  481. }
  482. _region_segment :: proc(region: ^Region, alloc: ^Allocation_Header, blocks, new_free_idx: u16) #no_bounds_check {
  483. old_next := alloc.next
  484. alloc.next = alloc.idx + blocks + 1
  485. region.memory[old_next].prev = alloc.next
  486. // Initialize alloc.next allocation header here.
  487. region.memory[alloc.next].prev = alloc.idx
  488. region.memory[alloc.next].next = old_next
  489. region.memory[alloc.next].idx = alloc.next
  490. region.memory[alloc.next].free_idx = new_free_idx
  491. // Replace our original spot in the free_list with new segment.
  492. region.hdr.free_list[new_free_idx] = alloc.next
  493. }
  494. _region_get_local_idx :: proc() -> int {
  495. idx: int
  496. for r := sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region {
  497. if r == _local_region {
  498. return idx
  499. }
  500. idx += 1
  501. }
  502. return -1
  503. }
  504. _region_find_and_assign_local :: proc(alloc: ^Allocation_Header) {
  505. // Find the region that contains this memory
  506. if !_region_contains_mem(_local_region, alloc) {
  507. _local_region = _region_retrieve_from_addr(alloc)
  508. }
  509. // At this point, _local_region is set correctly. Spin until acquire
  510. res := CURRENTLY_ACTIVE
  511. for res == CURRENTLY_ACTIVE {
  512. res = sync.atomic_compare_exchange_strong_explicit(
  513. &_local_region.hdr.local_addr,
  514. &_local_region,
  515. CURRENTLY_ACTIVE,
  516. .Acquire,
  517. .Relaxed,
  518. )
  519. }
  520. }
  521. _region_contains_mem :: proc(r: ^Region, memory: rawptr) -> bool #no_bounds_check {
  522. if r == nil {
  523. return false
  524. }
  525. mem_int := uintptr(memory)
  526. return mem_int >= uintptr(&r.memory[0]) && mem_int <= uintptr(&r.memory[BLOCKS_PER_REGION - 1])
  527. }
  528. _region_free_list_remove :: proc(region: ^Region, free_idx: u16) #no_bounds_check {
  529. // pop, swap and update allocation hdr
  530. if n := region.hdr.free_list_len - 1; free_idx != n {
  531. region.hdr.free_list[free_idx] = sync.atomic_load(&region.hdr.free_list[n])
  532. alloc_idx := region.hdr.free_list[free_idx]
  533. sync.atomic_store_explicit(&region.memory[alloc_idx].free_idx, free_idx, .Release)
  534. }
  535. region.hdr.free_list_len -= 1
  536. }
  537. //
  538. // Direct mmap
  539. //
  540. _direct_mmap_alloc :: proc(size: int) -> rawptr {
  541. mmap_size := _round_up_to_nearest(size + BLOCK_SIZE, PAGE_SIZE)
  542. new_allocation, errno := linux.mmap(0, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0)
  543. if errno != .NONE {
  544. return nil
  545. }
  546. alloc := (^Allocation_Header)(uintptr(new_allocation))
  547. alloc.requested = u64(size) // NOTE: requested = requested size
  548. alloc.requested += IS_DIRECT_MMAP
  549. return rawptr(mem.ptr_offset(alloc, 1))
  550. }
  551. _direct_mmap_resize :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr {
  552. old_requested := int(alloc.requested & REQUESTED_MASK)
  553. old_mmap_size := _round_up_to_nearest(old_requested + BLOCK_SIZE, PAGE_SIZE)
  554. new_mmap_size := _round_up_to_nearest(new_size + BLOCK_SIZE, PAGE_SIZE)
  555. if int(new_mmap_size) < MMAP_TO_REGION_SHRINK_THRESHOLD {
  556. return _direct_mmap_to_region(alloc, new_size)
  557. } else if old_requested == new_size {
  558. return mem.ptr_offset(alloc, 1)
  559. }
  560. new_allocation, errno := linux.mremap(alloc, uint(old_mmap_size), uint(new_mmap_size), {.MAYMOVE})
  561. if errno != .NONE {
  562. return nil
  563. }
  564. new_header := (^Allocation_Header)(uintptr(new_allocation))
  565. new_header.requested = u64(new_size)
  566. new_header.requested += IS_DIRECT_MMAP
  567. if new_mmap_size > old_mmap_size {
  568. // new section may not be pointer aligned, so cast to ^u8
  569. new_section := mem.ptr_offset((^u8)(new_header), old_requested + BLOCK_SIZE)
  570. mem.zero(new_section, new_mmap_size - old_mmap_size)
  571. }
  572. return mem.ptr_offset(new_header, 1)
  573. }
  574. _direct_mmap_from_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr {
  575. new_memory := _direct_mmap_alloc(new_size)
  576. if new_memory != nil {
  577. old_memory := mem.ptr_offset(alloc, 1)
  578. mem.copy(new_memory, old_memory, int(_get_block_count(alloc^)) * BLOCK_SIZE)
  579. }
  580. _region_find_and_assign_local(alloc)
  581. _region_local_free(alloc)
  582. sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
  583. return new_memory
  584. }
  585. _direct_mmap_to_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr {
  586. new_memory := heap_alloc(new_size)
  587. if new_memory != nil {
  588. mem.copy(new_memory, mem.ptr_offset(alloc, -1), new_size)
  589. _direct_mmap_free(alloc)
  590. }
  591. return new_memory
  592. }
  593. _direct_mmap_free :: proc(alloc: ^Allocation_Header) {
  594. requested := int(alloc.requested & REQUESTED_MASK)
  595. mmap_size := _round_up_to_nearest(requested + BLOCK_SIZE, PAGE_SIZE)
  596. linux.munmap(alloc, uint(mmap_size))
  597. }
  598. //
  599. // Util
  600. //
  601. _get_block_count :: #force_inline proc(alloc: Allocation_Header) -> u16 {
  602. return alloc.next - alloc.idx - 1
  603. }
  604. _get_allocation_header :: #force_inline proc(raw_mem: rawptr) -> ^Allocation_Header {
  605. return mem.ptr_offset((^Allocation_Header)(raw_mem), -1)
  606. }
  607. _round_up_to_nearest :: #force_inline proc(size, round: int) -> int {
  608. return (size-1) + round - (size-1) % round
  609. }