Skip to main content

gsp_allocator/
freeing_bump.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// SPDX-License-Identifier: Apache-2.0
3
4// See `README.md` for the upstream source and license reference.
5
6//! This module implements a freeing-bump allocator.
7//!
8//! The heap is a continuous linear memory and chunks are allocated using a bump allocator.
9//!
10//! ```ignore
11//! +-------------+-------------------------------------------------+
12//! | <allocated> | <unallocated>                                   |
13//! +-------------+-------------------------------------------------+
14//!               ^
15//!               |_ bumper
16//! ```
17//!
18//! Only allocations with sizes of power of two can be allocated. If the incoming request has a non
19//! power of two size it is increased to the nearest power of two. The power of two of size is
20//! referred as **an order**.
21//!
22//! Each allocation has a header immediately preceding to it. The header is always 8 bytes and can
23//! be of two types: free and occupied.
24//!
25//! For implementing freeing we maintain a linked lists for each order. The maximum supported
26//! allocation size is capped, therefore the number of orders and thus the linked lists is as well
27//! limited. Currently, the maximum size of an allocation is 32 MiB.
28//!
29//! When the allocator serves an allocation request it first checks the linked list for the
30//! respective order. If it doesn't have any free chunks, the allocator requests memory from the
31//! bump allocator. In any case the order is stored in the header of the allocation.
32//!
33//! Upon deallocation we get the order of the allocation from its header and then add that
34//! allocation to the linked list for the respective order.
35//!
36//! # Caveats
37//!
38//! This is a fast allocator but it is also dumb. There are specifically two main shortcomings
39//! that the user should keep in mind:
40//!
41//! - Once the bump allocator space is exhausted, there is no way to reclaim the memory. This means
42//!   that it's possible to end up in a situation where there are no live allocations yet a new
43//!   allocation will fail.
44//!
45//!   Let's look into an example. Given a heap of 32 MiB. The user makes a 32 MiB allocation that we
46//!   call `X` . Now the heap is full. Then user deallocates `X`. Since all the space in the bump
47//!   allocator was consumed by the 32 MiB allocation, allocations of all sizes except 32 MiB will
48//!   fail.
49//!
50//! - Sizes of allocations are rounded up to the nearest order. That is, an allocation of 2,00001
51//!   MiB will be put into the bucket of 4 MiB. Therefore, any allocation of size `(N, 2N]` will
52//!   take up to `2N`, thus assuming a uniform distribution of allocation sizes, the average amount
53//!   in use of a `2N` space on the heap will be `(3N + ε) / 2`. So average utilization is going to
54//!   be around 75% (`(3N + ε) / 2 / 2N`) meaning that around 25% of the space in allocation will be
55//!   wasted. This is more pronounced (in terms of absolute heap amounts) with larger allocation
56//!   sizes.
57
58use 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
66/// The minimal alignment guaranteed by this allocator.
67///
68/// The alignment of 8 is chosen because it is the maximum size of a primitive type supported by the
69/// target version of wasm32: i64's natural alignment is 8.
70const ALIGNMENT: u32 = 8;
71
72// Each pointer is prefixed with 8 bytes, which identify the list index
73// to which it belongs.
74const HEADER_SIZE: u32 = 8;
75
76/// Create an allocator error.
77fn error(msg: &'static str) -> Error {
78    Error::Other(msg)
79}
80
81const LOG_TARGET: &str = "wasm-heap";
82
83// The minimum possible allocation size is chosen to be 8 bytes because in that case we would have
84// easier time to provide the guaranteed alignment of 8.
85//
86// The maximum possible allocation size is set in the primitives to 32MiB.
87//
88// N_ORDERS - represents the number of orders supported.
89//
90// This number corresponds to the number of powers between the minimum possible allocation and
91// maximum possible allocation, or: 2^3...2^25 (both ends inclusive, hence 23).
92const N_ORDERS: usize = 23;
93const MIN_POSSIBLE_ALLOCATION: u32 = 8; // 2^3 bytes, 8 bytes
94
95/// The exponent for the power of two sized block adjusted to the minimum size.
96///
97/// This way, if `MIN_POSSIBLE_ALLOCATION == 8`, we would get:
98///
99/// power_of_two_size | order
100/// 8                 | 0
101/// 16                | 1
102/// 32                | 2
103/// 64                | 3
104/// ...
105/// 16777216          | 21
106/// 33554432          | 22
107///
108/// and so on.
109#[derive(Copy, Clone, PartialEq, Eq, Debug)]
110struct Order(u32);
111
112impl Order {
113    /// Create `Order` object from a raw order.
114    ///
115    /// Returns `Err` if it is greater than the maximum supported order.
116    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    /// Compute the order by the given size
125    ///
126    /// The size is clamped, so that the following holds:
127    ///
128    /// `MIN_POSSIBLE_ALLOCATION <= size <= MAX_POSSIBLE_ALLOCATION`
129    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        // Round the clamped size to the next power of two.
140        //
141        // It returns the unchanged value if the value is already a power of two.
142        let power_of_two_size = clamped_size.next_power_of_two();
143
144        // Compute the number of trailing zeroes to get the order. We adjust it by the number of
145        // trailing zeroes in the minimum possible allocation.
146        let order = power_of_two_size.trailing_zeros() - MIN_POSSIBLE_ALLOCATION.trailing_zeros();
147
148        Ok(Self(order))
149    }
150
151    /// Returns the corresponding size in bytes for this order.
152    ///
153    /// Note that it is always a power of two.
154    fn size(&self) -> u32 {
155        MIN_POSSIBLE_ALLOCATION << self.0
156    }
157
158    /// Extract the order as `u32`.
159    fn into_raw(self) -> u32 {
160        self.0
161    }
162}
163
164/// A special magic value for a pointer in a link that denotes the end of the linked list.
165const NIL_MARKER: u32 = u32::MAX;
166
167/// A link between headers in the free list.
168#[derive(Clone, Copy, Debug, PartialEq, Eq)]
169enum Link {
170    /// Nil, denotes that there is no next element.
171    Nil,
172    /// Link to the next element represented as a pointer to the a header.
173    Ptr(u32),
174}
175
176impl Link {
177    /// Creates a link from raw value.
178    fn from_raw(raw: u32) -> Self {
179        if raw != NIL_MARKER {
180            Self::Ptr(raw)
181        } else {
182            Self::Nil
183        }
184    }
185
186    /// Converts this link into a raw u32.
187    fn into_raw(self) -> u32 {
188        match self {
189            Self::Nil => NIL_MARKER,
190            Self::Ptr(ptr) => ptr,
191        }
192    }
193}
194
195/// A header of an allocation.
196///
197/// The header is encoded in memory as follows.
198///
199/// ## Free header
200///
201/// ```ignore
202/// 64             32                  0
203//  +--------------+-------------------+
204/// |            0 | next element link |
205/// +--------------+-------------------+
206/// ```
207/// ## Occupied header
208/// ```ignore
209/// 64             32                  0
210//  +--------------+-------------------+
211/// |            1 |             order |
212/// +--------------+-------------------+
213/// ```
214#[derive(Clone, Debug, PartialEq, Eq)]
215enum Header {
216    /// A free header contains a link to the next element to form a free linked list.
217    Free(Link),
218    /// An occupied header has attached order to know in which free list we should put the
219    /// allocation upon deallocation.
220    Occupied(Order),
221}
222
223impl Header {
224    /// Reads a header from memory.
225    ///
226    /// Returns an error if the `header_ptr` is out of bounds of the linear memory or if the read
227    /// header is corrupted (e.g. the order is incorrect).
228    fn read_from(memory: &impl Memory, header_ptr: u32) -> Result<Self, Error> {
229        let raw_header = memory.read_le_u64(header_ptr)?;
230
231        // Check if the header represents an occupied or free allocation and extract the header data
232        // by trimming (and discarding) the high bits.
233        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    /// Write out this header to memory.
244    ///
245    /// Returns an error if the `header_ptr` is out of bounds of the linear memory.
246    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    /// Returns the order of the allocation if this is an occupied header.
257    fn into_occupied(self) -> Option<Order> {
258        match self {
259            Self::Occupied(order) => Some(order),
260            _ => None,
261        }
262    }
263
264    /// Returns the link to the next element in the free list if this is a free header.
265    fn into_free(self) -> Option<Link> {
266        match self {
267            Self::Free(link) => Some(link),
268            _ => None,
269        }
270    }
271}
272
273/// This struct represents a collection of intrusive linked lists for each order.
274struct FreeLists {
275    heads: [Link; N_ORDERS],
276}
277
278impl FreeLists {
279    /// Creates the free empty lists.
280    fn new() -> Self {
281        Self {
282            heads: [Link::Nil; N_ORDERS],
283        }
284    }
285
286    /// Replaces a given link for the specified order and returns the old one.
287    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/// Memory allocation stats gathered during the lifetime of the allocator.
308#[derive(Clone, Debug, Default)]
309#[non_exhaustive]
310pub struct AllocationStats {
311    /// The current number of bytes allocated.
312    ///
313    /// This represents how many bytes are allocated *right now*.
314    pub bytes_allocated: u32,
315
316    /// The peak number of bytes ever allocated.
317    ///
318    /// This is the maximum the `bytes_allocated` ever reached.
319    pub bytes_allocated_peak: u32,
320
321    /// The sum of every allocation ever made.
322    ///
323    /// This increases every time a new allocation is made.
324    pub bytes_allocated_sum: u128,
325
326    /// The amount of address space (in bytes) used by the allocator.
327    ///
328    /// This is calculated as the difference between the allocator's bumper
329    /// and the heap base.
330    ///
331    /// Currently the bumper's only ever incremented, so this is simultaneously
332    /// the current value as well as the peak value.
333    pub address_space_used: u32,
334}
335
336/// Convert the given `size` in bytes into the number of pages.
337///
338/// The returned number of pages is ensured to be big enough to hold memory with the given `size`.
339///
340/// Returns `None` if the number of pages to not fit into `u32`.
341fn pages_from_size(size: u64) -> Option<u32> {
342    u32::try_from(size.div_ceil(PAGE_SIZE as u64)).ok()
343}
344
345/// An implementation of freeing bump allocator.
346///
347/// Refer to the module-level documentation for further details.
348pub 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    /// Creates a new allocation heap which follows a freeing-bump strategy.
365    ///
366    /// # Arguments
367    ///
368    /// - `heap_base` - the offset from the beginning of the linear memory where the heap starts.
369    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    /// Gets requested number of bytes to allocate and returns a pointer.
383    /// The maximum size which can be allocated at once is 32 MiB.
384    /// There is no minimum size, but whatever size is passed into
385    /// this function is rounded to the next power of two. If the requested
386    /// size is below 8 bytes it will be rounded up to 8 bytes.
387    ///
388    /// The identity or the type of the passed memory object does not matter. However, the size of
389    /// memory cannot shrink compared to the memory passed in previous invocations.
390    ///
391    /// NOTE: Once the allocator has returned an error all subsequent requests will return an error.
392    ///
393    /// # Arguments
394    ///
395    /// - `mem` - a slice representing the linear memory on which this allocator operates.
396    /// - `size` - size in bytes of the allocation request
397    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                // Remove this header from the free list.
422                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                // Corresponding free list is empty. Allocate a new item.
431                Self::bump(&mut self.bumper, order.size() + HEADER_SIZE, mem)?
432            }
433        };
434
435        // Write the order in the occupied header.
436        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    /// Deallocates the space which was allocated for a pointer.
451    ///
452    /// The identity or the type of the passed memory object does not matter. However, the size of
453    /// memory cannot shrink compared to the memory passed in previous invocations.
454    ///
455    /// NOTE: Once the allocator has returned an error all subsequent requests will return an error.
456    ///
457    /// # Arguments
458    ///
459    /// - `mem` - a slice representing the linear memory on which this allocator operates.
460    /// - `ptr` - pointer to the allocated chunk
461    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        // Update the just freed header and knit it back to the free list.
481        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    /// Returns the allocation stats for this allocator.
497    pub fn stats(&self) -> AllocationStats {
498        self.stats.clone()
499    }
500
501    /// Increases the `bumper` by `size`.
502    ///
503    /// Returns the `bumper` from before the increase. Returns an `Error::AllocatorOutOfSpace` if
504    /// the operation would exhaust the heap.
505    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            // Ideally we want to double our current number of pages,
539            // as long as it's less than the absolute maximum we can have.
540            let next_pages = min(current_pages * 2, max_pages);
541            // ...but if even more pages are required then try to allocate that many.
542            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
577/// A trait for abstraction of accesses to a wasm linear memory. Used to read or modify the
578/// allocation prefixes.
579///
580/// A wasm linear memory behaves similarly to a vector. The address space doesn't have holes and is
581/// accessible up to the reported size.
582///
583/// The linear memory can grow in size with the wasm page granularity (64KiB), but it cannot shrink.
584trait MemoryExt: Memory {
585    /// Read a u64 from the heap in LE form. Returns an error if any of the bytes read are out of
586    /// bounds.
587    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    /// Write a u64 to the heap in LE form. Returns an error if any of the bytes written are out of
599    /// bounds.
600    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    /// Returns the full size of the memory in bytes.
611    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
630/// A guard that will raise the poisoned flag on drop unless disarmed.
631struct 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    /// Makes a pointer out of the given address.
652    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        // given
716        let mut mem = MemoryInstance::with_pages(1);
717        let mut heap = FreeingBumpHeapAllocator::new(0);
718
719        // when
720        let ptr = heap.allocate(&mut mem, 1).unwrap();
721
722        // then
723        // returned pointer must start right after `HEADER_SIZE`
724        assert_eq!(ptr, to_pointer(HEADER_SIZE));
725    }
726
727    #[test]
728    fn should_always_align_pointers_to_multiples_of_8() {
729        // given
730        let mut mem = MemoryInstance::with_pages(1);
731        let mut heap = FreeingBumpHeapAllocator::new(13);
732
733        // when
734        let ptr = heap.allocate(&mut mem, 1).unwrap();
735
736        // then
737        // the pointer must start at the next multiple of 8 from 13
738        // + the prefix of 8 bytes.
739        assert_eq!(ptr, to_pointer(24));
740    }
741
742    #[test]
743    fn should_increment_pointers_properly() {
744        // given
745        let mut mem = MemoryInstance::with_pages(1);
746        let mut heap = FreeingBumpHeapAllocator::new(0);
747
748        // when
749        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        // then
754        // a prefix of 8 bytes is prepended to each pointer
755        assert_eq!(ptr1, to_pointer(HEADER_SIZE));
756
757        // the prefix of 8 bytes + the content of ptr1 padded to the lowest possible
758        // item size of 8 bytes + the prefix of ptr1
759        assert_eq!(ptr2, to_pointer(24));
760
761        // ptr2 + its content of 16 bytes + the prefix of 8 bytes
762        assert_eq!(ptr3, to_pointer(24 + 16 + HEADER_SIZE));
763    }
764
765    #[test]
766    fn should_free_properly() {
767        // given
768        let mut mem = MemoryInstance::with_pages(1);
769        let mut heap = FreeingBumpHeapAllocator::new(0);
770        let ptr1 = heap.allocate(&mut mem, 1).unwrap();
771        // the prefix of 8 bytes is prepended to the pointer
772        assert_eq!(ptr1, to_pointer(HEADER_SIZE));
773
774        let ptr2 = heap.allocate(&mut mem, 1).unwrap();
775        // the prefix of 8 bytes + the content of ptr 1 is prepended to the pointer
776        assert_eq!(ptr2, to_pointer(24));
777
778        // when
779        heap.deallocate(&mut mem, ptr2).unwrap();
780
781        // then
782        // then the heads table should contain a pointer to the
783        // prefix of ptr2 in the leftmost entry
784        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        // given
793        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        // the prefix of 8 bytes is prepended to the pointer
799        assert_eq!(ptr1, to_pointer(padded_offset + HEADER_SIZE));
800
801        let ptr2 = heap.allocate(&mut mem, 9).unwrap();
802        // the padded_offset + the previously allocated ptr (8 bytes prefix +
803        // 8 bytes content) + the prefix of 8 bytes which is prepended to the
804        // current pointer
805        assert_eq!(ptr2, to_pointer(padded_offset + 16 + HEADER_SIZE));
806
807        // when
808        heap.deallocate(&mut mem, ptr2).unwrap();
809        let ptr3 = heap.allocate(&mut mem, 9).unwrap();
810
811        // then
812        // should have re-allocated
813        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        // given
820        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        // when
828        heap.deallocate(&mut mem, ptr1).unwrap();
829        heap.deallocate(&mut mem, ptr2).unwrap();
830        heap.deallocate(&mut mem, ptr3).unwrap();
831
832        // then
833        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        // given
850        let mut mem = MemoryInstance::with_pages(1);
851        mem.set_max_wasm_pages(1);
852        let mut heap = FreeingBumpHeapAllocator::new(13);
853
854        // when
855        let ptr = heap.allocate(&mut mem, PAGE_SIZE - 13);
856
857        // then
858        assert_eq!(Error::AllocatorOutOfSpace, ptr.unwrap_err());
859    }
860
861    #[test]
862    fn should_not_allocate_if_full() {
863        // given
864        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        // when
873        let ptr2 = heap.allocate(&mut mem, PAGE_SIZE / 2);
874
875        // then
876        // there is no room for another half page incl. its 8 byte prefix
877        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        // given
886        let mut mem = MemoryInstance::with_pages(1);
887        let mut heap = FreeingBumpHeapAllocator::new(0);
888
889        // when
890        let ptr = heap.allocate(&mut mem, MAX_POSSIBLE_ALLOCATION).unwrap();
891
892        // then
893        assert_eq!(ptr, to_pointer(HEADER_SIZE));
894    }
895
896    #[test]
897    fn should_not_allocate_if_requested_size_too_large() {
898        // given
899        let mut mem = MemoryInstance::with_pages(1);
900        let mut heap = FreeingBumpHeapAllocator::new(0);
901
902        // when
903        let ptr = heap.allocate(&mut mem, MAX_POSSIBLE_ALLOCATION + 1);
904
905        // then
906        assert_eq!(Error::RequestedAllocationTooLarge, ptr.unwrap_err());
907    }
908
909    #[test]
910    fn should_return_error_when_bumper_greater_than_heap_size() {
911        // given
912        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        // Allocate another 8 byte to use the full heap.
932        heap.allocate(&mut mem, 8).expect("Allocate 8 byte");
933
934        // when
935        // the `bumper` value is equal to `size` here and any
936        // further allocation which would increment the bumper must fail.
937        // we try to allocate 8 bytes here, which will increment the
938        // bumper since no 8 byte item has been freed before.
939        assert_eq!(heap.bumper as u64, mem.size());
940        let ptr = heap.allocate(&mut mem, 8);
941
942        // then
943        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        // given
984        let mut mem = MemoryInstance::with_pages(1);
985        let mut heap = FreeingBumpHeapAllocator::new(1);
986
987        // when
988        // an item size of 16 must be used then
989        heap.allocate(&mut mem, 9).unwrap();
990
991        // then
992        assert_eq!(heap.stats.bytes_allocated, HEADER_SIZE + 16);
993    }
994
995    #[test]
996    fn should_calculate_total_heap_size_to_zero() {
997        // given
998        let mut mem = MemoryInstance::with_pages(1);
999        let mut heap = FreeingBumpHeapAllocator::new(13);
1000
1001        // when
1002        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        // then
1007        assert_eq!(heap.stats.bytes_allocated, 0);
1008    }
1009
1010    #[test]
1011    fn should_calculate_total_size_of_zero() {
1012        // given
1013        let mut mem = MemoryInstance::with_pages(1);
1014        let mut heap = FreeingBumpHeapAllocator::new(19);
1015
1016        // when
1017        for _ in 1..10 {
1018            let ptr = heap.allocate(&mut mem, 42).unwrap();
1019            heap.deallocate(&mut mem, ptr).unwrap();
1020        }
1021
1022        // then
1023        assert_eq!(heap.stats.bytes_allocated, 0);
1024    }
1025
1026    #[test]
1027    fn should_read_and_write_u64_correctly() {
1028        // given
1029        let mut mem = MemoryInstance::with_pages(1);
1030
1031        // when
1032        mem.write_le_u64(40, 4480113).unwrap();
1033
1034        // then
1035        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        // given
1042        let raw_order = 0;
1043
1044        // when
1045        let item_size = Order::from_raw(raw_order).unwrap().size();
1046
1047        // then
1048        assert_eq!(item_size, 8);
1049    }
1050
1051    #[test]
1052    fn should_get_max_item_size_from_index() {
1053        // given
1054        let raw_order = 22;
1055
1056        // when
1057        let item_size = Order::from_raw(raw_order).unwrap().size();
1058
1059        // then
1060        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        // Allocate and free some pointers
1069        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        // Second time we should be able to allocate all of them again and get the same pointers!
1077        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        // given
1103        let mut mem = MemoryInstance::with_pages(1);
1104        mem.set_max_wasm_pages(1);
1105
1106        let mut heap = FreeingBumpHeapAllocator::new(0);
1107
1108        // when
1109        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        // then
1116        assert!(heap.poisoned);
1117        assert!(heap.deallocate(&mut mem, alloc_ptr).is_err());
1118    }
1119
1120    #[test]
1121    fn test_n_orders() {
1122        // Test that N_ORDERS is consistent with min and max possible allocation.
1123        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}