use alloc::{collections::BTreeMap, vec::Vec};
use miden_air::RowIndex;
use vm_core::{EMPTY_WORD, Felt, WORD_SIZE, Word, ZERO};
use crate::{ContextId, ExecutionError, MemoryAddress, MemoryError, errors::ErrorContext};
#[derive(Debug, Default)]
pub struct Memory {
memory: BTreeMap<(ContextId, u32), [Felt; WORD_SIZE]>,
}
impl Memory {
pub fn new() -> Self {
Self::default()
}
pub fn read_element(&mut self, ctx: ContextId, addr: Felt) -> Result<Felt, ExecutionError> {
let element = self.read_element_impl(ctx, clean_addr(addr)?).unwrap_or(ZERO);
Ok(element)
}
pub fn read_word(
&self,
ctx: ContextId,
addr: Felt,
clk: RowIndex,
) -> Result<&Word, ExecutionError> {
let addr = clean_addr(addr)?;
let word = self.read_word_impl(ctx, addr, Some(clk))?.unwrap_or(&EMPTY_WORD);
Ok(word)
}
pub fn write_element(
&mut self,
ctx: ContextId,
addr: Felt,
element: Felt,
) -> Result<(), ExecutionError> {
let (word_addr, idx) = split_addr(clean_addr(addr)?);
self.memory
.entry((ctx, word_addr))
.and_modify(|word| {
word[idx as usize] = element;
})
.or_insert_with(|| {
let mut word = [ZERO; WORD_SIZE];
word[idx as usize] = element;
word
});
Ok(())
}
pub fn write_word(
&mut self,
ctx: ContextId,
addr: Felt,
clk: RowIndex,
word: Word,
) -> Result<(), ExecutionError> {
let addr = enforce_word_aligned_addr(ctx, clean_addr(addr)?, Some(clk))?;
self.memory.insert((ctx, addr), word);
Ok(())
}
pub fn get_memory_state(&self, ctx: ContextId) -> Vec<(MemoryAddress, Felt)> {
self.memory
.iter()
.filter(|((c, _), _)| *c == ctx)
.flat_map(|(&(_c, addr), word)| {
let addr: MemoryAddress = addr.into();
[
(addr, word[0]),
(addr + 1_u32, word[1]),
(addr + 2_u32, word[2]),
(addr + 3_u32, word[3]),
]
})
.collect()
}
pub(crate) fn read_element_impl(&self, ctx: ContextId, addr: u32) -> Option<Felt> {
let (word_addr, idx) = split_addr(addr);
self.memory.get(&(ctx, word_addr)).copied().map(|word| word[idx as usize])
}
pub(crate) fn read_word_impl(
&self,
ctx: ContextId,
addr: u32,
clk: Option<RowIndex>,
) -> Result<Option<&Word>, ExecutionError> {
let addr = enforce_word_aligned_addr(ctx, addr, clk)?;
let word = self.memory.get(&(ctx, addr));
Ok(word)
}
}
fn clean_addr(addr: Felt) -> Result<u32, ExecutionError> {
let addr = addr.as_int();
addr.try_into().map_err(|_| {
ExecutionError::MemoryError(MemoryError::address_out_of_bounds(
addr,
&ErrorContext::default(),
))
})
}
fn split_addr(addr: u32) -> (u32, u32) {
let idx = addr % WORD_SIZE as u32;
(addr - idx, idx)
}
fn enforce_word_aligned_addr(
ctx: ContextId,
addr: u32,
clk: Option<RowIndex>,
) -> Result<u32, ExecutionError> {
if addr % WORD_SIZE as u32 != 0 {
return match clk {
Some(clk) => Err(ExecutionError::MemoryError(MemoryError::unaligned_word_access(
addr,
ctx,
Felt::from(clk.as_u32()),
&ErrorContext::default(),
))),
None => Err(ExecutionError::MemoryError(MemoryError::UnalignedWordAccessNoClk {
addr,
ctx,
})),
};
}
Ok(addr)
}