1use crate::{Error, MAX_POSSIBLE_ALLOCATION, MAX_WASM_PAGES, Memory, PAGE_SIZE};
59use sp_wasm_interface_common::{Pointer, WordSize};
60use std::{
61 cmp::{max, min},
62 mem,
63 ops::{Index, IndexMut, Range},
64};
65
66const ALIGNMENT: u32 = 8;
71
72const HEADER_SIZE: u32 = 8;
75
76fn error(msg: &'static str) -> Error {
78 Error::Other(msg)
79}
80
81const LOG_TARGET: &str = "wasm-heap";
82
83const N_ORDERS: usize = 23;
93const MIN_POSSIBLE_ALLOCATION: u32 = 8; #[derive(Copy, Clone, PartialEq, Eq, Debug)]
110struct Order(u32);
111
112impl Order {
113 fn from_raw(order: u32) -> Result<Self, Error> {
117 if order < N_ORDERS as u32 {
118 Ok(Self(order))
119 } else {
120 Err(error("invalid order"))
121 }
122 }
123
124 fn from_size(size: u32) -> Result<Self, Error> {
130 let clamped_size = if size > MAX_POSSIBLE_ALLOCATION {
131 log::warn!(target: LOG_TARGET, "going to fail due to allocating {:?}", size);
132 return Err(Error::RequestedAllocationTooLarge);
133 } else if size < MIN_POSSIBLE_ALLOCATION {
134 MIN_POSSIBLE_ALLOCATION
135 } else {
136 size
137 };
138
139 let power_of_two_size = clamped_size.next_power_of_two();
143
144 let order = power_of_two_size.trailing_zeros() - MIN_POSSIBLE_ALLOCATION.trailing_zeros();
147
148 Ok(Self(order))
149 }
150
151 fn size(&self) -> u32 {
155 MIN_POSSIBLE_ALLOCATION << self.0
156 }
157
158 fn into_raw(self) -> u32 {
160 self.0
161 }
162}
163
164const NIL_MARKER: u32 = u32::MAX;
166
167#[derive(Clone, Copy, Debug, PartialEq, Eq)]
169enum Link {
170 Nil,
172 Ptr(u32),
174}
175
176impl Link {
177 fn from_raw(raw: u32) -> Self {
179 if raw != NIL_MARKER {
180 Self::Ptr(raw)
181 } else {
182 Self::Nil
183 }
184 }
185
186 fn into_raw(self) -> u32 {
188 match self {
189 Self::Nil => NIL_MARKER,
190 Self::Ptr(ptr) => ptr,
191 }
192 }
193}
194
195#[derive(Clone, Debug, PartialEq, Eq)]
215enum Header {
216 Free(Link),
218 Occupied(Order),
221}
222
223impl Header {
224 fn read_from(memory: &impl Memory, header_ptr: u32) -> Result<Self, Error> {
229 let raw_header = memory.read_le_u64(header_ptr)?;
230
231 let occupied = raw_header & 0x00000001_00000000 != 0;
234 let header_data = raw_header as u32;
235
236 Ok(if occupied {
237 Self::Occupied(Order::from_raw(header_data)?)
238 } else {
239 Self::Free(Link::from_raw(header_data))
240 })
241 }
242
243 fn write_into(&self, memory: &mut impl Memory, header_ptr: u32) -> Result<(), Error> {
247 let (header_data, occupied_mask) = match *self {
248 Self::Occupied(order) => (order.into_raw(), 0x00000001_00000000),
249 Self::Free(link) => (link.into_raw(), 0x00000000_00000000),
250 };
251 let raw_header = header_data as u64 | occupied_mask;
252 memory.write_le_u64(header_ptr, raw_header)?;
253 Ok(())
254 }
255
256 fn into_occupied(self) -> Option<Order> {
258 match self {
259 Self::Occupied(order) => Some(order),
260 _ => None,
261 }
262 }
263
264 fn into_free(self) -> Option<Link> {
266 match self {
267 Self::Free(link) => Some(link),
268 _ => None,
269 }
270 }
271}
272
273struct FreeLists {
275 heads: [Link; N_ORDERS],
276}
277
278impl FreeLists {
279 fn new() -> Self {
281 Self {
282 heads: [Link::Nil; N_ORDERS],
283 }
284 }
285
286 fn replace(&mut self, order: Order, new: Link) -> Link {
288 let prev = self[order];
289 self[order] = new;
290 prev
291 }
292}
293
294impl Index<Order> for FreeLists {
295 type Output = Link;
296 fn index(&self, index: Order) -> &Link {
297 &self.heads[index.0 as usize]
298 }
299}
300
301impl IndexMut<Order> for FreeLists {
302 fn index_mut(&mut self, index: Order) -> &mut Link {
303 &mut self.heads[index.0 as usize]
304 }
305}
306
307#[derive(Clone, Debug, Default)]
309#[non_exhaustive]
310pub struct AllocationStats {
311 pub bytes_allocated: u32,
315
316 pub bytes_allocated_peak: u32,
320
321 pub bytes_allocated_sum: u128,
325
326 pub address_space_used: u32,
334}
335
336fn pages_from_size(size: u64) -> Option<u32> {
342 u32::try_from(size.div_ceil(PAGE_SIZE as u64)).ok()
343}
344
345pub struct FreeingBumpHeapAllocator {
349 original_heap_base: u32,
350 bumper: u32,
351 free_lists: FreeLists,
352 poisoned: bool,
353 last_observed_memory_size: u64,
354 stats: AllocationStats,
355}
356
357impl Drop for FreeingBumpHeapAllocator {
358 fn drop(&mut self) {
359 log::debug!(target: LOG_TARGET, "allocator dropped: {:?}", self.stats)
360 }
361}
362
363impl FreeingBumpHeapAllocator {
364 pub fn new(heap_base: u32) -> Self {
370 let aligned_heap_base = heap_base.div_ceil(ALIGNMENT) * ALIGNMENT;
371
372 FreeingBumpHeapAllocator {
373 original_heap_base: aligned_heap_base,
374 bumper: aligned_heap_base,
375 free_lists: FreeLists::new(),
376 poisoned: false,
377 last_observed_memory_size: 0,
378 stats: AllocationStats::default(),
379 }
380 }
381
382 pub fn allocate(
398 &mut self,
399 mem: &mut impl Memory,
400 size: WordSize,
401 ) -> Result<Pointer<u8>, Error> {
402 if self.poisoned {
403 return Err(error("the allocator has been poisoned"));
404 }
405
406 let bomb = PoisonBomb {
407 poisoned: &mut self.poisoned,
408 };
409
410 Self::observe_memory_size(&mut self.last_observed_memory_size, mem)?;
411 let order = Order::from_size(size)?;
412
413 let header_ptr: u32 = match self.free_lists[order] {
414 Link::Ptr(header_ptr) => {
415 if (u64::from(header_ptr) + u64::from(order.size()) + u64::from(HEADER_SIZE))
416 > mem.size()
417 {
418 return Err(error("Invalid header pointer detected"));
419 }
420
421 let next_free = Header::read_from(mem, header_ptr)?
423 .into_free()
424 .ok_or_else(|| error("free list points to a occupied header"))?;
425 self.free_lists[order] = next_free;
426
427 header_ptr
428 }
429 Link::Nil => {
430 Self::bump(&mut self.bumper, order.size() + HEADER_SIZE, mem)?
432 }
433 };
434
435 Header::Occupied(order).write_into(mem, header_ptr)?;
437
438 self.stats.bytes_allocated += order.size() + HEADER_SIZE;
439 self.stats.bytes_allocated_sum += u128::from(order.size() + HEADER_SIZE);
440 self.stats.bytes_allocated_peak =
441 max(self.stats.bytes_allocated_peak, self.stats.bytes_allocated);
442 self.stats.address_space_used = self.bumper - self.original_heap_base;
443
444 log::trace!(target: LOG_TARGET, "after allocation: {:?}", self.stats);
445
446 bomb.disarm();
447 Ok(Pointer::new(header_ptr + HEADER_SIZE))
448 }
449
450 pub fn deallocate(&mut self, mem: &mut impl Memory, ptr: Pointer<u8>) -> Result<(), Error> {
462 if self.poisoned {
463 return Err(error("the allocator has been poisoned"));
464 }
465
466 let bomb = PoisonBomb {
467 poisoned: &mut self.poisoned,
468 };
469
470 Self::observe_memory_size(&mut self.last_observed_memory_size, mem)?;
471
472 let header_ptr = u32::from(ptr)
473 .checked_sub(HEADER_SIZE)
474 .ok_or_else(|| error("Invalid pointer for deallocation"))?;
475
476 let order = Header::read_from(mem, header_ptr)?
477 .into_occupied()
478 .ok_or_else(|| error("the allocation points to an empty header"))?;
479
480 let prev_head = self.free_lists.replace(order, Link::Ptr(header_ptr));
482 Header::Free(prev_head).write_into(mem, header_ptr)?;
483
484 self.stats.bytes_allocated = self
485 .stats
486 .bytes_allocated
487 .checked_sub(order.size() + HEADER_SIZE)
488 .ok_or_else(|| error("underflow of the currently allocated bytes count"))?;
489
490 log::trace!("after deallocation: {:?}", self.stats);
491
492 bomb.disarm();
493 Ok(())
494 }
495
496 pub fn stats(&self) -> AllocationStats {
498 self.stats.clone()
499 }
500
501 fn bump(bumper: &mut u32, size: u32, memory: &mut impl Memory) -> Result<u32, Error> {
506 let required_size = u64::from(*bumper) + u64::from(size);
507 if required_size > u64::from(u32::MAX) {
508 return Err(Error::AllocatorOutOfSpace);
509 }
510
511 if required_size > memory.size() {
512 let required_pages =
513 pages_from_size(required_size).ok_or(Error::AllocatorOutOfSpace)?;
514
515 let current_pages = memory.pages();
516 let max_pages = memory.max_pages().unwrap_or(MAX_WASM_PAGES);
517 debug_assert!(
518 current_pages < required_pages,
519 "current pages {current_pages} < required pages {required_pages}"
520 );
521
522 if current_pages >= max_pages {
523 log::debug!(
524 target: LOG_TARGET,
525 "Wasm pages ({current_pages}) are already at the maximum.",
526 );
527
528 return Err(Error::AllocatorOutOfSpace);
529 } else if required_pages > max_pages {
530 log::debug!(
531 target: LOG_TARGET,
532 "Failed to grow memory from {current_pages} pages to at least {required_pages}\
533 pages due to the maximum limit of {max_pages} pages",
534 );
535 return Err(Error::AllocatorOutOfSpace);
536 }
537
538 let next_pages = min(current_pages * 2, max_pages);
541 let next_pages = max(next_pages, required_pages);
543
544 if memory.grow(next_pages - current_pages).is_err() {
545 log::error!(
546 target: LOG_TARGET,
547 "Failed to grow memory from {current_pages} pages to {next_pages} pages",
548 );
549
550 return Err(Error::AllocatorOutOfSpace);
551 }
552
553 debug_assert_eq!(
554 memory.pages(),
555 next_pages,
556 "Number of pages should have increased!"
557 );
558 }
559
560 let res = *bumper;
561 *bumper += size;
562 Ok(res)
563 }
564
565 fn observe_memory_size(
566 last_observed_memory_size: &mut u64,
567 mem: &mut impl Memory,
568 ) -> Result<(), Error> {
569 if mem.size() < *last_observed_memory_size {
570 return Err(Error::MemoryShrunk);
571 }
572 *last_observed_memory_size = mem.size();
573 Ok(())
574 }
575}
576
577trait MemoryExt: Memory {
585 fn read_le_u64(&self, ptr: u32) -> Result<u64, Error> {
588 self.with_access(|memory| {
589 let range =
590 heap_range(ptr, 8, memory.len()).ok_or_else(|| error("read out of heap bounds"))?;
591 let bytes = memory[range]
592 .try_into()
593 .expect("[u8] slice of length 8 must be convertible to [u8; 8]");
594 Ok(u64::from_le_bytes(bytes))
595 })
596 }
597
598 fn write_le_u64(&mut self, ptr: u32, val: u64) -> Result<(), Error> {
601 self.with_access_mut(|memory| {
602 let range = heap_range(ptr, 8, memory.len())
603 .ok_or_else(|| error("write out of heap bounds"))?;
604 let bytes = val.to_le_bytes();
605 memory[range].copy_from_slice(&bytes[..]);
606 Ok(())
607 })
608 }
609
610 fn size(&self) -> u64 {
612 debug_assert!(self.pages() <= MAX_WASM_PAGES);
613
614 self.pages() as u64 * PAGE_SIZE as u64
615 }
616}
617
618impl<T: Memory> MemoryExt for T {}
619
620fn heap_range(offset: u32, length: u32, heap_len: usize) -> Option<Range<usize>> {
621 let start = offset as usize;
622 let end = offset.checked_add(length)? as usize;
623 if end <= heap_len {
624 Some(start..end)
625 } else {
626 None
627 }
628}
629
630struct PoisonBomb<'a> {
632 poisoned: &'a mut bool,
633}
634
635impl<'a> PoisonBomb<'a> {
636 fn disarm(self) {
637 mem::forget(self)
638 }
639}
640
641impl<'a> Drop for PoisonBomb<'a> {
642 fn drop(&mut self) {
643 *self.poisoned = true;
644 }
645}
646
647#[cfg(test)]
648mod tests {
649 use super::*;
650
651 fn to_pointer(address: u32) -> Pointer<u8> {
653 Pointer::new(address)
654 }
655
656 #[derive(Debug)]
657 struct MemoryInstance {
658 data: Vec<u8>,
659 max_wasm_pages: u32,
660 }
661
662 impl MemoryInstance {
663 fn with_pages(pages: u32) -> Self {
664 Self {
665 data: vec![0; (pages * PAGE_SIZE) as usize],
666 max_wasm_pages: MAX_WASM_PAGES,
667 }
668 }
669
670 fn set_max_wasm_pages(&mut self, max_pages: u32) {
671 self.max_wasm_pages = max_pages;
672 }
673 }
674
675 impl Memory for MemoryInstance {
676 fn with_access<R>(&self, run: impl FnOnce(&[u8]) -> R) -> R {
677 run(&self.data)
678 }
679
680 fn with_access_mut<R>(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R {
681 run(&mut self.data)
682 }
683
684 fn pages(&self) -> u32 {
685 pages_from_size(self.data.len() as u64).unwrap()
686 }
687
688 fn max_pages(&self) -> Option<u32> {
689 Some(self.max_wasm_pages)
690 }
691
692 fn grow(&mut self, pages: u32) -> Result<(), ()> {
693 if self.pages() + pages > self.max_wasm_pages {
694 Err(())
695 } else {
696 self.data
697 .resize(((self.pages() + pages) * PAGE_SIZE) as usize, 0);
698 Ok(())
699 }
700 }
701 }
702
703 #[test]
704 fn test_pages_from_size() {
705 assert_eq!(pages_from_size(0).unwrap(), 0);
706 assert_eq!(pages_from_size(1).unwrap(), 1);
707 assert_eq!(pages_from_size(65536).unwrap(), 1);
708 assert_eq!(pages_from_size(65536 + 1).unwrap(), 2);
709 assert_eq!(pages_from_size(2 * 65536).unwrap(), 2);
710 assert_eq!(pages_from_size(2 * 65536 + 1).unwrap(), 3);
711 }
712
713 #[test]
714 fn should_allocate_properly() {
715 let mut mem = MemoryInstance::with_pages(1);
717 let mut heap = FreeingBumpHeapAllocator::new(0);
718
719 let ptr = heap.allocate(&mut mem, 1).unwrap();
721
722 assert_eq!(ptr, to_pointer(HEADER_SIZE));
725 }
726
727 #[test]
728 fn should_always_align_pointers_to_multiples_of_8() {
729 let mut mem = MemoryInstance::with_pages(1);
731 let mut heap = FreeingBumpHeapAllocator::new(13);
732
733 let ptr = heap.allocate(&mut mem, 1).unwrap();
735
736 assert_eq!(ptr, to_pointer(24));
740 }
741
742 #[test]
743 fn should_increment_pointers_properly() {
744 let mut mem = MemoryInstance::with_pages(1);
746 let mut heap = FreeingBumpHeapAllocator::new(0);
747
748 let ptr1 = heap.allocate(&mut mem, 1).unwrap();
750 let ptr2 = heap.allocate(&mut mem, 9).unwrap();
751 let ptr3 = heap.allocate(&mut mem, 1).unwrap();
752
753 assert_eq!(ptr1, to_pointer(HEADER_SIZE));
756
757 assert_eq!(ptr2, to_pointer(24));
760
761 assert_eq!(ptr3, to_pointer(24 + 16 + HEADER_SIZE));
763 }
764
765 #[test]
766 fn should_free_properly() {
767 let mut mem = MemoryInstance::with_pages(1);
769 let mut heap = FreeingBumpHeapAllocator::new(0);
770 let ptr1 = heap.allocate(&mut mem, 1).unwrap();
771 assert_eq!(ptr1, to_pointer(HEADER_SIZE));
773
774 let ptr2 = heap.allocate(&mut mem, 1).unwrap();
775 assert_eq!(ptr2, to_pointer(24));
777
778 heap.deallocate(&mut mem, ptr2).unwrap();
780
781 assert_eq!(
785 heap.free_lists.heads[0],
786 Link::Ptr(u32::from(ptr2) - HEADER_SIZE)
787 );
788 }
789
790 #[test]
791 fn should_deallocate_and_reallocate_properly() {
792 let mut mem = MemoryInstance::with_pages(1);
794 let padded_offset = 16;
795 let mut heap = FreeingBumpHeapAllocator::new(13);
796
797 let ptr1 = heap.allocate(&mut mem, 1).unwrap();
798 assert_eq!(ptr1, to_pointer(padded_offset + HEADER_SIZE));
800
801 let ptr2 = heap.allocate(&mut mem, 9).unwrap();
802 assert_eq!(ptr2, to_pointer(padded_offset + 16 + HEADER_SIZE));
806
807 heap.deallocate(&mut mem, ptr2).unwrap();
809 let ptr3 = heap.allocate(&mut mem, 9).unwrap();
810
811 assert_eq!(ptr3, to_pointer(padded_offset + 16 + HEADER_SIZE));
814 assert_eq!(heap.free_lists.heads, [Link::Nil; N_ORDERS]);
815 }
816
817 #[test]
818 fn should_build_linked_list_of_free_areas_properly() {
819 let mut mem = MemoryInstance::with_pages(1);
821 let mut heap = FreeingBumpHeapAllocator::new(0);
822
823 let ptr1 = heap.allocate(&mut mem, 8).unwrap();
824 let ptr2 = heap.allocate(&mut mem, 8).unwrap();
825 let ptr3 = heap.allocate(&mut mem, 8).unwrap();
826
827 heap.deallocate(&mut mem, ptr1).unwrap();
829 heap.deallocate(&mut mem, ptr2).unwrap();
830 heap.deallocate(&mut mem, ptr3).unwrap();
831
832 assert_eq!(
834 heap.free_lists.heads[0],
835 Link::Ptr(u32::from(ptr3) - HEADER_SIZE)
836 );
837
838 let ptr4 = heap.allocate(&mut mem, 8).unwrap();
839 assert_eq!(ptr4, ptr3);
840
841 assert_eq!(
842 heap.free_lists.heads[0],
843 Link::Ptr(u32::from(ptr2) - HEADER_SIZE)
844 );
845 }
846
847 #[test]
848 fn should_not_allocate_if_too_large() {
849 let mut mem = MemoryInstance::with_pages(1);
851 mem.set_max_wasm_pages(1);
852 let mut heap = FreeingBumpHeapAllocator::new(13);
853
854 let ptr = heap.allocate(&mut mem, PAGE_SIZE - 13);
856
857 assert_eq!(Error::AllocatorOutOfSpace, ptr.unwrap_err());
859 }
860
861 #[test]
862 fn should_not_allocate_if_full() {
863 let mut mem = MemoryInstance::with_pages(1);
865 mem.set_max_wasm_pages(1);
866 let mut heap = FreeingBumpHeapAllocator::new(0);
867 let ptr1 = heap
868 .allocate(&mut mem, (PAGE_SIZE / 2) - HEADER_SIZE)
869 .unwrap();
870 assert_eq!(ptr1, to_pointer(HEADER_SIZE));
871
872 let ptr2 = heap.allocate(&mut mem, PAGE_SIZE / 2);
874
875 match ptr2.unwrap_err() {
878 Error::AllocatorOutOfSpace => {}
879 e => panic!("Expected allocator out of space error, got: {:?}", e),
880 }
881 }
882
883 #[test]
884 fn should_allocate_max_possible_allocation_size() {
885 let mut mem = MemoryInstance::with_pages(1);
887 let mut heap = FreeingBumpHeapAllocator::new(0);
888
889 let ptr = heap.allocate(&mut mem, MAX_POSSIBLE_ALLOCATION).unwrap();
891
892 assert_eq!(ptr, to_pointer(HEADER_SIZE));
894 }
895
896 #[test]
897 fn should_not_allocate_if_requested_size_too_large() {
898 let mut mem = MemoryInstance::with_pages(1);
900 let mut heap = FreeingBumpHeapAllocator::new(0);
901
902 let ptr = heap.allocate(&mut mem, MAX_POSSIBLE_ALLOCATION + 1);
904
905 assert_eq!(Error::RequestedAllocationTooLarge, ptr.unwrap_err());
907 }
908
909 #[test]
910 fn should_return_error_when_bumper_greater_than_heap_size() {
911 let mut mem = MemoryInstance::with_pages(1);
913 mem.set_max_wasm_pages(1);
914 let mut heap = FreeingBumpHeapAllocator::new(0);
915
916 let mut ptrs = Vec::new();
917 for _ in 0..(PAGE_SIZE as usize / 40) {
918 ptrs.push(heap.allocate(&mut mem, 32).expect("Allocate 32 byte"));
919 }
920
921 assert_eq!(heap.stats.bytes_allocated, PAGE_SIZE - 16);
922 assert_eq!(heap.bumper, PAGE_SIZE - 16);
923
924 ptrs.into_iter()
925 .for_each(|ptr| heap.deallocate(&mut mem, ptr).expect("Deallocate 32 byte"));
926
927 assert_eq!(heap.stats.bytes_allocated, 0);
928 assert_eq!(heap.stats.bytes_allocated_peak, PAGE_SIZE - 16);
929 assert_eq!(heap.bumper, PAGE_SIZE - 16);
930
931 heap.allocate(&mut mem, 8).expect("Allocate 8 byte");
933
934 assert_eq!(heap.bumper as u64, mem.size());
940 let ptr = heap.allocate(&mut mem, 8);
941
942 assert_eq!(Error::AllocatorOutOfSpace, ptr.unwrap_err());
944 }
945
946 #[test]
947 fn should_not_overflow_when_bump_reaches_wasm_address_limit() {
948 struct MaxPagesMemory;
949
950 impl Memory for MaxPagesMemory {
951 fn with_access<R>(&self, run: impl FnOnce(&[u8]) -> R) -> R {
952 run(&[])
953 }
954
955 fn with_access_mut<R>(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R {
956 run(&mut [])
957 }
958
959 fn pages(&self) -> u32 {
960 MAX_WASM_PAGES
961 }
962
963 fn max_pages(&self) -> Option<u32> {
964 Some(MAX_WASM_PAGES)
965 }
966
967 fn grow(&mut self, _: u32) -> Result<(), ()> {
968 unreachable!("memory is already at the wasm address limit")
969 }
970 }
971
972 let mut mem = MaxPagesMemory;
973 let mut bumper = u32::MAX - 7;
974
975 let ptr = FreeingBumpHeapAllocator::bump(&mut bumper, 8, &mut mem);
976
977 assert_eq!(Error::AllocatorOutOfSpace, ptr.unwrap_err());
978 assert_eq!(bumper, u32::MAX - 7);
979 }
980
981 #[test]
982 fn should_include_prefixes_in_total_heap_size() {
983 let mut mem = MemoryInstance::with_pages(1);
985 let mut heap = FreeingBumpHeapAllocator::new(1);
986
987 heap.allocate(&mut mem, 9).unwrap();
990
991 assert_eq!(heap.stats.bytes_allocated, HEADER_SIZE + 16);
993 }
994
995 #[test]
996 fn should_calculate_total_heap_size_to_zero() {
997 let mut mem = MemoryInstance::with_pages(1);
999 let mut heap = FreeingBumpHeapAllocator::new(13);
1000
1001 let ptr = heap.allocate(&mut mem, 42).unwrap();
1003 assert_eq!(ptr, to_pointer(16 + HEADER_SIZE));
1004 heap.deallocate(&mut mem, ptr).unwrap();
1005
1006 assert_eq!(heap.stats.bytes_allocated, 0);
1008 }
1009
1010 #[test]
1011 fn should_calculate_total_size_of_zero() {
1012 let mut mem = MemoryInstance::with_pages(1);
1014 let mut heap = FreeingBumpHeapAllocator::new(19);
1015
1016 for _ in 1..10 {
1018 let ptr = heap.allocate(&mut mem, 42).unwrap();
1019 heap.deallocate(&mut mem, ptr).unwrap();
1020 }
1021
1022 assert_eq!(heap.stats.bytes_allocated, 0);
1024 }
1025
1026 #[test]
1027 fn should_read_and_write_u64_correctly() {
1028 let mut mem = MemoryInstance::with_pages(1);
1030
1031 mem.write_le_u64(40, 4480113).unwrap();
1033
1034 let value = MemoryExt::read_le_u64(&mem, 40).unwrap();
1036 assert_eq!(value, 4480113);
1037 }
1038
1039 #[test]
1040 fn should_get_item_size_from_order() {
1041 let raw_order = 0;
1043
1044 let item_size = Order::from_raw(raw_order).unwrap().size();
1046
1047 assert_eq!(item_size, 8);
1049 }
1050
1051 #[test]
1052 fn should_get_max_item_size_from_index() {
1053 let raw_order = 22;
1055
1056 let item_size = Order::from_raw(raw_order).unwrap().size();
1058
1059 assert_eq!(item_size, MAX_POSSIBLE_ALLOCATION);
1061 }
1062
1063 #[test]
1064 fn deallocate_needs_to_maintain_linked_list() {
1065 let mut mem = MemoryInstance::with_pages(1);
1066 let mut heap = FreeingBumpHeapAllocator::new(0);
1067
1068 let ptrs = (0..4)
1070 .map(|_| heap.allocate(&mut mem, 8).unwrap())
1071 .collect::<Vec<_>>();
1072 ptrs.iter()
1073 .rev()
1074 .for_each(|ptr| heap.deallocate(&mut mem, *ptr).unwrap());
1075
1076 let new_ptrs = (0..4)
1078 .map(|_| heap.allocate(&mut mem, 8).unwrap())
1079 .collect::<Vec<_>>();
1080 assert_eq!(ptrs, new_ptrs);
1081 }
1082
1083 #[test]
1084 fn header_read_write() {
1085 let roundtrip = |header: Header| {
1086 let mut memory = MemoryInstance::with_pages(1);
1087 header.write_into(&mut memory, 0).unwrap();
1088
1089 let read_header = Header::read_from(&memory, 0).unwrap();
1090 assert_eq!(header, read_header);
1091 };
1092
1093 roundtrip(Header::Occupied(Order(0)));
1094 roundtrip(Header::Occupied(Order(1)));
1095 roundtrip(Header::Free(Link::Nil));
1096 roundtrip(Header::Free(Link::Ptr(0)));
1097 roundtrip(Header::Free(Link::Ptr(4)));
1098 }
1099
1100 #[test]
1101 fn poison_oom() {
1102 let mut mem = MemoryInstance::with_pages(1);
1104 mem.set_max_wasm_pages(1);
1105
1106 let mut heap = FreeingBumpHeapAllocator::new(0);
1107
1108 let alloc_ptr = heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap();
1110 assert_eq!(
1111 Error::AllocatorOutOfSpace,
1112 heap.allocate(&mut mem, PAGE_SIZE).unwrap_err()
1113 );
1114
1115 assert!(heap.poisoned);
1117 assert!(heap.deallocate(&mut mem, alloc_ptr).is_err());
1118 }
1119
1120 #[test]
1121 fn test_n_orders() {
1122 assert_eq!(
1124 MIN_POSSIBLE_ALLOCATION * 2u32.pow(N_ORDERS as u32 - 1),
1125 MAX_POSSIBLE_ALLOCATION
1126 );
1127 }
1128
1129 #[test]
1130 fn accepts_growing_memory() {
1131 let mut mem = MemoryInstance::with_pages(1);
1132 let mut heap = FreeingBumpHeapAllocator::new(0);
1133
1134 heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap();
1135 heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap();
1136
1137 mem.grow(1).unwrap();
1138
1139 heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap();
1140 }
1141
1142 #[test]
1143 fn doesnt_accept_shrinking_memory() {
1144 let mut mem = MemoryInstance::with_pages(2);
1145 let mut heap = FreeingBumpHeapAllocator::new(0);
1146
1147 heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap();
1148
1149 mem.data.truncate(PAGE_SIZE as usize);
1150
1151 match heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap_err() {
1152 Error::MemoryShrunk => (),
1153 _ => panic!(),
1154 }
1155 }
1156
1157 #[test]
1158 fn should_grow_memory_when_running_out_of_memory() {
1159 let mut mem = MemoryInstance::with_pages(1);
1160 let mut heap = FreeingBumpHeapAllocator::new(0);
1161
1162 assert_eq!(1, mem.pages());
1163
1164 heap.allocate(&mut mem, PAGE_SIZE * 2).unwrap();
1165
1166 assert_eq!(3, mem.pages());
1167 }
1168
1169 #[test]
1170 fn modifying_the_header_leads_to_an_error() {
1171 let mut mem = MemoryInstance::with_pages(1);
1172 let mut heap = FreeingBumpHeapAllocator::new(0);
1173
1174 let ptr = heap.allocate(&mut mem, 5).unwrap();
1175
1176 heap.deallocate(&mut mem, ptr).unwrap();
1177
1178 Header::Free(Link::Ptr(u32::MAX - 1))
1179 .write_into(&mut mem, u32::from(ptr) - HEADER_SIZE)
1180 .unwrap();
1181
1182 heap.allocate(&mut mem, 5).unwrap();
1183 assert!(
1184 heap.allocate(&mut mem, 5)
1185 .unwrap_err()
1186 .to_string()
1187 .contains("Invalid header pointer")
1188 );
1189 }
1190}