concordevm_lib/
cpu.rs

1//! ConcordeVM's CPU.
2//!
3//! Provides a CPU struct that executes instructions in memory, according to a stack of instruction
4//! pointers.
5//!
6//! Instructions are stored as `Vec<Instruction>`s under symbols in memory. 
7
8use crate::instructions::execute_instruction;
9use crate::memory::*;
10
11use concordeisa::{instructions::Instruction, memory::Symbol};
12
13use log::info;
14use std::vec::Vec;
15
16/// `ExecutionPointer`s represent a location in memory where code is being executed.
17///
18/// Contains the symbol under which the instructions are stored, as well as the index of the
19/// instruction currently being executed.
20#[derive(Clone, Eq, PartialEq)]
21pub struct ExecutionPointer {
22    pub symbol: Symbol,
23    pub index: usize,
24}
25
26/// The `ExecutionStack` is the stack of the CPU. It stores `ExecutionPointer`s to every block of
27/// code being executed at any moment. 
28#[derive(Clone)]
29pub struct ExecutionStack(Vec<ExecutionPointer>);
30
31impl ExecutionStack {
32    /// Create a new empty `ExecutionStack`.
33    pub fn new() -> ExecutionStack {
34        ExecutionStack(Vec::new())
35    }
36
37    /// Delete everything in the stack.
38    pub fn clear(&mut self) {
39        self.0.clear();
40    }
41
42    /// Get the top pointer on the stack. Returns None if the stack is empty.
43    pub fn top(&self) -> Option<&ExecutionPointer>{
44        self.0.last()
45    }
46
47    /// Increment the index of the top pointer on the stack.
48    pub fn increment(&mut self) {
49        self.0.last_mut().unwrap().index += 1;
50    }
51
52    /// Jump execution to a given symbol. Will not error, even if the symbol is undefined.
53    pub fn jump(&mut self, target: &Symbol) {
54        info!("Jumped to {}!", target.0);
55        self.0.push(ExecutionPointer { symbol: target.clone(), index: 0 });
56    }
57
58    /// Return execution to the previous location. Will not error.
59    pub fn ret(&mut self) {
60        info!("Returned!");
61        self.0.pop();
62    }
63
64    pub fn dump(&self) -> Vec<ExecutionPointer> {
65        self.0.clone()
66    }
67}
68
69/// The `CPU` is where instruction reading and execution is handled.
70///
71/// Contains `Memory`, as well as an `ExecutionStack`. These are used to read and execute
72/// instructions.
73#[allow(clippy::upper_case_acronyms)]
74pub struct CPU {
75    memory: Memory,
76    stack: ExecutionStack,
77}
78
79impl CPU {
80    /// Create a new `CPU`. Initializes both the memory and stack to be empty.
81    pub fn new() -> CPU {
82        CPU {
83            memory: Memory::new(),
84            stack: ExecutionStack::new(),
85        }
86    }
87    
88    /// Load instructions into memory at a given symbol.
89    pub fn load_instructions(&mut self, instructions: &Vec<Instruction>, symbol: &Symbol) {
90        self.memory.write(symbol, Data::new(instructions));
91        info!("Loaded {} instructions into symbol {}", instructions.len(), symbol.0);
92    }
93
94    /// Get the CPU ready to start executing code. Clears the stack and jumps to the entrypoint.
95    pub fn init_execution(&mut self, entrypoint: &Symbol) {
96        self.stack.clear();
97        self.stack.jump(entrypoint);
98    }
99
100    /// Complete one CPU cycle. Returns false iff the stack is empty. Returns an error if something
101    /// goes wrong during execution. Returns true otherwise.
102    ///
103    /// Each CPU cycle does the following:
104    ///   - Checks if the stack is empty. If it is, return false. If not, continue.
105    ///   - Reads the instructions that the `ExecutionPointer` at the top of the stack points to.
106    ///   - If we're done execution there, return from that block, and return true. 
107    ///   - Otherwise, read the instruction at the given index and execute it.
108    ///   - If the instruction errors, return the error. Otherwise, return true.
109    ///
110    /// One CPU cycle does not necessarily map to one instruction, as a CPU cycle is used every time
111    /// we pop an execution pointer off of the stack when we are done executing those instructions. This is
112    /// technically equivalent to every instruction vector having a return instruction tacked on at
113    /// the end, but isn't handled the same way.
114    pub fn cycle(&mut self) -> Result<bool, String> {
115        if let Some(exec_pointer) = self.stack.top() {
116            info!("Currently executing code at symbol [{}], index {}", exec_pointer.symbol.0, exec_pointer.index);
117            let instruction_vec = self.memory.read_typed::<Vec<Instruction>>(&exec_pointer.symbol)?;
118            // This execution pointer has reached the end of it's code, so we can return
119            if instruction_vec.len() <= exec_pointer.index {
120                info!("Execution pointer at symbol {} has reached the end of it's code at index {}!", exec_pointer.symbol.0, exec_pointer.index);
121                self.stack.ret();
122                if self.stack.top().is_none() {
123                    info!("CPU stack is empty!");
124                    return Ok(false);
125                }
126                self.stack.increment();
127            } else {
128                let instruction = &instruction_vec[exec_pointer.index].clone();
129                execute_instruction(instruction, &mut self.memory, &mut self.stack)?;
130            }
131            Ok(true)
132        }
133        else {
134            info!("CPU Stack is empty!");
135            Ok(false)
136        }
137    }
138
139    /// Get a clone of the memory for debugging.
140    pub fn get_memory(&self) -> Memory {
141        self.memory.clone()
142    }
143
144    /// Get a clone of the stack for debugging.
145    pub fn get_stack(&self) -> ExecutionStack {
146        self.stack.clone()
147    }
148}
149
150impl Default for CPU {
151   fn default() -> Self {
152       CPU::new()
153   } 
154}