wasm_allocator.odin 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. #+build wasm32, wasm64p32
  2. package runtime
  3. import "base:intrinsics"
  4. /*
  5. Port of emmalloc, modified for use in Odin.
  6. Invariants:
  7. - Per-allocation header overhead is 8 bytes, smallest allocated payload
  8. amount is 8 bytes, and a multiple of 4 bytes.
  9. - Acquired memory blocks are subdivided into disjoint regions that lie
  10. next to each other.
  11. - A region is either in used or free.
  12. Used regions may be adjacent, and a used and unused region
  13. may be adjacent, but not two unused ones - they would be
  14. merged.
  15. - Memory allocation takes constant time, unless the alloc needs to wasm_memory_grow()
  16. or memory is very close to being exhausted.
  17. - Free and used regions are managed inside "root regions", which are slabs
  18. of memory acquired via wasm_memory_grow().
  19. - Memory retrieved using wasm_memory_grow() can not be given back to the OS.
  20. Therefore, frees are internal to the allocator.
  21. Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
  22. Permission is hereby granted, free of charge, to any person obtaining a copy
  23. of this software and associated documentation files (the "Software"), to deal
  24. in the Software without restriction, including without limitation the rights
  25. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  26. copies of the Software, and to permit persons to whom the Software is
  27. furnished to do so, subject to the following conditions:
  28. The above copyright notice and this permission notice shall be included in
  29. all copies or substantial portions of the Software.
  30. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  31. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  32. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  33. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  34. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  35. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  36. THE SOFTWARE.
  37. */
  38. WASM_Allocator :: struct #no_copy {
  39. // The minimum alignment of allocations.
  40. alignment: uint,
  41. // A region that contains as payload a single forward linked list of pointers to
  42. // root regions of each disjoint region blocks.
  43. list_of_all_regions: ^Root_Region,
  44. // For each of the buckets, maintain a linked list head node. The head node for each
  45. // free region is a sentinel node that does not actually represent any free space, but
  46. // the sentinel is used to avoid awkward testing against (if node == freeRegionHeadNode)
  47. // when adding and removing elements from the linked list, i.e. we are guaranteed that
  48. // the sentinel node is always fixed and there, and the actual free region list elements
  49. // start at free_region_buckets[i].next each.
  50. free_region_buckets: [NUM_FREE_BUCKETS]Region,
  51. // A bitmask that tracks the population status for each of the 64 distinct memory regions:
  52. // a zero at bit position i means that the free list bucket i is empty. This bitmask is
  53. // used to avoid redundant scanning of the 64 different free region buckets: instead by
  54. // looking at the bitmask we can find in constant time an index to a free region bucket
  55. // that contains free memory of desired size.
  56. free_region_buckets_used: BUCKET_BITMASK_T,
  57. // Because wasm memory can only be allocated in pages of 64k at a time, we keep any
  58. // spilled/unused bytes that are left from the allocated pages here, first using this
  59. // when bytes are needed.
  60. spill: []byte,
  61. // Mutex for thread safety, only used if the target feature "atomics" is enabled.
  62. mu: Mutex_State,
  63. }
  64. // Not required to be called, called on first allocation otherwise.
  65. wasm_allocator_init :: proc(a: ^WASM_Allocator, alignment: uint = 8) {
  66. assert(is_power_of_two(alignment), "alignment must be a power of two")
  67. assert(alignment > 4, "alignment must be more than 4")
  68. a.alignment = alignment
  69. for i in 0..<NUM_FREE_BUCKETS {
  70. a.free_region_buckets[i].next = &a.free_region_buckets[i]
  71. a.free_region_buckets[i].prev = a.free_region_buckets[i].next
  72. }
  73. if !claim_more_memory(a, 3*size_of(Region)) {
  74. panic("wasm_allocator: initial memory could not be allocated")
  75. }
  76. }
  77. global_default_wasm_allocator_data: WASM_Allocator
  78. default_wasm_allocator :: proc() -> Allocator {
  79. return wasm_allocator(&global_default_wasm_allocator_data)
  80. }
  81. wasm_allocator :: proc(a: ^WASM_Allocator) -> Allocator {
  82. return {
  83. data = a,
  84. procedure = wasm_allocator_proc,
  85. }
  86. }
  87. wasm_allocator_proc :: proc(a: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
  88. a := (^WASM_Allocator)(a)
  89. if a == nil {
  90. a = &global_default_wasm_allocator_data
  91. }
  92. if a.alignment == 0 {
  93. wasm_allocator_init(a)
  94. }
  95. switch mode {
  96. case .Alloc:
  97. ptr := aligned_alloc(a, uint(alignment), uint(size), loc)
  98. if ptr == nil {
  99. return nil, .Out_Of_Memory
  100. }
  101. intrinsics.mem_zero(ptr, size)
  102. return ([^]byte)(ptr)[:size], nil
  103. case .Alloc_Non_Zeroed:
  104. ptr := aligned_alloc(a, uint(alignment), uint(size), loc)
  105. if ptr == nil {
  106. return nil, .Out_Of_Memory
  107. }
  108. return ([^]byte)(ptr)[:size], nil
  109. case .Resize:
  110. ptr := aligned_realloc(a, old_memory, uint(alignment), uint(size), loc)
  111. if ptr == nil {
  112. return nil, .Out_Of_Memory
  113. }
  114. bytes := ([^]byte)(ptr)[:size]
  115. if size > old_size {
  116. new_region := raw_data(bytes[old_size:])
  117. intrinsics.mem_zero(new_region, size - old_size)
  118. }
  119. return bytes, nil
  120. case .Resize_Non_Zeroed:
  121. ptr := aligned_realloc(a, old_memory, uint(alignment), uint(size), loc)
  122. if ptr == nil {
  123. return nil, .Out_Of_Memory
  124. }
  125. return ([^]byte)(ptr)[:size], nil
  126. case .Free:
  127. free(a, old_memory, loc)
  128. return nil, nil
  129. case .Free_All, .Query_Info:
  130. return nil, .Mode_Not_Implemented
  131. case .Query_Features:
  132. set := (^Allocator_Mode_Set)(old_memory)
  133. if set != nil {
  134. set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Resize_Non_Zeroed, .Query_Features }
  135. }
  136. return nil, nil
  137. }
  138. unreachable()
  139. }
  140. // Returns the allocated size of the allocator (both free and used).
  141. // If `nil` is given, the global allocator is used.
  142. wasm_allocator_size :: proc(a: ^WASM_Allocator = nil) -> (size: uint) {
  143. a := a
  144. if a == nil {
  145. a = &global_default_wasm_allocator_data
  146. }
  147. lock(a)
  148. defer unlock(a)
  149. root := a.list_of_all_regions
  150. for root != nil {
  151. size += uint(uintptr(root.end_ptr) - uintptr(root))
  152. root = root.next
  153. }
  154. size += len(a.spill)
  155. return
  156. }
  157. // Returns the amount of free memory on the allocator.
  158. // If `nil` is given, the global allocator is used.
  159. wasm_allocator_free_space :: proc(a: ^WASM_Allocator = nil) -> (free: uint) {
  160. a := a
  161. if a == nil {
  162. a = &global_default_wasm_allocator_data
  163. }
  164. lock(a)
  165. defer unlock(a)
  166. bucket_index: u64 = 0
  167. bucket_mask := a.free_region_buckets_used
  168. for bucket_mask != 0 {
  169. index_add := intrinsics.count_trailing_zeros(bucket_mask)
  170. bucket_index += index_add
  171. bucket_mask >>= index_add
  172. for free_region := a.free_region_buckets[bucket_index].next; free_region != &a.free_region_buckets[bucket_index]; free_region = free_region.next {
  173. free += free_region.size - REGION_HEADER_SIZE
  174. }
  175. bucket_index += 1
  176. bucket_mask >>= 1
  177. }
  178. free += len(a.spill)
  179. return
  180. }
  181. @(private="file")
  182. NUM_FREE_BUCKETS :: 64
  183. @(private="file")
  184. BUCKET_BITMASK_T :: u64
  185. // Dynamic memory is subdivided into regions, in the format
  186. // <size:u32> ..... <size:u32> | <size:u32> ..... <size:u32> | <size:u32> ..... <size:u32> | .....
  187. // That is, at the bottom and top end of each memory region, the size of that region is stored. That allows traversing the
  188. // memory regions backwards and forwards. Because each allocation must be at least a multiple of 4 bytes, the lowest two bits of
  189. // each size field is unused. Free regions are distinguished by used regions by having the FREE_REGION_FLAG bit present
  190. // in the size field. I.e. for free regions, the size field is odd, and for used regions, the size field reads even.
  191. @(private="file")
  192. FREE_REGION_FLAG :: 0x1
  193. // Attempts to alloc more than this many bytes would cause an overflow when calculating the size of a region,
  194. // therefore allocations larger than this are short-circuited immediately on entry.
  195. @(private="file")
  196. MAX_ALLOC_SIZE :: 0xFFFFFFC7
  197. // A free region has the following structure:
  198. // <size:uint> <prevptr> <nextptr> ... <size:uint>
  199. @(private="file")
  200. Region :: struct {
  201. size: uint,
  202. prev, next: ^Region,
  203. _at_the_end_of_this_struct_size: uint,
  204. }
  205. // Each memory block starts with a Root_Region at the beginning.
  206. // The Root_Region specifies the size of the region block, and forms a linked
  207. // list of all Root_Regions in the program, starting with `list_of_all_regions`
  208. // below.
  209. @(private="file")
  210. Root_Region :: struct {
  211. size: u32,
  212. next: ^Root_Region,
  213. end_ptr: ^byte,
  214. }
  215. @(private="file")
  216. Mutex_State :: enum u32 {
  217. Unlocked = 0,
  218. Locked = 1,
  219. Waiting = 2,
  220. }
  221. @(private="file")
  222. lock :: proc(a: ^WASM_Allocator) {
  223. when intrinsics.has_target_feature("atomics") {
  224. @(cold)
  225. lock_slow :: proc(a: ^WASM_Allocator, curr_state: Mutex_State) {
  226. new_state := curr_state // Make a copy of it
  227. spin_lock: for spin in 0..<i32(100) {
  228. state, ok := intrinsics.atomic_compare_exchange_weak_explicit(&a.mu, .Unlocked, new_state, .Acquire, .Consume)
  229. if ok {
  230. return
  231. }
  232. if state == .Waiting {
  233. break spin_lock
  234. }
  235. for i := min(spin+1, 32); i > 0; i -= 1 {
  236. intrinsics.cpu_relax()
  237. }
  238. }
  239. // Set just in case 100 iterations did not do it
  240. new_state = .Waiting
  241. for {
  242. if intrinsics.atomic_exchange_explicit(&a.mu, .Waiting, .Acquire) == .Unlocked {
  243. return
  244. }
  245. ret := intrinsics.wasm_memory_atomic_wait32((^u32)(&a.mu), u32(new_state), -1)
  246. assert(ret != 0)
  247. intrinsics.cpu_relax()
  248. }
  249. }
  250. if v := intrinsics.atomic_exchange_explicit(&a.mu, .Locked, .Acquire); v != .Unlocked {
  251. lock_slow(a, v)
  252. }
  253. }
  254. }
  255. @(private="file")
  256. unlock :: proc(a: ^WASM_Allocator) {
  257. when intrinsics.has_target_feature("atomics") {
  258. @(cold)
  259. unlock_slow :: proc(a: ^WASM_Allocator) {
  260. for {
  261. s := intrinsics.wasm_memory_atomic_notify32((^u32)(&a.mu), 1)
  262. if s >= 1 {
  263. return
  264. }
  265. }
  266. }
  267. switch intrinsics.atomic_exchange_explicit(&a.mu, .Unlocked, .Release) {
  268. case .Unlocked:
  269. unreachable()
  270. case .Locked:
  271. // Okay
  272. case .Waiting:
  273. unlock_slow(a)
  274. }
  275. }
  276. }
  277. @(private="file")
  278. assert_locked :: proc(a: ^WASM_Allocator) {
  279. when intrinsics.has_target_feature("atomics") {
  280. assert(intrinsics.atomic_load(&a.mu) != .Unlocked)
  281. }
  282. }
  283. @(private="file")
  284. has_alignment_uintptr :: proc(ptr: uintptr, #any_int alignment: uintptr) -> bool {
  285. return ptr & (alignment-1) == 0
  286. }
  287. @(private="file")
  288. has_alignment_uint :: proc(ptr: uint, alignment: uint) -> bool {
  289. return ptr & (alignment-1) == 0
  290. }
  291. @(private="file")
  292. has_alignment :: proc {
  293. has_alignment_uintptr,
  294. has_alignment_uint,
  295. }
  296. @(private="file")
  297. REGION_HEADER_SIZE :: 2*size_of(uint)
  298. @(private="file")
  299. SMALLEST_ALLOCATION_SIZE :: 2*size_of(rawptr)
  300. // Subdivide regions of free space into distinct circular doubly linked lists, where each linked list
  301. // represents a range of free space blocks. The following function compute_free_list_bucket() converts
  302. // an allocation size to the bucket index that should be looked at.
  303. #assert(NUM_FREE_BUCKETS == 64, "Following function is tailored specifically for the NUM_FREE_BUCKETS == 64 case")
  304. @(private="file")
  305. compute_free_list_bucket :: proc(size: uint) -> uint {
  306. if size < 128 { return (size >> 3) - 1 }
  307. clz := intrinsics.count_leading_zeros(i32(size))
  308. bucket_index: i32 = ((clz > 19) \
  309. ? 110 - (clz<<2) + ((i32)(size >> (u32)(29-clz)) ~ 4) \
  310. : min( 71 - (clz<<1) + ((i32)(size >> (u32)(30-clz)) ~ 2), NUM_FREE_BUCKETS-1))
  311. assert(bucket_index >= 0)
  312. assert(bucket_index < NUM_FREE_BUCKETS)
  313. return uint(bucket_index)
  314. }
  315. @(private="file")
  316. prev_region :: proc(region: ^Region) -> ^Region {
  317. prev_region_size := ([^]uint)(region)[-1]
  318. prev_region_size = prev_region_size & ~uint(FREE_REGION_FLAG)
  319. return (^Region)(uintptr(region)-uintptr(prev_region_size))
  320. }
  321. @(private="file")
  322. next_region :: proc(region: ^Region) -> ^Region {
  323. return (^Region)(uintptr(region)+uintptr(region.size))
  324. }
  325. @(private="file")
  326. region_ceiling_size :: proc(region: ^Region) -> uint {
  327. return ([^]uint)(uintptr(region)+uintptr(region.size))[-1]
  328. }
  329. @(private="file")
  330. region_is_free :: proc(r: ^Region) -> bool {
  331. return region_ceiling_size(r) & FREE_REGION_FLAG >= 1
  332. }
  333. @(private="file")
  334. region_is_in_use :: proc(r: ^Region) -> bool {
  335. return r.size == region_ceiling_size(r)
  336. }
  337. @(private="file")
  338. region_payload_start_ptr :: proc(r: ^Region) -> [^]byte {
  339. return ([^]byte)(r)[size_of(uint):]
  340. }
  341. @(private="file")
  342. region_payload_end_ptr :: proc(r: ^Region) -> [^]byte {
  343. return ([^]byte)(r)[r.size-size_of(uint):]
  344. }
  345. @(private="file")
  346. create_used_region :: proc(ptr: rawptr, size: uint) {
  347. assert(has_alignment(uintptr(ptr), size_of(uint)))
  348. assert(has_alignment(size, size_of(uint)))
  349. assert(size >= size_of(Region))
  350. uptr := ([^]uint)(ptr)
  351. uptr[0] = size
  352. uptr[size/size_of(uint)-1] = size
  353. }
  354. @(private="file")
  355. create_free_region :: proc(ptr: rawptr, size: uint) {
  356. assert(has_alignment(uintptr(ptr), size_of(uint)))
  357. assert(has_alignment(size, size_of(uint)))
  358. assert(size >= size_of(Region))
  359. free_region := (^Region)(ptr)
  360. free_region.size = size
  361. ([^]uint)(ptr)[size/size_of(uint)-1] = size | FREE_REGION_FLAG
  362. }
  363. @(private="file")
  364. prepend_to_free_list :: proc(region: ^Region, prepend_to: ^Region) {
  365. assert(region_is_free(region))
  366. region.next = prepend_to
  367. region.prev = prepend_to.prev
  368. prepend_to.prev = region
  369. region.prev.next = region
  370. }
  371. @(private="file")
  372. unlink_from_free_list :: proc(region: ^Region) {
  373. assert(region_is_free(region))
  374. region.prev.next = region.next
  375. region.next.prev = region.prev
  376. }
  377. @(private="file")
  378. link_to_free_list :: proc(a: ^WASM_Allocator, free_region: ^Region) {
  379. assert(free_region.size >= size_of(Region))
  380. bucket_index := compute_free_list_bucket(free_region.size-REGION_HEADER_SIZE)
  381. free_list_head := &a.free_region_buckets[bucket_index]
  382. free_region.prev = free_list_head
  383. free_region.next = free_list_head.next
  384. free_list_head.next = free_region
  385. free_region.next.prev = free_region
  386. a.free_region_buckets_used |= BUCKET_BITMASK_T(1) << bucket_index
  387. }
  388. @(private="file")
  389. claim_more_memory :: proc(a: ^WASM_Allocator, num_bytes: uint) -> bool {
  390. PAGE_SIZE :: 64 * 1024
  391. page_alloc :: proc(page_count: int) -> []byte {
  392. prev_page_count := intrinsics.wasm_memory_grow(0, uintptr(page_count))
  393. if prev_page_count < 0 { return nil }
  394. ptr := ([^]byte)(uintptr(prev_page_count) * PAGE_SIZE)
  395. return ptr[:page_count * PAGE_SIZE]
  396. }
  397. alloc :: proc(a: ^WASM_Allocator, num_bytes: uint) -> (bytes: [^]byte) #no_bounds_check {
  398. if uint(len(a.spill)) >= num_bytes {
  399. bytes = raw_data(a.spill[:num_bytes])
  400. a.spill = a.spill[num_bytes:]
  401. return
  402. }
  403. pages := int((num_bytes / PAGE_SIZE) + 1)
  404. allocated := page_alloc(pages)
  405. if allocated == nil { return nil }
  406. // If the allocated memory is a direct continuation of the spill from before,
  407. // we can just extend the spill.
  408. spill_end := uintptr(raw_data(a.spill)) + uintptr(len(a.spill))
  409. if spill_end == uintptr(raw_data(allocated)) {
  410. raw_spill := (^Raw_Slice)(&a.spill)
  411. raw_spill.len += len(allocated)
  412. } else {
  413. // Otherwise, we have to "waste" the previous spill.
  414. // Now this is probably uncommon, and will only happen if another code path
  415. // is also requesting pages.
  416. a.spill = allocated
  417. }
  418. bytes = raw_data(a.spill)
  419. a.spill = a.spill[num_bytes:]
  420. return
  421. }
  422. num_bytes := num_bytes
  423. num_bytes = align_forward(num_bytes, a.alignment)
  424. start_ptr := alloc(a, uint(num_bytes))
  425. if start_ptr == nil { return false }
  426. assert(has_alignment(uintptr(start_ptr), align_of(uint)))
  427. end_ptr := start_ptr[num_bytes:]
  428. end_sentinel_region := (^Region)(end_ptr[-size_of(Region):])
  429. create_used_region(end_sentinel_region, size_of(Region))
  430. // If we are the sole user of wasm_memory_grow(), it will feed us continuous/consecutive memory addresses - take advantage
  431. // of that if so: instead of creating two disjoint memory regions blocks, expand the previous one to a larger size.
  432. prev_alloc_end_address := a.list_of_all_regions != nil ? a.list_of_all_regions.end_ptr : nil
  433. if start_ptr == prev_alloc_end_address {
  434. prev_end_sentinel := prev_region((^Region)(start_ptr))
  435. assert(region_is_in_use(prev_end_sentinel))
  436. prev_region := prev_region(prev_end_sentinel)
  437. a.list_of_all_regions.end_ptr = end_ptr
  438. // Two scenarios, either the last region of the previous block was in use, in which case we need to create
  439. // a new free region in the newly allocated space; or it was free, in which case we can extend that region
  440. // to cover a larger size.
  441. if region_is_free(prev_region) {
  442. new_free_region_size := uint(uintptr(end_sentinel_region) - uintptr(prev_region))
  443. unlink_from_free_list(prev_region)
  444. create_free_region(prev_region, new_free_region_size)
  445. link_to_free_list(a, prev_region)
  446. return true
  447. }
  448. start_ptr = start_ptr[-size_of(Region):]
  449. } else {
  450. create_used_region(start_ptr, size_of(Region))
  451. new_region_block := (^Root_Region)(start_ptr)
  452. new_region_block.next = a.list_of_all_regions
  453. new_region_block.end_ptr = end_ptr
  454. a.list_of_all_regions = new_region_block
  455. start_ptr = start_ptr[size_of(Region):]
  456. }
  457. create_free_region(start_ptr, uint(uintptr(end_sentinel_region)-uintptr(start_ptr)))
  458. link_to_free_list(a, (^Region)(start_ptr))
  459. return true
  460. }
  461. @(private="file")
  462. validate_alloc_size :: proc(size: uint) -> uint {
  463. #assert(size_of(uint) >= size_of(uintptr))
  464. #assert(size_of(uint) % size_of(uintptr) == 0)
  465. // NOTE: emmalloc aligns this forward on pointer size, but I think that is a mistake and will
  466. // do bad on wasm64p32.
  467. validated_size := size > SMALLEST_ALLOCATION_SIZE ? align_forward(size, size_of(uint)) : SMALLEST_ALLOCATION_SIZE
  468. assert(validated_size >= size) // Assert we haven't wrapped.
  469. return validated_size
  470. }
  471. @(private="file")
  472. allocate_memory :: proc(a: ^WASM_Allocator, alignment: uint, size: uint, loc := #caller_location) -> rawptr {
  473. attempt_allocate :: proc(a: ^WASM_Allocator, free_region: ^Region, alignment, size: uint) -> rawptr {
  474. assert_locked(a)
  475. free_region := free_region
  476. payload_start_ptr := uintptr(region_payload_start_ptr(free_region))
  477. payload_start_ptr_aligned := align_forward(payload_start_ptr, uintptr(alignment))
  478. payload_end_ptr := uintptr(region_payload_end_ptr(free_region))
  479. if payload_start_ptr_aligned + uintptr(size) > payload_end_ptr {
  480. return nil
  481. }
  482. // We have enough free space, so the memory allocation will be made into this region. Remove this free region
  483. // from the list of free regions: whatever slop remains will be later added back to the free region pool.
  484. unlink_from_free_list(free_region)
  485. // Before we proceed further, fix up the boundary between this and the preceding region,
  486. // so that the boundary between the two regions happens at a right spot for the payload to be aligned.
  487. if payload_start_ptr != payload_start_ptr_aligned {
  488. prev := prev_region(free_region)
  489. assert(region_is_in_use(prev))
  490. region_boundary_bump_amount := payload_start_ptr_aligned - payload_start_ptr
  491. new_this_region_size := free_region.size - uint(region_boundary_bump_amount)
  492. create_used_region(prev, prev.size + uint(region_boundary_bump_amount))
  493. free_region = (^Region)(uintptr(free_region) + region_boundary_bump_amount)
  494. free_region.size = new_this_region_size
  495. }
  496. // Next, we need to decide whether this region is so large that it should be split into two regions,
  497. // one representing the newly used memory area, and at the high end a remaining leftover free area.
  498. // This splitting to two is done always if there is enough space for the high end to fit a region.
  499. // Carve 'size' bytes of payload off this region. So,
  500. // [sz prev next sz]
  501. // becomes
  502. // [sz payload sz] [sz prev next sz]
  503. if size_of(Region) + REGION_HEADER_SIZE + size <= free_region.size {
  504. new_free_region := (^Region)(uintptr(free_region) + REGION_HEADER_SIZE + uintptr(size))
  505. create_free_region(new_free_region, free_region.size - size - REGION_HEADER_SIZE)
  506. link_to_free_list(a, new_free_region)
  507. create_used_region(free_region, size + REGION_HEADER_SIZE)
  508. } else {
  509. // There is not enough space to split the free memory region into used+free parts, so consume the whole
  510. // region as used memory, not leaving a free memory region behind.
  511. // Initialize the free region as used by resetting the ceiling size to the same value as the size at bottom.
  512. ([^]uint)(uintptr(free_region) + uintptr(free_region.size))[-1] = free_region.size
  513. }
  514. return rawptr(uintptr(free_region) + size_of(uint))
  515. }
  516. assert_locked(a)
  517. assert(is_power_of_two(alignment))
  518. assert(size <= MAX_ALLOC_SIZE, "allocation too big", loc=loc)
  519. alignment := alignment
  520. alignment = max(alignment, a.alignment)
  521. size := size
  522. size = validate_alloc_size(size)
  523. // Attempt to allocate memory starting from smallest bucket that can contain the required amount of memory.
  524. // Under normal alignment conditions this should always be the first or second bucket we look at, but if
  525. // performing an allocation with complex alignment, we may need to look at multiple buckets.
  526. bucket_index := compute_free_list_bucket(size)
  527. bucket_mask := a.free_region_buckets_used >> bucket_index
  528. // Loop through each bucket that has free regions in it, based on bits set in free_region_buckets_used bitmap.
  529. for bucket_mask != 0 {
  530. index_add := intrinsics.count_trailing_zeros(bucket_mask)
  531. bucket_index += uint(index_add)
  532. bucket_mask >>= index_add
  533. assert(bucket_index <= NUM_FREE_BUCKETS-1)
  534. assert(a.free_region_buckets_used & (BUCKET_BITMASK_T(1) << bucket_index) > 0)
  535. free_region := a.free_region_buckets[bucket_index].next
  536. assert(free_region != nil)
  537. if free_region != &a.free_region_buckets[bucket_index] {
  538. ptr := attempt_allocate(a, free_region, alignment, size)
  539. if ptr != nil {
  540. return ptr
  541. }
  542. // We were not able to allocate from the first region found in this bucket, so penalize
  543. // the region by cycling it to the end of the doubly circular linked list. (constant time)
  544. // This provides a randomized guarantee that when performing allocations of size k to a
  545. // bucket of [k-something, k+something] range, we will not always attempt to satisfy the
  546. // allocation from the same available region at the front of the list, but we try each
  547. // region in turn.
  548. unlink_from_free_list(free_region)
  549. prepend_to_free_list(free_region, &a.free_region_buckets[bucket_index])
  550. // But do not stick around to attempt to look at other regions in this bucket - move
  551. // to search the next populated bucket index if this did not fit. This gives a practical
  552. // "allocation in constant time" guarantee, since the next higher bucket will only have
  553. // regions that are all of strictly larger size than the requested allocation. Only if
  554. // there is a difficult alignment requirement we may fail to perform the allocation from
  555. // a region in the next bucket, and if so, we keep trying higher buckets until one of them
  556. // works.
  557. bucket_index += 1
  558. bucket_mask >>= 1
  559. } else {
  560. // This bucket was not populated after all with any regions,
  561. // but we just had a stale bit set to mark a populated bucket.
  562. // Reset the bit to update latest status so that we do not
  563. // redundantly look at this bucket again.
  564. a.free_region_buckets_used &~= BUCKET_BITMASK_T(1) << bucket_index
  565. bucket_mask ~= 1
  566. }
  567. assert((bucket_index == NUM_FREE_BUCKETS && bucket_mask == 0) || (bucket_mask == a.free_region_buckets_used >> bucket_index))
  568. }
  569. // None of the buckets were able to accommodate an allocation. If this happens we are almost out of memory.
  570. // The largest bucket might contain some suitable regions, but we only looked at one region in that bucket, so
  571. // as a last resort, loop through more free regions in the bucket that represents the largest allocations available.
  572. // But only if the bucket representing largest allocations available is not any of the first thirty buckets,
  573. // these represent allocatable areas less than <1024 bytes - which could be a lot of scrap.
  574. // In such case, prefer to claim more memory right away.
  575. largest_bucket_index := NUM_FREE_BUCKETS - 1 - intrinsics.count_leading_zeros(a.free_region_buckets_used)
  576. // free_region will be null if there is absolutely no memory left. (all buckets are 100% used)
  577. free_region := a.free_region_buckets_used > 0 ? a.free_region_buckets[largest_bucket_index].next : nil
  578. // The 30 first free region buckets cover memory blocks < 2048 bytes, so skip looking at those here (too small)
  579. if a.free_region_buckets_used >> 30 > 0 {
  580. // Look only at a constant number of regions in this bucket max, to avoid bad worst case behavior.
  581. // If this many regions cannot find free space, we give up and prefer to claim more memory instead.
  582. max_regions_to_try_before_giving_up :: 99
  583. num_tries_left := max_regions_to_try_before_giving_up
  584. for ; free_region != &a.free_region_buckets[largest_bucket_index] && num_tries_left > 0; num_tries_left -= 1 {
  585. ptr := attempt_allocate(a, free_region, alignment, size)
  586. if ptr != nil {
  587. return ptr
  588. }
  589. free_region = free_region.next
  590. }
  591. }
  592. // We were unable to find a free memory region. Must claim more memory!
  593. num_bytes_to_claim := size+size_of(Region)*3
  594. if alignment > a.alignment {
  595. num_bytes_to_claim += alignment
  596. }
  597. success := claim_more_memory(a, num_bytes_to_claim)
  598. if (success) {
  599. // Try allocate again with the newly available memory.
  600. return allocate_memory(a, alignment, size)
  601. }
  602. // also claim_more_memory failed, we are really really constrained :( As a last resort, go back to looking at the
  603. // bucket we already looked at above, continuing where the above search left off - perhaps there are
  604. // regions we overlooked the first time that might be able to satisfy the allocation.
  605. if free_region != nil {
  606. for free_region != &a.free_region_buckets[largest_bucket_index] {
  607. ptr := attempt_allocate(a, free_region, alignment, size)
  608. if ptr != nil {
  609. return ptr
  610. }
  611. free_region = free_region.next
  612. }
  613. }
  614. // Fully out of memory.
  615. return nil
  616. }
  617. @(private="file")
  618. aligned_alloc :: proc(a: ^WASM_Allocator, alignment, size: uint, loc := #caller_location) -> rawptr {
  619. lock(a)
  620. defer unlock(a)
  621. return allocate_memory(a, alignment, size, loc)
  622. }
  623. @(private="file")
  624. free :: proc(a: ^WASM_Allocator, ptr: rawptr, loc := #caller_location) {
  625. if ptr == nil {
  626. return
  627. }
  628. region_start_ptr := uintptr(ptr) - size_of(uint)
  629. region := (^Region)(region_start_ptr)
  630. assert(has_alignment(region_start_ptr, size_of(uint)))
  631. lock(a)
  632. defer unlock(a)
  633. size := region.size
  634. assert(region_is_in_use(region), "double free or corrupt region", loc=loc)
  635. prev_region_size_field := ([^]uint)(region)[-1]
  636. prev_region_size := prev_region_size_field & ~uint(FREE_REGION_FLAG)
  637. if prev_region_size_field != prev_region_size {
  638. prev_region := (^Region)(uintptr(region) - uintptr(prev_region_size))
  639. unlink_from_free_list(prev_region)
  640. region_start_ptr = uintptr(prev_region)
  641. size += prev_region_size
  642. }
  643. next_reg := next_region(region)
  644. size_at_end := (^uint)(region_payload_end_ptr(next_reg))^
  645. if next_reg.size != size_at_end {
  646. unlink_from_free_list(next_reg)
  647. size += next_reg.size
  648. }
  649. create_free_region(rawptr(region_start_ptr), size)
  650. link_to_free_list(a, (^Region)(region_start_ptr))
  651. }
  652. @(private="file")
  653. aligned_realloc :: proc(a: ^WASM_Allocator, ptr: rawptr, alignment, size: uint, loc := #caller_location) -> rawptr {
  654. attempt_region_resize :: proc(a: ^WASM_Allocator, region: ^Region, size: uint) -> bool {
  655. lock(a)
  656. defer unlock(a)
  657. // First attempt to resize this region, if the next region that follows this one
  658. // is a free region.
  659. next_reg := next_region(region)
  660. next_region_end_ptr := uintptr(next_reg) + uintptr(next_reg.size)
  661. size_at_ceiling := ([^]uint)(next_region_end_ptr)[-1]
  662. if next_reg.size != size_at_ceiling { // Next region is free?
  663. assert(region_is_free(next_reg))
  664. new_next_region_start_ptr := uintptr(region) + uintptr(size)
  665. assert(has_alignment(new_next_region_start_ptr, size_of(uint)))
  666. // Next region does not shrink to too small size?
  667. if new_next_region_start_ptr + size_of(Region) <= next_region_end_ptr {
  668. unlink_from_free_list(next_reg)
  669. create_free_region(rawptr(new_next_region_start_ptr), uint(next_region_end_ptr - new_next_region_start_ptr))
  670. link_to_free_list(a, (^Region)(new_next_region_start_ptr))
  671. create_used_region(region, uint(new_next_region_start_ptr - uintptr(region)))
  672. return true
  673. }
  674. // If we remove the next region altogether, allocation is satisfied?
  675. if new_next_region_start_ptr <= next_region_end_ptr {
  676. unlink_from_free_list(next_reg)
  677. create_used_region(region, region.size + next_reg.size)
  678. return true
  679. }
  680. } else {
  681. // Next region is an used region - we cannot change its starting address. However if we are shrinking the
  682. // size of this region, we can create a new free region between this and the next used region.
  683. if size + size_of(Region) <= region.size {
  684. free_region_size := region.size - size
  685. create_used_region(region, size)
  686. free_region := (^Region)(uintptr(region) + uintptr(size))
  687. create_free_region(free_region, free_region_size)
  688. link_to_free_list(a, free_region)
  689. return true
  690. } else if size <= region.size {
  691. // Caller was asking to shrink the size, but due to not being able to fit a full Region in the shrunk
  692. // area, we cannot actually do anything. This occurs if the shrink amount is really small. In such case,
  693. // just call it success without doing any work.
  694. return true
  695. }
  696. }
  697. return false
  698. }
  699. if ptr == nil {
  700. return aligned_alloc(a, alignment, size, loc)
  701. }
  702. if size == 0 {
  703. free(a, ptr, loc)
  704. return nil
  705. }
  706. if size > MAX_ALLOC_SIZE {
  707. return nil
  708. }
  709. assert(is_power_of_two(alignment))
  710. assert(has_alignment(uintptr(ptr), alignment), "realloc on different alignment than original allocation", loc=loc)
  711. size := size
  712. size = validate_alloc_size(size)
  713. region := (^Region)(uintptr(ptr) - size_of(uint))
  714. // Attempt an in-place resize.
  715. if attempt_region_resize(a, region, size + REGION_HEADER_SIZE) {
  716. return ptr
  717. }
  718. // Can't do it in-place, allocate new region and copy over.
  719. newptr := aligned_alloc(a, alignment, size, loc)
  720. if newptr != nil {
  721. intrinsics.mem_copy(newptr, ptr, min(size, region.size - REGION_HEADER_SIZE))
  722. free(a, ptr, loc=loc)
  723. }
  724. return newptr
  725. }