wasm_allocator.odin 30 KB

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