# Memory Trait: Two Years Later
###### tags: `technical`
## Flash
I think it makes sense to use the traits in [`embedded-storage`](https://docs.rs/embedded-storage/latest/embedded_storage/index.html); specifically:
## RAM Cache
```rust=
// works with both flash units that are memory mapped
// and those that are external (we don't actually care
// about this, the actual read optimization is handled
// by the underlying traits; we just need to know whether
// to figure out our offset based on address math or
// whether we're going to be handed an offset outright)
trait PersistentStorage {
type Error: Debug;
const READ_SIZE: usize;
const WRITE_SIZE: usize;
// Must be aligned.
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error>;
// Will handle erasing for you; must be aligned.
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error>;
//
fn capacity(&self) -> usize;
}
struct Empty;
impl PersistentStorage {
type Error = !;
const READ_SIZE: usize = 1;
const WRITE_SIZE: usize = 1;
fn read(&mut self, _offset: u32, _bytes: &mut [u8]) -> Result<(), !> { panic!() }
fn write(&mut self, _offset: u32, _bytes: &[u8]) -> Result<(), !> { panic!() }
fn capacity(&self) -> usize { 0 }
}
// TODO: hoist the &mut persistent_storage up to here so
// that we can report on it in `capacity()` so it's
// included in bound checks?
//
// actually i think this is not worth it...
//
// we just have checks in the constructor for RamCache and
// that's fine
#[derive(Debug, ...)]
pub struct Storage<S: es::Storage>(pub S);
impl<S: es::Storage> PersistentStorage for Storage<S> {
type Error = S::Error;
const READ_SIZE: usize = 1;
const WRITE_SIZE: usize = 1;
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
self.0.read(offset, bytes)
}
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
self.0.write(offset, bytes)
}
fn capacity(&self) -> usize { self.0.capacity() }
}
#[derive(Debug, ...)]
pub struct NorFlash<F: es::nor_flash::NorFlash>(pub F);
impl<F: es::nor_flash::NorFlash> PersistentStorage for NorFlash<F> {
type Error = F::Error;
const READ_SIZE: usize = F::READ_SIZE;
const WRITE_SIZE: usize = {
// Since writes now involve erasing first,
// the write size is now whichever is larger
// of the write and erase sizes:
let smaller = min(F::ERASE_SIZE, F::WRITE_SIZE);
let larger = max(F::ERASE_SIZE, F::WRITE_SIZE);
// We also need to ensure that the smaller number
// (whether it's the write size or the erase size)
// is a factor of the larger number:
assert!(larger % smaller == 0);
larger
};
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
self.0.read(offset, bytes)
}
// write now requires alignment to erase size too
// this is only an issue if erase size is larger than
// write size or not a factor/multiple of write size
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
es::nor_flash::check_write(self.0, offset, bytes.len())?;
self.0.erase(offset, offset.checked_add(bytes.len()).unwrap())?;
self.0.write(offset, bytes)
}
fn capacity(&self) -> usize { self.0.capacity() }
}
pub enum PersistentStorageAllocation<const NUM_PAGES: usize> {
MemMapped {
/// Device specific! memory mapped address
///
/// FlashExt::address, for example
persistent_storage_base_memory_address: usize,
allocation: &mut [u16; NUM_PAGES * WORDS_IN_A_PAGE],
},
// for devices that are mem mapped we assume the `read`
// function does the "smart" thing and just creates the
// appropriate pointer and reads from it; we never explicitly touch `allocation`, it's purely just a compile time check. (volatile_read once to make sure it's not optimized out, TODO)
Offset(usize),
}
impl<const NUM_PAGES: usize> PersistentStorageAllocation {
fn offset(&self) -> u32 {
match self {
Self::MemMapped { persistent_storage_base_memory_address, allocation } => {
(allocation as *mut _ as usize - persistent_storage_base_memory_address).try_into().unwrap()
},
Self::Offset(offs) => offs,
}
}
}
enum PageState {
#[default]
Main,
// in RAM but clean
InRam(u16),
// in RAM but dirty
InRamDirty(u16),
// in `Swap` (and thus, dirty)
Swapped(u16),
}
type PageMap = [PageState; NUM_PAGES];
trait EvictionPolicy<const RAM_PAGES: usize> {
fn evict(&mut self, page_map: &PageMap) -> u16;
// hooks to inform the policy:
fn loaded_page(&mut self, pg_idx: u8) { } // when a page is first loaded into ram
fn written_to_page(&mut self, pg_idx: u8) { } // when a page is first marked as dirty
fn dropped_page(&mut self, pg_idx: u8) { } // evicted clean page
fn swapped_page(&mut self, pg_idx: u8) { } // evicted dirty page
fn read_from_page(&mut self, pg_idx: u8) { } // any read from the page
fn write_to_page(&mut self, pg_idx: u8) { } // any write from the page
}
// naive example!
struct EvictFirst;
impl<const N: usize> EvictionPolicy<N> for EvictFirst {
fn evict(&mut self, page_map: &PageMap) -> u16 {
for (idx, e) in page_map.iter() {
match e {
// TODO: have this assertion at the caller
PageState::InRam(idx) | PageState::InRamDirty(idx) => return idx,
_ => {},
}
}
unreachable!()
}
}
// struct EvictClean<Then: EvictionPolicy>(Then);
// impl<T: EvictionPolicy> EvictionPolicy for EvictClean<T> {
// fn evict(&mut self, page_map: &PageMap) -> u16 {
// for (idx, e) in page_map.iter() {
// if let PageState::
// }
// }
// // TODO: forward other methods to inside...
// }
struct Map<const ENTRIES: usize, Val> {
[Option<Val>; ENTRIES]
}
// fn set
// fn inc
impl<const E: usize, Val: Cmp> Map<E, Val> {
fn min(&self) -> Option<u8> { // idx
}
}
struct EvictLruLevel<Then: EvictionPolicy = EvictFirst, const RAM_PAGES: usize> {
filter: fn(&PageState) -> bool,
then: Then,
counter: usize, // starts at 0
lru: Lru<
}
impl<const N: usize, T: EvictionPolicy> EvictionPolicy<N> for EvictLruLevel<N, T> {
fn evict()
}
struct EvictFreqLevel<Then: EvictionPolicy = EvictFirst, const RAM_PAGES: usize> {
filter: fn(&PageState) -> bool,
then: Then,
counter: usize,
freq:
}
// Really we only want to bound on `Storage` but...
// that does clever "is subset" checks to elide erases
// and "merge buffer" things for unaligned writes.
//
// Our use case explicitly does not make use of unaligned
// writes and the "is subset" check (which reads in all
// of the current data which we're overwriting) is unlikely
// to benefit us.
//
// So, we use the NorFlash traits directly here.
//
// However, we also want to provide the _ability_ to use
// `Storage` impls, so... we'll introduce some indirection
// (another trait).
struct RamCache<
/// Only read in pages on write if true, else eagerly.
// /
// / when true, assumes a flash unit that's mapped into
// / the address space
// /
// / if you wish to use an external flash unit you must
// / set this to false
const LAZY_LOAD_PAGES: bool,
const NUM_RAM_PAGES: usize,
S: PersistentStorage,
E: EvictionPolicy,
const NUM_SWAP_PAGES: usize = 0,
Swap: PersistentStorage = Empty,
> {
pages: [
[u16; PAGE_SIZE]; NUM_RAM_PAGES
],
page_map: PageMap,
main_allocation: PersistentStorageAllocation<LC3_PAGES>,
main_storage: S,
evic: E,
page_table: [
],
swap_allocation: PersistentStorageAllocation<NUM_SWAP_PAGES>, // defaults to offset = 0
swap_storage: Swap,
/// ASSERT that S::WRITE_SIZE <= PAGE SIZE
/// ASSERT that PAGE SIZE % S::WRITE_SIZE == 0
/// ASSERT that
/// if LAZY_LOAD_PAGES:
/// S::READ_SIZE <= 2 (for direct access to words...)
/// else /* eager page load */:
/// S::READ_SIZE <= PAGE SIZE
/// PAGE SIZE % S::READ_SIZE == 0
/// ASSERT that NUM_PAGES >= 0
}
impl<S, NUM_PAGES> {
/// SAFETY: Persistent Storage impl given must match the `PersistentStorageAllocation` given, offset (if specified directly) must be correct and not overwrite program data, etc.
unsafe fn new(persistent_storage_allocation: PersistentStorageAllocation<NUM_PAGES>, storage: S, swap_storage: Swap) -> Self {
assert!(self.main_allocation.offset() + size_of::<u16> * LC3_PAGES * WORDS_IN_A_PAGE <= storage.capacity());
assert!(self.swap_allocation.offset() + size_of::<u16> * NUM_SWAP_PAGES * WORDS_IN_A_PAGE <= swap_storage.capacity());
...
}
// construct the wrappers for you...
unsafe fn from_storage() -> Self;
unsafe fn from_nor_flash() -> Self;
}
```
## Memory Impl