concordevm_lib/
memory.rs

1//! ConcordeVM's Memory system.
2//! 
3//! Provides a symbol table that acts as ConcordeVM's "RAM"
4//! 
5//! We chose to use a symbol table because it's an inherently safer form of memory, especially
6//! once we implement proper scoping for it, since you cannot access memory without having access
7//! to the symbol you need.
8
9use crate::log_and_return_err;
10
11use concordeisa::{memory::Symbol};
12
13use std::any::type_name;
14use std::collections::HashMap;
15use log::error;
16use cloneable_any::CloneableAny;
17use dyn_clone::clone_box;
18
19/// `Data` is a wrapper struct for the actual data stored in memory.
20///
21/// It wraps a `Box<dyn CloneableAny>`, which allows any cloneable type to be stored in it, on the
22/// heap. Data stored in memory must be cloneable, as we need to be able to clone it to get owned
23/// copies when performing certain operations.
24pub struct Data(Box<dyn CloneableAny>);
25
26impl Data {
27    /// Create a new `Data` struct containing a clone of the given value.
28    /// 
29    /// We always clone when creating new Data, since we want to have ownership over the contents,
30    /// and because the lifetime of the passed value is not guaranteed to last as long as we want to.
31    pub fn new<T: Clone + 'static>(value: &T) -> Data {
32        Data(Box::new(value.clone()))
33    }
34
35    /// Downcast the data to a specific type. If the data is the wrong type, returns an error.
36    ///
37    /// The type must implement `Clone`, for reasons described above.
38    pub fn as_type<T: CloneableAny + 'static>(&self) -> Result<&T, String> {
39        match self.0.downcast_ref::<T>() {
40            Some(result) => Ok(result),
41            None => log_and_return_err!("Could not downcast data to {}!", type_name::<T>())
42        }
43    }
44}
45
46impl AsRef<dyn CloneableAny> for Data {
47    fn as_ref(&self) -> &dyn CloneableAny {
48        self.0.as_ref()
49    }
50}
51
52impl Clone for Data {
53    fn clone(&self) -> Data {
54        Data(clone_box(self.as_ref()))
55    }
56}
57
58/// `Memory` is what actually handles reading and writing from the symbol table.
59///
60/// It wraps a `HashMap<Symbol, Data>` and implements basic memory operations over that, including
61/// both typed and untyped reading, writing, and copying.
62#[derive(Clone)]
63pub struct Memory(HashMap<Symbol, Data>);
64
65impl Memory {
66    /// Create a new block of memory
67    pub fn new() -> Memory {
68        Memory(HashMap::new())
69    }
70
71    /// Create a new block of memory with a given capacity
72    #[allow(dead_code)]
73    pub fn with_capacity(size: usize) -> Memory {
74        Memory(HashMap::with_capacity(size))
75    }
76
77    /// Write the given data to the symbol. If the symbol does not already exist, create it.
78    ///
79    /// Returns nothing and should never be able to fail, since any Symbol can we written to, even
80    /// if it is undefined.
81    pub fn write(&mut self, symbol: &Symbol, data: Data) {
82        self.0.insert(symbol.clone(), data);
83    }
84
85    /// Read from the given symbol, returning an untyped `CloneableAny`.
86    ///
87    /// If the symbol does not exist, return an error due to trying to read an undefined symbol. 
88    pub fn read_untyped(&self, symbol: &Symbol) -> Result<&dyn CloneableAny, String> {
89        match self.0.get(symbol) {
90            Some(data) => Ok(data.as_ref()), 
91            None => log_and_return_err!("Tried to read from undefined symbol: {}", symbol.0)
92        }
93    }
94
95    /// Read from the given symbol, expecting a specific type. Guaranteed to return that type or error.
96    ///
97    /// If the symbol does not exist, return an error due to trying to read an undefined symbol. If the symbol does exist, but is
98    /// not of the expected type, return an error.
99    pub fn read_typed<T: CloneableAny + 'static>(&self, symbol: &Symbol) -> Result<&T, String> {
100        match self.0.get(symbol) {
101            Some(data) => {
102                let typed_data = data.as_type::<T>()?;
103                Ok(typed_data)
104            },
105            None => log_and_return_err!("Tried to read from undefined symbol: {}", symbol.0)
106        }
107    }
108
109    /// Copy the data from source to dest. If dest doesn't exist yet, create it.
110    ///
111    /// If the source doesn't exist, return an error.
112    ///
113    /// While this could arguably be implented at the instruction level, having this be a memory
114    /// level operation may be good for operations besides just copying.
115    pub fn copy(&mut self, source: &Symbol, dest: &Symbol) -> Result<(), String> {
116        match self.0.get(source) {
117            Some(data) => {
118                self.0.insert(dest.clone(), data.clone());
119                Ok(())
120            }
121            None => log_and_return_err!("Couldn't copy undefined symbol {} to {}!", source.0, dest.0)
122        }
123    }
124
125    /// Get an iterator over all of the symbols currently in memory. Useful for debugging purposes.
126    pub fn dump(&self) -> HashMap<Symbol, Data> {
127        self.0.clone()
128    }
129}
130
131impl Default for Memory {
132   fn default() -> Self {
133       Memory::new()
134   } 
135}