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}