use crate::{
read_struct, read_to_vec,
types::{Address, Bytes},
write, write_struct, Memory, WASM_PAGE_SIZE,
};
use std::cell::{Cell, RefCell};
use std::rc::Rc;
const MAGIC: &[u8; 3] = b"MGR";
const LAYOUT_VERSION: u8 = 1;
const MAX_NUM_MEMORIES: u8 = 255;
const MAX_NUM_BUCKETS: u64 = 32768;
const BUCKET_SIZE_IN_PAGES: u64 = 128;
const UNALLOCATED_BUCKET_MARKER: u8 = MAX_NUM_MEMORIES;
const BUCKETS_OFFSET_IN_PAGES: u64 = 1;
const BUCKETS_OFFSET_IN_BYTES: u64 = BUCKETS_OFFSET_IN_PAGES * WASM_PAGE_SIZE;
const HEADER_RESERVED_BYTES: usize = 32;
pub struct MemoryManager<M: Memory> {
inner: Rc<RefCell<MemoryManagerInner<M>>>,
}
impl<M: Memory> MemoryManager<M> {
pub fn init(memory: M) -> Self {
Self::init_with_bucket_size(memory, BUCKET_SIZE_IN_PAGES as u16)
}
pub fn init_with_bucket_size(memory: M, bucket_size_in_pages: u16) -> Self {
Self {
inner: Rc::new(RefCell::new(MemoryManagerInner::init(
memory,
bucket_size_in_pages,
))),
}
}
pub fn get(&self, id: MemoryId) -> VirtualMemory<M> {
VirtualMemory {
id,
memory_manager: self.inner.clone(),
cache: BucketCache::new(),
}
}
pub fn into_memory(self) -> Option<M> {
Rc::into_inner(self.inner).map(|inner| inner.into_inner().into_memory())
}
}
#[repr(C, packed)]
struct Header {
magic: [u8; 3],
version: u8,
num_allocated_buckets: u16,
bucket_size_in_pages: u16,
_reserved: [u8; HEADER_RESERVED_BYTES],
memory_sizes_in_pages: [u64; MAX_NUM_MEMORIES as usize],
}
impl Header {
fn size() -> Bytes {
Bytes::new(core::mem::size_of::<Self>() as u64)
}
}
#[derive(Clone)]
pub struct VirtualMemory<M: Memory> {
id: MemoryId,
memory_manager: Rc<RefCell<MemoryManagerInner<M>>>,
cache: BucketCache,
}
impl<M: Memory> Memory for VirtualMemory<M> {
fn size(&self) -> u64 {
self.memory_manager.borrow().memory_size(self.id)
}
fn grow(&self, pages: u64) -> i64 {
self.memory_manager.borrow_mut().grow(self.id, pages)
}
fn read(&self, offset: u64, dst: &mut [u8]) {
self.memory_manager
.borrow()
.read(self.id, offset, dst, &self.cache)
}
unsafe fn read_unsafe(&self, offset: u64, dst: *mut u8, count: usize) {
self.memory_manager
.borrow()
.read_unsafe(self.id, offset, dst, count, &self.cache)
}
fn write(&self, offset: u64, src: &[u8]) {
self.memory_manager
.borrow()
.write(self.id, offset, src, &self.cache)
}
}
#[derive(Clone)]
struct MemoryManagerInner<M: Memory> {
memory: M,
allocated_buckets: u16,
bucket_size_in_pages: u16,
memory_sizes_in_pages: [u64; MAX_NUM_MEMORIES as usize],
memory_buckets: Vec<Vec<BucketId>>,
}
impl<M: Memory> MemoryManagerInner<M> {
fn init(memory: M, bucket_size_in_pages: u16) -> Self {
if memory.size() == 0 {
return Self::new(memory, bucket_size_in_pages);
}
let mut dst = [0; 3];
memory.read(0, &mut dst);
if &dst != MAGIC {
MemoryManagerInner::new(memory, bucket_size_in_pages)
} else {
MemoryManagerInner::load(memory)
}
}
fn new(memory: M, bucket_size_in_pages: u16) -> Self {
let mem_mgr = Self {
memory,
allocated_buckets: 0,
memory_sizes_in_pages: [0; MAX_NUM_MEMORIES as usize],
memory_buckets: vec![vec![]; MAX_NUM_MEMORIES as usize],
bucket_size_in_pages,
};
mem_mgr.save_header();
write(
&mem_mgr.memory,
bucket_allocations_address(BucketId(0)).get(),
&[UNALLOCATED_BUCKET_MARKER; MAX_NUM_BUCKETS as usize],
);
mem_mgr
}
fn load(memory: M) -> Self {
let header: Header = read_struct(Address::from(0), &memory);
assert_eq!(&header.magic, MAGIC, "Bad magic.");
assert_eq!(header.version, LAYOUT_VERSION, "Unsupported version.");
let mut buckets = vec![];
read_to_vec(
&memory,
bucket_allocations_address(BucketId(0)),
&mut buckets,
MAX_NUM_BUCKETS as usize,
);
let mut memory_buckets = vec![vec![]; MAX_NUM_MEMORIES as usize];
for (bucket_idx, memory) in buckets.into_iter().enumerate() {
if memory != UNALLOCATED_BUCKET_MARKER {
memory_buckets[memory as usize].push(BucketId(bucket_idx as u16));
}
}
Self {
memory,
allocated_buckets: header.num_allocated_buckets,
bucket_size_in_pages: header.bucket_size_in_pages,
memory_sizes_in_pages: header.memory_sizes_in_pages,
memory_buckets,
}
}
fn save_header(&self) {
let header = Header {
magic: *MAGIC,
version: LAYOUT_VERSION,
num_allocated_buckets: self.allocated_buckets,
bucket_size_in_pages: self.bucket_size_in_pages,
_reserved: [0; HEADER_RESERVED_BYTES],
memory_sizes_in_pages: self.memory_sizes_in_pages,
};
write_struct(&header, Address::from(0), &self.memory);
}
fn memory_size(&self, id: MemoryId) -> u64 {
self.memory_sizes_in_pages[id.0 as usize]
}
fn grow(&mut self, id: MemoryId, pages: u64) -> i64 {
let old_size = self.memory_size(id);
let new_size = old_size + pages;
let current_buckets = self.num_buckets_needed(old_size);
let required_buckets = self.num_buckets_needed(new_size);
let new_buckets_needed = required_buckets - current_buckets;
if new_buckets_needed + self.allocated_buckets as u64 > MAX_NUM_BUCKETS {
return -1;
}
let memory_bucket = &mut self.memory_buckets[id.0 as usize];
memory_bucket.reserve(new_buckets_needed as usize);
for _ in 0..new_buckets_needed {
let new_bucket_id = BucketId(self.allocated_buckets);
memory_bucket.push(new_bucket_id);
write(
&self.memory,
bucket_allocations_address(new_bucket_id).get(),
&[id.0],
);
self.allocated_buckets += 1;
}
let pages_needed = BUCKETS_OFFSET_IN_PAGES
+ self.bucket_size_in_pages as u64 * self.allocated_buckets as u64;
if pages_needed > self.memory.size() {
let additional_pages_needed = pages_needed - self.memory.size();
let prev_pages = self.memory.grow(additional_pages_needed);
if prev_pages == -1 {
panic!("{id:?}: grow failed");
}
}
self.memory_sizes_in_pages[id.0 as usize] = new_size;
self.save_header();
old_size as i64
}
fn write(&self, id: MemoryId, offset: u64, src: &[u8], bucket_cache: &BucketCache) {
if let Some(real_address) = bucket_cache.get(VirtualSegment {
address: offset.into(),
length: src.len().into(),
}) {
self.memory.write(real_address.get(), src);
return;
}
if (offset + src.len() as u64) > self.memory_size(id) * WASM_PAGE_SIZE {
panic!("{id:?}: write out of bounds");
}
let mut bytes_written = 0;
self.for_each_bucket(
id,
VirtualSegment {
address: offset.into(),
length: src.len().into(),
},
bucket_cache,
|RealSegment { address, length }| {
self.memory.write(
address.get(),
&src[bytes_written as usize..(bytes_written + length.get()) as usize],
);
bytes_written += length.get();
},
);
}
#[inline]
fn read(&self, id: MemoryId, offset: u64, dst: &mut [u8], bucket_cache: &BucketCache) {
unsafe { self.read_unsafe(id, offset, dst.as_mut_ptr(), dst.len(), bucket_cache) }
}
unsafe fn read_unsafe(
&self,
id: MemoryId,
offset: u64,
dst: *mut u8,
count: usize,
bucket_cache: &BucketCache,
) {
if let Some(real_address) = bucket_cache.get(VirtualSegment {
address: offset.into(),
length: count.into(),
}) {
self.memory.read_unsafe(real_address.get(), dst, count);
return;
}
if (offset + count as u64) > self.memory_size(id) * WASM_PAGE_SIZE {
panic!("{id:?}: read out of bounds");
}
let mut bytes_read = Bytes::new(0);
self.for_each_bucket(
id,
VirtualSegment {
address: offset.into(),
length: count.into(),
},
bucket_cache,
|RealSegment { address, length }| {
self.memory.read_unsafe(
address.get(),
dst.add(bytes_read.get() as usize),
length.get() as usize,
);
bytes_read += length;
},
)
}
fn for_each_bucket(
&self,
MemoryId(id): MemoryId,
virtual_segment: VirtualSegment,
bucket_cache: &BucketCache,
mut func: impl FnMut(RealSegment),
) {
let buckets = self.memory_buckets[id as usize].as_slice();
let bucket_size_in_bytes = self.bucket_size_in_bytes().get();
let virtual_offset = virtual_segment.address.get();
let mut length = virtual_segment.length.get();
let mut bucket_idx = (virtual_offset / bucket_size_in_bytes) as usize;
let mut start_offset_in_bucket = virtual_offset % bucket_size_in_bytes;
while length > 0 {
let bucket_address =
self.bucket_address(buckets.get(bucket_idx).expect("bucket idx out of bounds"));
let bucket_start = bucket_idx as u64 * bucket_size_in_bytes;
let segment_len = (bucket_size_in_bytes - start_offset_in_bucket).min(length);
bucket_cache.store(
VirtualSegment {
address: bucket_start.into(),
length: self.bucket_size_in_bytes(),
},
bucket_address,
);
func(RealSegment {
address: bucket_address + start_offset_in_bucket.into(),
length: segment_len.into(),
});
length -= segment_len;
bucket_idx += 1;
start_offset_in_bucket = 0;
}
}
fn bucket_size_in_bytes(&self) -> Bytes {
Bytes::from(self.bucket_size_in_pages as u64 * WASM_PAGE_SIZE)
}
fn num_buckets_needed(&self, num_pages: u64) -> u64 {
num_pages.div_ceil(self.bucket_size_in_pages as u64)
}
pub fn into_memory(self) -> M {
self.memory
}
#[inline]
fn bucket_address(&self, id: &BucketId) -> Address {
Address::from(BUCKETS_OFFSET_IN_BYTES) + self.bucket_size_in_bytes() * Bytes::from(id.0)
}
}
#[derive(Copy, Clone)]
struct VirtualSegment {
address: Address,
length: Bytes,
}
impl VirtualSegment {
fn contains_segment(&self, other: &VirtualSegment) -> bool {
self.address <= other.address && other.address + other.length <= self.address + self.length
}
}
struct RealSegment {
address: Address,
length: Bytes,
}
#[derive(Clone, Copy, Ord, Eq, PartialEq, PartialOrd, Debug)]
pub struct MemoryId(u8);
impl MemoryId {
pub const fn new(id: u8) -> Self {
assert!(id != UNALLOCATED_BUCKET_MARKER);
Self(id)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct BucketId(u16);
fn bucket_allocations_address(id: BucketId) -> Address {
Address::from(0) + Header::size() + Bytes::from(id.0)
}
#[derive(Clone)]
struct BucketCache {
bucket: Cell<VirtualSegment>,
real_address: Cell<Address>,
}
impl BucketCache {
#[inline]
fn new() -> Self {
BucketCache {
bucket: Cell::new(VirtualSegment {
address: Address::from(0),
length: Bytes::new(0),
}),
real_address: Cell::new(Address::from(0)),
}
}
}
impl BucketCache {
#[inline]
fn get(&self, virtual_segment: VirtualSegment) -> Option<Address> {
let cached_bucket = self.bucket.get();
cached_bucket
.contains_segment(&virtual_segment)
.then(|| self.real_address.get() + (virtual_segment.address - cached_bucket.address))
}
#[inline]
fn store(&self, bucket: VirtualSegment, real_address: Address) {
self.bucket.set(bucket);
self.real_address.set(real_address);
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::safe_write;
use proptest::prelude::*;
const MAX_MEMORY_IN_PAGES: u64 = MAX_NUM_BUCKETS * BUCKET_SIZE_IN_PAGES;
fn make_memory() -> Rc<RefCell<Vec<u8>>> {
Rc::new(RefCell::new(Vec::new()))
}
#[test]
fn can_get_memory() {
let mem_mgr = MemoryManager::init(make_memory());
let memory = mem_mgr.get(MemoryId(0));
assert_eq!(memory.size(), 0);
}
#[test]
fn can_allocate_and_use_memory() {
let mem_mgr = MemoryManager::init(make_memory());
let memory = mem_mgr.get(MemoryId(0));
assert_eq!(memory.grow(1), 0);
assert_eq!(memory.size(), 1);
memory.write(0, &[1, 2, 3]);
let mut bytes = vec![0; 3];
memory.read(0, &mut bytes);
assert_eq!(bytes, vec![1, 2, 3]);
assert_eq!(mem_mgr.inner.borrow().memory_buckets[0], vec![BucketId(0)]);
assert!(mem_mgr.inner.borrow().memory_buckets[1..]
.iter()
.all(|x| x.is_empty()));
}
#[test]
fn can_allocate_and_use_multiple_memories() {
let mem = make_memory();
let mem_mgr = MemoryManager::init(mem.clone());
let memory_0 = mem_mgr.get(MemoryId(0));
let memory_1 = mem_mgr.get(MemoryId(1));
assert_eq!(memory_0.grow(1), 0);
assert_eq!(memory_1.grow(1), 0);
assert_eq!(memory_0.size(), 1);
assert_eq!(memory_1.size(), 1);
assert_eq!(
&mem_mgr.inner.borrow().memory_buckets[..2],
&[vec![BucketId(0)], vec![BucketId(1)],]
);
assert!(mem_mgr.inner.borrow().memory_buckets[2..]
.iter()
.all(|x| x.is_empty()));
memory_0.write(0, &[1, 2, 3]);
memory_0.write(0, &[1, 2, 3]);
memory_1.write(0, &[4, 5, 6]);
let mut bytes = vec![0; 3];
memory_0.read(0, &mut bytes);
assert_eq!(bytes, vec![1, 2, 3]);
let mut bytes = vec![0; 3];
memory_1.read(0, &mut bytes);
assert_eq!(bytes, vec![4, 5, 6]);
assert_eq!(mem.size(), 2 * BUCKET_SIZE_IN_PAGES + 1);
}
#[test]
fn can_be_reinitialized_from_memory() {
let mem = make_memory();
let mem_mgr = MemoryManager::init(mem.clone());
let memory_0 = mem_mgr.get(MemoryId(0));
let memory_1 = mem_mgr.get(MemoryId(1));
assert_eq!(memory_0.grow(1), 0);
assert_eq!(memory_1.grow(1), 0);
memory_0.write(0, &[1, 2, 3]);
memory_1.write(0, &[4, 5, 6]);
let mem_mgr = MemoryManager::init(mem);
let memory_0 = mem_mgr.get(MemoryId(0));
let memory_1 = mem_mgr.get(MemoryId(1));
let mut bytes = vec![0; 3];
memory_0.read(0, &mut bytes);
assert_eq!(bytes, vec![1, 2, 3]);
memory_1.read(0, &mut bytes);
assert_eq!(bytes, vec![4, 5, 6]);
}
#[test]
fn growing_same_memory_multiple_times_doesnt_increase_underlying_allocation() {
let mem = make_memory();
let mem_mgr = MemoryManager::init(mem.clone());
let memory_0 = mem_mgr.get(MemoryId(0));
assert_eq!(memory_0.grow(1), 0);
assert_eq!(mem.size(), 1 + BUCKET_SIZE_IN_PAGES);
assert_eq!(memory_0.grow(1), 1);
assert_eq!(memory_0.size(), 2);
assert_eq!(mem.size(), 1 + BUCKET_SIZE_IN_PAGES);
assert_eq!(memory_0.grow(BUCKET_SIZE_IN_PAGES - 2), 2);
assert_eq!(memory_0.size(), BUCKET_SIZE_IN_PAGES);
assert_eq!(mem.size(), 1 + BUCKET_SIZE_IN_PAGES);
assert_eq!(memory_0.grow(1), BUCKET_SIZE_IN_PAGES as i64);
assert_eq!(memory_0.size(), BUCKET_SIZE_IN_PAGES + 1);
assert_eq!(mem.size(), 1 + 2 * BUCKET_SIZE_IN_PAGES);
}
#[test]
fn does_not_grow_memory_unnecessarily() {
let mem = make_memory();
let initial_size = BUCKET_SIZE_IN_PAGES * 2;
mem.grow(initial_size);
let mem_mgr = MemoryManager::init(mem.clone());
let memory_0 = mem_mgr.get(MemoryId(0));
assert_eq!(memory_0.grow(1), 0);
assert_eq!(mem.size(), initial_size);
assert_eq!(memory_0.grow(BUCKET_SIZE_IN_PAGES), 1);
assert_eq!(mem.size(), 1 + BUCKET_SIZE_IN_PAGES * 2);
}
#[test]
fn growing_beyond_capacity_fails() {
let mem = make_memory();
let mem_mgr = MemoryManager::init(mem);
let memory_0 = mem_mgr.get(MemoryId(0));
assert_eq!(memory_0.grow(MAX_MEMORY_IN_PAGES + 1), -1);
assert_eq!(memory_0.grow(1), 0); assert_eq!(memory_0.grow(MAX_MEMORY_IN_PAGES), -1); }
#[test]
fn can_write_across_bucket_boundaries() {
let mem = make_memory();
let mem_mgr = MemoryManager::init(mem);
let memory_0 = mem_mgr.get(MemoryId(0));
assert_eq!(memory_0.grow(BUCKET_SIZE_IN_PAGES + 1), 0);
memory_0.write(
mem_mgr.inner.borrow().bucket_size_in_bytes().get() - 1,
&[1, 2, 3],
);
let mut bytes = vec![0; 3];
memory_0.read(
mem_mgr.inner.borrow().bucket_size_in_bytes().get() - 1,
&mut bytes,
);
assert_eq!(bytes, vec![1, 2, 3]);
}
#[test]
fn can_write_across_bucket_boundaries_with_interleaving_memories() {
let mem = make_memory();
let mem_mgr = MemoryManager::init(mem);
let memory_0 = mem_mgr.get(MemoryId(0));
let memory_1 = mem_mgr.get(MemoryId(1));
assert_eq!(memory_0.grow(BUCKET_SIZE_IN_PAGES), 0);
assert_eq!(memory_1.grow(1), 0);
assert_eq!(memory_0.grow(1), BUCKET_SIZE_IN_PAGES as i64);
memory_0.write(
mem_mgr.inner.borrow().bucket_size_in_bytes().get() - 1,
&[1, 2, 3],
);
memory_1.write(0, &[4, 5, 6]);
let mut bytes = vec![0; 3];
memory_0.read(WASM_PAGE_SIZE * BUCKET_SIZE_IN_PAGES - 1, &mut bytes);
assert_eq!(bytes, vec![1, 2, 3]);
let mut bytes = vec![0; 3];
memory_1.read(0, &mut bytes);
assert_eq!(bytes, vec![4, 5, 6]);
}
#[test]
#[should_panic]
fn reading_out_of_bounds_should_panic() {
let mem = make_memory();
let mem_mgr = MemoryManager::init(mem);
let memory_0 = mem_mgr.get(MemoryId(0));
let memory_1 = mem_mgr.get(MemoryId(1));
assert_eq!(memory_0.grow(1), 0);
assert_eq!(memory_1.grow(1), 0);
let mut bytes = vec![0; WASM_PAGE_SIZE as usize + 1];
memory_0.read(0, &mut bytes);
}
#[test]
#[should_panic]
fn writing_out_of_bounds_should_panic() {
let mem = make_memory();
let mem_mgr = MemoryManager::init(mem);
let memory_0 = mem_mgr.get(MemoryId(0));
let memory_1 = mem_mgr.get(MemoryId(1));
assert_eq!(memory_0.grow(1), 0);
assert_eq!(memory_1.grow(1), 0);
let bytes = vec![0; WASM_PAGE_SIZE as usize + 1];
memory_0.write(0, &bytes);
}
#[test]
fn reading_zero_bytes_from_empty_memory_should_not_panic() {
let mem = make_memory();
let mem_mgr = MemoryManager::init(mem);
let memory_0 = mem_mgr.get(MemoryId(0));
assert_eq!(memory_0.size(), 0);
let mut bytes = vec![];
memory_0.read(0, &mut bytes);
}
#[test]
fn writing_zero_bytes_to_empty_memory_should_not_panic() {
let mem = make_memory();
let mem_mgr = MemoryManager::init(mem);
let memory_0 = mem_mgr.get(MemoryId(0));
assert_eq!(memory_0.size(), 0);
memory_0.write(0, &[]);
}
#[test]
fn write_and_read_random_bytes() {
let mem = make_memory();
let mem_mgr = MemoryManager::init_with_bucket_size(mem, 1);
let memories: Vec<_> = (0..MAX_NUM_MEMORIES)
.map(|id| mem_mgr.get(MemoryId(id)))
.collect();
proptest!(|(
num_memories in 0..255usize,
data in proptest::collection::vec(0..u8::MAX, 0..2*WASM_PAGE_SIZE as usize),
offset in 0..10*WASM_PAGE_SIZE
)| {
for memory in memories.iter().take(num_memories) {
write(memory, offset, &data);
{
let mut bytes = vec![0; data.len()];
memory.read(offset, &mut bytes);
assert_eq!(bytes, data);
}
{
let mut bytes = vec![];
read_to_vec(memory, offset.into(), &mut bytes, data.len());
assert_eq!(bytes, data);
}
{
let mut bytes = vec![0; data.len()];
unsafe {
memory.read_unsafe(offset, bytes.as_mut_ptr(), data.len());
}
assert_eq!(bytes, data);
}
}
});
}
#[test]
fn init_with_non_default_bucket_size() {
let bucket_size = 256;
assert_ne!(bucket_size, BUCKET_SIZE_IN_PAGES as u16);
let mem = make_memory();
let mem_mgr = MemoryManager::init_with_bucket_size(mem.clone(), bucket_size);
let memory_0 = mem_mgr.get(MemoryId(0));
let memory_1 = mem_mgr.get(MemoryId(1));
memory_0.grow(300);
memory_1.grow(100);
memory_0.write(0, &[1; 1000]);
memory_1.write(0, &[2; 1000]);
let mem_mgr = MemoryManager::init(mem);
assert_eq!(mem_mgr.inner.borrow().bucket_size_in_pages, bucket_size);
let memory_0 = mem_mgr.get(MemoryId(0));
let memory_1 = mem_mgr.get(MemoryId(1));
assert_eq!(memory_0.size(), 300);
assert_eq!(memory_1.size(), 100);
let mut buf = vec![0; 1000];
memory_0.read(0, &mut buf);
assert_eq!(buf, vec![1; 1000]);
memory_1.read(0, &mut buf);
assert_eq!(buf, vec![2; 1000]);
}
#[test]
fn stability() {
let mem = make_memory();
let mem_mgr = MemoryManager::init_with_bucket_size(mem.clone(), 1);
let data: Vec<_> = (0u8..=255u8)
.cycle()
.take((WASM_PAGE_SIZE * 2 + 901) as usize)
.collect();
const MEMORIES: u8 = 3;
let mut memory_id = 0;
let mut next_memory = || {
memory_id += 1;
memory_id %= MEMORIES;
mem_mgr.get(MemoryId(memory_id))
};
for _ in 0..MEMORIES * 5 {
safe_write(&next_memory(), 0, data.as_slice()).unwrap();
safe_write(&next_memory(), WASM_PAGE_SIZE / 2, data.as_slice()).unwrap();
safe_write(&next_memory(), WASM_PAGE_SIZE - 1, data.as_slice()).unwrap();
safe_write(&next_memory(), WASM_PAGE_SIZE + 1, data.as_slice()).unwrap();
safe_write(
&next_memory(),
2 * WASM_PAGE_SIZE + WASM_PAGE_SIZE / 2,
data.as_slice(),
)
.unwrap();
}
let expected_write = include_bytes!("memory_manager/stability_write.golden");
assert!(expected_write.as_slice() == mem.borrow().as_slice());
let mut read = vec![0; 4 * WASM_PAGE_SIZE as usize];
for _ in 0..MEMORIES * 4 {
next_memory().read(0, &mut read[0..WASM_PAGE_SIZE as usize / 2]);
next_memory().read(
WASM_PAGE_SIZE / 2 - 150,
&mut read[WASM_PAGE_SIZE as usize / 2..WASM_PAGE_SIZE as usize],
);
next_memory().read(
WASM_PAGE_SIZE - 473,
&mut read[WASM_PAGE_SIZE as usize..WASM_PAGE_SIZE as usize * 2],
);
next_memory().read(WASM_PAGE_SIZE * 2, &mut read[WASM_PAGE_SIZE as usize * 2..]);
}
let expected_read = include_bytes!("memory_manager/stability_read.golden");
assert!(expected_read.as_slice() == read.as_slice());
}
#[test]
fn bucket_cache() {
let bucket_cache = BucketCache::new();
assert_eq!(
bucket_cache.get(VirtualSegment {
address: Address::from(0),
length: Bytes::from(1u64)
}),
None
);
bucket_cache.store(
VirtualSegment {
address: Address::from(0),
length: Bytes::from(335u64),
},
Address::from(983),
);
assert_eq!(
bucket_cache.get(VirtualSegment {
address: Address::from(1),
length: Bytes::from(2u64)
}),
Some(Address::from(984))
);
assert_eq!(
bucket_cache.get(VirtualSegment {
address: Address::from(334),
length: Bytes::from(1u64)
}),
Some(Address::from(1317))
);
assert_eq!(
bucket_cache.get(VirtualSegment {
address: Address::from(0),
length: Bytes::from(335u64),
}),
Some(Address::from(983))
);
assert_eq!(
bucket_cache.get(VirtualSegment {
address: Address::from(1),
length: Bytes::from(335u64)
}),
None
);
}
}