use alloc::{
collections::{BTreeMap, btree_map::Entry},
vec::Vec,
};
use miden_air::trace::RowIndex;
use miden_core::WORD_SIZE;
use super::{Felt, INIT_MEM_VALUE, MemoryError, Word};
use crate::{ContextId, MemoryAddress};
#[derive(Debug, Default)]
pub struct MemorySegmentTrace(BTreeMap<u32, Vec<MemorySegmentAccess>>);
impl MemorySegmentTrace {
pub fn get_value(&self, addr: u32) -> Option<Felt> {
let (word_addr, addr_idx_in_word) = addr_to_word_addr_and_idx(addr);
match self.0.get(&word_addr) {
Some(addr_trace) => {
addr_trace.last().map(|access| access.word()[addr_idx_in_word as usize])
},
None => None,
}
}
pub fn get_word(&self, addr: u32) -> Result<Option<Word>, ()> {
if !addr.is_multiple_of(WORD_SIZE as u32) {
return Err(());
}
let (word_addr, _) = addr_to_word_addr_and_idx(addr);
match self.0.get(&word_addr) {
Some(addr_trace) => Ok(addr_trace.last().map(|access| access.word())),
None => Ok(None),
}
}
pub fn get_state_at(&self, clk: RowIndex) -> Vec<(MemoryAddress, Felt)> {
let mut result: Vec<(MemoryAddress, Felt)> = Vec::new();
if clk == 0 {
return result;
}
let search_clk: u64 = (clk - 1).into();
for (&addr, addr_trace) in self.0.iter() {
match addr_trace
.binary_search_by(|access| access.clk().as_canonical_u64().cmp(&search_clk))
{
Ok(i) => {
let word_addr = addr_trace[i].word();
result.extend([
(MemoryAddress(addr), word_addr[0]),
(MemoryAddress(addr + 1), word_addr[1]),
(MemoryAddress(addr + 2), word_addr[2]),
(MemoryAddress(addr + 3), word_addr[3]),
]);
},
Err(i) => {
if i > 0 {
let word_addr = addr_trace[i - 1].word();
result.extend([
(MemoryAddress(addr), word_addr[0]),
(MemoryAddress(addr + 1), word_addr[1]),
(MemoryAddress(addr + 2), word_addr[2]),
(MemoryAddress(addr + 3), word_addr[3]),
]);
}
},
}
}
result
}
pub fn read(&mut self, ctx: ContextId, addr: u32, clk: Felt) -> Result<Felt, MemoryError> {
let (word_addr, addr_idx_in_word) = addr_to_word_addr_and_idx(addr);
let word = self.read_word_helper(
ctx,
word_addr,
clk,
MemoryAccessType::Element { addr_idx_in_word },
)?;
Ok(word[addr_idx_in_word as usize])
}
pub fn read_word(
&mut self,
ctx: ContextId,
word_addr: u32,
clk: Felt,
) -> Result<Word, MemoryError> {
debug_assert!(word_addr.is_multiple_of(4), "unaligned word access: {word_addr}");
let (word_addr, _) = addr_to_word_addr_and_idx(word_addr);
self.read_word_helper(ctx, word_addr, clk, MemoryAccessType::Word)
}
pub fn write(
&mut self,
ctx: ContextId,
addr: u32,
clk: Felt,
value: Felt,
) -> Result<(), MemoryError> {
let (word_addr, addr_idx_in_word) = addr_to_word_addr_and_idx(addr);
match self.0.entry(word_addr) {
Entry::Vacant(vacant_entry) => {
let word = {
let mut word = Word::default();
word[addr_idx_in_word as usize] = value;
word
};
let access = MemorySegmentAccess::new(
clk,
MemoryOperation::Write,
MemoryAccessType::Element { addr_idx_in_word },
word,
);
vacant_entry.insert(vec![access]);
Ok(())
},
Entry::Occupied(mut occupied_entry) => {
let addr_trace = occupied_entry.get_mut();
if addr_trace.last().expect("empty address trace").clk() == clk {
Err(MemoryError::IllegalMemoryAccess { ctx, addr, clk })
} else {
let word = {
let mut last_word = addr_trace.last().expect("empty address trace").word();
last_word[addr_idx_in_word as usize] = value;
last_word
};
let access = MemorySegmentAccess::new(
clk,
MemoryOperation::Write,
MemoryAccessType::Element { addr_idx_in_word },
word,
);
addr_trace.push(access);
Ok(())
}
},
}
}
pub fn write_word(
&mut self,
ctx: ContextId,
addr: u32,
clk: Felt,
word: Word,
) -> Result<(), MemoryError> {
debug_assert!(addr.is_multiple_of(4), "unaligned memory access: {addr}");
let (word_addr, _) = addr_to_word_addr_and_idx(addr);
let access =
MemorySegmentAccess::new(clk, MemoryOperation::Write, MemoryAccessType::Word, word);
match self.0.entry(word_addr) {
Entry::Vacant(vacant_entry) => {
vacant_entry.insert(vec![access]);
Ok(())
},
Entry::Occupied(mut occupied_entry) => {
let addr_trace = occupied_entry.get_mut();
if addr_trace.last().expect("empty address trace").clk() == clk {
Err(MemoryError::IllegalMemoryAccess { ctx, addr, clk })
} else {
addr_trace.push(access);
Ok(())
}
},
}
}
pub(super) fn inner(&self) -> &BTreeMap<u32, Vec<MemorySegmentAccess>> {
&self.0
}
pub(super) fn into_inner(self) -> BTreeMap<u32, Vec<MemorySegmentAccess>> {
self.0
}
fn read_word_helper(
&mut self,
ctx: ContextId,
word_addr: u32,
clk: Felt,
access_type: MemoryAccessType,
) -> Result<Word, MemoryError> {
match self.0.entry(word_addr) {
Entry::Vacant(vacant_entry) => {
let access = MemorySegmentAccess::new(
clk,
MemoryOperation::Read,
access_type,
INIT_MEM_VALUE,
);
vacant_entry.insert(vec![access]);
Ok(INIT_MEM_VALUE)
},
Entry::Occupied(mut occupied_entry) => {
let addr_trace = occupied_entry.get_mut();
let last_access = addr_trace.last().expect("empty address trace");
if last_access.clk() == clk && last_access.operation() == MemoryOperation::Write {
Err(MemoryError::IllegalMemoryAccess { ctx, addr: word_addr, clk })
} else {
let last_word = addr_trace.last().expect("empty address trace").word();
let access = MemorySegmentAccess::new(
clk,
MemoryOperation::Read,
access_type,
last_word,
);
addr_trace.push(access);
Ok(last_word)
}
},
}
}
#[cfg(test)]
pub fn num_accessed_words(&self) -> usize {
self.0.len()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MemoryOperation {
Read,
Write,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MemoryAccessType {
Element { addr_idx_in_word: u8 },
Word,
}
#[derive(Copy, Debug, Clone)]
pub struct MemorySegmentAccess {
clk: Felt,
operation: MemoryOperation,
access_type: MemoryAccessType,
word: Word,
}
impl MemorySegmentAccess {
fn new(clk: Felt, op: MemoryOperation, access_type: MemoryAccessType, word: Word) -> Self {
Self { clk, operation: op, access_type, word }
}
pub(super) fn clk(&self) -> Felt {
self.clk
}
pub(super) fn operation(&self) -> MemoryOperation {
self.operation
}
pub(super) fn access_type(&self) -> MemoryAccessType {
self.access_type
}
pub(super) fn word(&self) -> Word {
self.word
}
}
pub fn addr_to_word_addr_and_idx(addr: u32) -> (u32, u8) {
let idx = addr % WORD_SIZE as u32;
(addr - idx, idx as u8)
}