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}