1use std::vec;
4
5use crate::{
6 arch::{
7 InstructionAddress, LocalAddress, Word, ARGUMENT_LOCALS, CODE_SIZE,
8 LOCALS_SIZE, RETURN_LOCALS,
9 },
10 opcode::{Op, IMM_BITS, IMM_EXT_BITS},
11};
12
13macro_rules! sign_extend_to {
14 ($t:ty, $value:expr, $bits:expr) => {{
15 let value = $value as $t;
16 let sign_bit = 1 << ($bits - 1);
17 if value & sign_bit != 0 {
18 let extension = !((1 << $bits) - 1);
19 value | extension
20 } else {
21 value
22 }
23 }};
24}
25
26struct StackFrame {
27 locals: [Word; LOCALS_SIZE],
28 return_address: InstructionAddress,
29}
30
31pub struct VM {
32 code: Box<[Word]>,
33 code_length: InstructionAddress,
34 call_stack: Vec<StackFrame>,
35 ip: InstructionAddress,
36}
37
38#[derive(Debug)]
39pub enum VMError {
40 InvalidOp,
41 InvalidArgs,
42 CodeOversized,
43 InvalidIP,
44}
45
46pub type VMResult = Result<(), VMError>;
47
48impl Default for VM {
49 fn default() -> Self {
50 Self {
51 code: vec![0; CODE_SIZE].into_boxed_slice(),
52 code_length: 0,
53 call_stack: vec![],
54 ip: 0,
55 }
56 }
57}
58
59impl VM {
60 pub fn load(&mut self, program: &[Op]) -> VMResult {
61 for (i, op) in program.iter().enumerate() {
62 self.code[i] = op.encode_packed();
63 }
64 self.code_length = program.len();
65 self.call_stack.clear();
66 self.call_stack.push(StackFrame {
67 locals: [0; LOCALS_SIZE],
68 return_address: 0, });
72 Ok(())
73 }
74
75 pub fn run(&mut self) -> VMResult {
76 while !self.call_stack.is_empty() {
77 self.step()?;
78 }
79 Ok(())
80 }
81
82 fn decode_op(&self) -> Op {
83 Op::decode_packed(self.code[self.ip]).unwrap()
84 }
85
86 fn step(&mut self) -> VMResult {
87 match self.decode_op() {
88 Op::Mov(to, from) => {
89 self.write_local(to, self.read_local(from));
90 self.jump_to(self.ip + 1)
91 }
92 Op::MovI(to, constant) => {
93 let extended = sign_extend_to!(Word, constant, IMM_BITS);
94 self.write_local(to, extended);
95 self.jump_to(self.ip + 1)
96 }
97 Op::Add(to, first, second) => {
98 let sum = self.read_local(first) + self.read_local(second);
99 self.write_local(to, sum);
100 self.jump_to(self.ip + 1)
101 }
102 Op::Ret => {
103 let frame = self.current_frame();
104 let ra = frame.return_address;
105
106 let popped = self.call_stack.pop().expect(
107 "call stack expected to always have one frame while running.",
108 );
109 if let Some(frame_below) = self.call_stack.last_mut() {
110 frame_below.locals[RETURN_LOCALS]
111 .copy_from_slice(&popped.locals[RETURN_LOCALS]);
112 }
113
114 self.jump_to(ra)
115 }
116 Op::Call(offset) => {
117 let as_usize: usize = offset
118 .try_into()
119 .expect("illegal offset, too large for platform"); let extended =
121 sign_extend_to!(InstructionAddress, as_usize, IMM_EXT_BITS);
122 let new_ip = self.ip.wrapping_add(extended);
123 let mut new_frame = StackFrame {
126 locals: [0; LOCALS_SIZE],
127 return_address: self.ip + 1,
128 };
129 new_frame.locals[ARGUMENT_LOCALS].copy_from_slice(
130 &self.current_frame().locals[ARGUMENT_LOCALS],
131 );
132
133 self.call_stack.push(new_frame);
134
135 self.jump_to(new_ip)
136 }
137 Op::Nop => Ok(()),
138 }?;
139
140 Ok(())
141 }
142
143 fn jump_to(&mut self, new_ip: usize) -> VMResult {
144 if new_ip >= self.code_length {
145 return Err(VMError::InvalidIP);
146 };
147
148 self.ip = new_ip;
149
150 Ok(())
151 }
152
153 fn read_local(&self, address: LocalAddress) -> Word {
154 self.current_frame().locals[address as usize]
155 }
156
157 fn write_local(&mut self, address: LocalAddress, value: Word) {
158 self.current_frame_mut().locals[address as usize] = value;
159 }
160
161 fn current_frame(&self) -> &StackFrame {
162 self.call_stack.last().expect(
163 "call stack expected to always have one frame while running.",
164 )
165 }
166
167 fn current_frame_mut(&mut self) -> &mut StackFrame {
168 self.call_stack.last_mut().expect(
169 "call stack expected to always have one frame while running.",
170 )
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::VM;
177 use crate::opcode::{ExtendedImmediate, Op};
178
179 #[test]
180 fn basic_program() {
181 let mut vm = VM::default();
182 vm.load(&[Op::MovI(0, 1), Op::MovI(1, 2), Op::Add(2, 0, 1), Op::Ret])
183 .expect("invalid program");
184
185 for _ in 0..3 {
186 vm.step().expect("failed to run program");
187 }
188
189 assert_eq!(1, vm.current_frame().locals[0]);
190 assert_eq!(2, vm.current_frame().locals[1]);
191 assert_eq!(3, vm.current_frame().locals[2]);
192
193 vm.step().expect("failed to run program");
194 assert!(vm.call_stack.is_empty());
195 }
196
197 #[test]
198 fn call_return() {
199 let mut vm = VM::default();
200 vm.load(&[
201 Op::MovI(0, 1),
203 Op::MovI(1, 2),
204 Op::Call(2), Op::Ret,
206 Op::Add(0, 0, 1),
208 Op::Ret,
209 ])
210 .expect("invalid program");
211
212 for _ in 0..5 {
213 vm.step().expect("failed to run program");
214 }
215
216 assert_eq!(3, vm.current_frame().locals[0]);
217 assert_eq!(2, vm.current_frame().locals[1]);
218
219 vm.step().expect("failed to run program");
220 assert!(vm.call_stack.is_empty());
221 }
222
223 #[test]
224 fn call_neg_offset() {
225 let mut vm = VM::default();
226 let program = [
227 Op::MovI(0, 3),
229 Op::Call(4), Op::Ret,
231 Op::Add(0, 0, 1),
233 Op::Ret,
234 Op::Mov(1, 0),
236 Op::Call((0 as ExtendedImmediate).wrapping_sub(3)), Op::Ret,
238 ];
239
240 vm.load(&program).expect("invalid program");
241
242 for _ in 0..7 {
243 vm.step().expect("failed to run program");
244 }
245
246 assert_eq!(6, vm.current_frame().locals[0]);
247
248 vm.step().expect("failed to run program");
249 assert!(vm.call_stack.is_empty());
250 }
251}