lc3_ensemble/sim/frame.rs
1//! The frame stack and call frame management.
2//!
3//! This module exposes:
4//! - [`FrameStack`]: The frame stack used by the Simulator.
5//! - [`Frame`]: All the data from a given frame.
6//! - [`ParameterList`]: An enum which defines the signature of a subroutine or trap.
7//!
8//! # Usage
9//!
10//! [`FrameStack`] is available on the [`Simulator`] via the `frame_stack` field, and the extent of its functionality
11//! depends on the [`Simulator`]'s `debug_frames` flag.
12//!
13//! If `debug_frames` is not enabled, `FrameStack` only holds the frame depth
14//! (how many frames deep the simulator is in execution) in [`FrameStack::len`]:
15//! - After a JSR instruction, the frame count increases by one.
16//! - After a RET instruction, the frame count decreases by one.
17//!
18//! If `debug_frames` is enabled, `FrameStack` also holds more data from a given frame,
19//! such as caller and callee address. This is accessible via [`FrameStack::frames`].
20//!
21//! The simulator also has the capability to keep track of the arguments introduced
22//! to a frame and the frame pointer address of the frame; however, this requires
23//! defining a subroutine signature using [`FrameStack::set_subroutine_def`].
24//!
25//! ```
26//! use lc3_ensemble::parse::parse_ast;
27//! use lc3_ensemble::asm::assemble;
28//! use lc3_ensemble::sim::Simulator;
29//! use lc3_ensemble::sim::frame::ParameterList;
30//! use lc3_ensemble::ast::Reg::R0;
31//!
32//! let src = "
33//! .orig x3000
34//! AND R0, R0, #0
35//! JSR FOO
36//! HALT
37//!
38//! FOO:
39//! ADD R0, R0, #10
40//! RET
41//! .end
42//! ";
43//! let ast = parse_ast(src).unwrap();
44//! let obj_file = assemble(ast).unwrap();
45//!
46//! // debug_frames: false
47//! let mut sim = Simulator::new(Default::default());
48//! sim.load_obj_file(&obj_file);
49//!
50//! // AND R0, R0, #0
51//! assert_eq!(sim.frame_stack.len(), 0);
52//! // JSR FOO
53//! sim.step_in().unwrap();
54//! assert_eq!(sim.frame_stack.len(), 0);
55//! // ADD R0, R0, #10
56//! sim.step_in().unwrap();
57//! assert_eq!(sim.frame_stack.len(), 1);
58//! // RET
59//! sim.step_in().unwrap();
60//! assert_eq!(sim.frame_stack.len(), 1);
61//! // HALT
62//! sim.step_in().unwrap();
63//! assert_eq!(sim.frame_stack.len(), 0);
64//!
65//! // debug_frames: true
66//! sim.flags.debug_frames = true;
67//! sim.reset();
68//! sim.load_obj_file(&obj_file);
69//!
70//! let pl = ParameterList::with_pass_by_register(&[("input", R0)], Some(R0)); // fn(R0) -> R0
71//! sim.frame_stack.set_subroutine_def(0x3003, pl);
72//!
73//! sim.step_in().unwrap();
74//! sim.step_in().unwrap();
75//!
76//! let frames = sim.frame_stack.frames().unwrap();
77//! assert_eq!(frames.len(), 1);
78//! assert_eq!(frames[0].caller_addr, 0x3001);
79//! assert_eq!(frames[0].callee_addr, 0x3003);
80//! assert_eq!(frames[0].arguments[0].get(), 0);
81//! ```
82//!
83//! # Subroutine definitions
84//!
85//! Subroutine definitions come in two flavors:
86//! standard LC-3 calling convention, and pass-by-register calling convention.
87//!
88//! With standard LC-3 calling convention subroutines:
89//! - the arguments and return value are placed on the stack in their standard positions
90//! - the notion of a frame pointer exists, and is present in [`Frame`]'s data
91//! - when declaring a LC-3 calling convention subroutine, you only specify the argument names:
92//! ```
93//! # use lc3_ensemble::sim::frame::ParameterList;
94//! // equivalent to fn(arg1, arg2) -> _
95//! let pl = ParameterList::with_calling_convention(&["arg1", "arg2"]);
96//! ```
97//!
98//! With pass-by-register calling convention subroutine:
99//! - the arguments are placed in specific registers, and the return value comes from a specific register
100//! - a pass-by-register subroutine has to define an argument name and the associated register per argument:
101//! ```
102//! # use lc3_ensemble::sim::frame::ParameterList;
103//! # use lc3_ensemble::ast::Reg::*;
104//! // equivalent to fn(arg1: R0, arg2: R1) -> _
105//! let pl = ParameterList::with_pass_by_register(&[("arg1", R0), ("arg2", R1)], None);
106//! // equivalent to fn(arg1: R0, arg2: R1) -> R0
107//! let pl = ParameterList::with_pass_by_register(&[("arg1", R0), ("arg2", R1)], Some(R0));
108//! ```
109//!
110//! The argument names are not used by the [`Simulator`], but can be accessed by other APIs
111//! via the [`FrameStack::get_subroutine_def`] method.
112//!
113//! [`Simulator`]: super::Simulator
114
115use std::collections::HashMap;
116
117use crate::ast::Reg::{R0, R6};
118use crate::ast::Reg;
119
120use super::mem::{MemArray, RegFile, Word};
121
122
123/// A list of parameters, used to define the signature of a subroutine or trap.
124#[derive(Clone, PartialEq, Eq, Hash)]
125pub enum ParameterList {
126 /// A parameter list defined with standard LC-3 calling convention.
127 ///
128 /// If a subroutine is defined with this parameter list variant,
129 /// arguments are pulled from the stack at FP+4 to FP+4+n.
130 ///
131 /// The `params` field defines the names of the parameters accepted by this
132 /// subroutine or trap.
133 ///
134 /// This variant can be readily created with [`ParameterList::with_calling_convention`].
135 CallingConvention {
136 /// Names for the parameters.
137 params: Vec<String>
138 },
139
140 /// A parameter list defined by pass-by-register calling convention.
141 ///
142 /// If a subroutine is defined with this parameter list variant,
143 /// arguments are pulled from registers.
144 ///
145 /// The `params` field defines the name of the parameters accepted by this
146 /// subroutine or trap and the register where the argument is located.
147 ///
148 /// The `ret` field defines which register the return value is located in
149 /// (if it exists).
150 ///
151 /// This variant can be readily created with [`ParameterList::with_pass_by_register`].
152 PassByRegister {
153 /// Names for the parameters and the register the parameter is located at.
154 params: Vec<(String, Reg)>,
155 /// The register to store the return value in (if there is one).
156 ret: Option<Reg>
157 }
158}
159impl ParameterList {
160 /// Creates a new standard LC-3 calling convention parameter list.
161 pub fn with_calling_convention(params: &[&str]) -> Self {
162 let params = params.iter()
163 .map(|name| name.to_string())
164 .collect();
165
166 Self::CallingConvention { params }
167 }
168
169 /// Creates a new pass-by-register parameter list.
170 pub fn with_pass_by_register(params: &[(&str, Reg)], ret: Option<Reg>) -> Self {
171 let params = params.iter()
172 .map(|&(name, reg)| (name.to_string(), reg))
173 .collect();
174
175 Self::PassByRegister { params, ret }
176 }
177
178 /// Compute the arguments of this parameter list.
179 fn get_arguments(&self, regs: &RegFile, mem: &MemArray, fp: u16) -> Vec<Word> {
180 match self {
181 ParameterList::CallingConvention { params } => {
182 (0..params.len())
183 .map(|i| fp.wrapping_add(4).wrapping_add(i as u16))
184 .map(|addr| mem[addr])
185 .collect()
186 },
187 ParameterList::PassByRegister { params, ret: _ } => {
188 params.iter()
189 .map(|&(_, r)| regs[r])
190 .collect()
191 },
192 }
193 }
194}
195impl std::fmt::Debug for ParameterList {
196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197 match self {
198 Self::CallingConvention { params } => {
199 f.write_str("fn[stdcc](")?;
200 if let Some((first, rest)) = params.split_first() {
201 f.write_str(first)?;
202 for param in rest {
203 f.write_str(", ")?;
204 f.write_str(param)?;
205 }
206 }
207 f.write_str(") -> _")?;
208 Ok(())
209 },
210 Self::PassByRegister { params, ret } => {
211 f.write_str("fn[pass by reg](")?;
212 if let Some(((first_param, first_reg), rest)) = params.split_first() {
213 write!(f, "{first_param} = {first_reg}")?;
214 for (param, reg) in rest {
215 write!(f, ", {param} = {reg}")?;
216 }
217 }
218 f.write_str(")")?;
219 if let Some(ret) = ret {
220 write!(f, " -> {ret}")?;
221 }
222 Ok(())
223 },
224 }
225 }
226}
227
228/// Where this frame came from.
229#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
230pub enum FrameType {
231 /// Frame came from a subroutine call.
232 Subroutine,
233 /// Frame came from a trap call.
234 Trap,
235 /// Frame came from an interrupt.
236 Interrupt
237}
238/// A frame entry, which defines all the known information about a frame.
239///
240/// This information is only exposed by the Simulator if the `debug_frames` flag is enabled.
241#[derive(Debug, Clone)]
242pub struct Frame {
243 /// The memory location of the caller instruction.
244 pub caller_addr: u16,
245
246 /// The memory location of the start of the callee subroutine.
247 ///
248 /// For subroutines, this should point to the start of the callee subroutine.
249 /// For traps and interrupts, this should point to where the entry exists within their respective tables
250 /// (in other words, this value should be `0x00`-`0xFF` for traps and `0x100`-`0x1FF` for interrupts).
251 pub callee_addr: u16,
252
253 /// Whether this frame is from a subroutine call, trap call, or interrupt.
254 pub frame_type: FrameType,
255
256 /// The address of the current frame pointer.
257 ///
258 /// This is only `Some` when:
259 /// - the callee's definition is defined, and
260 /// - the callee's definition is defined with the standard LC-3 calling convention variant
261 pub frame_ptr: Option<Word>,
262
263 /// The arguments of the call.
264 ///
265 /// If the callee's definition is defined, this is a list of the arguments used in the call.
266 /// Otherwise, this is an empty Vec.
267 pub arguments: Vec<Word>
268}
269/// The stack of call frames.
270///
271/// This struct is used within the Simulator to keep track of the frames of subroutine/trap calls.
272/// The amount of information it keeps track of depends on the `debug_frames` flag of the Simulator.
273/// - If the `debug_frames` flag is true, this keeps track of a Vec of [`Frame`]s, which contains a large set of information about each frame.
274/// - If the `debug_frames` flag is false, this only keeps track of the number of frames traversed.
275#[derive(Debug)]
276pub struct FrameStack {
277 /// The number of frames traversed.
278 ///
279 /// At top level execution, `frame_no` == 0.
280 /// Every subroutine/trap call (i.e., JSR, JSRR, TRAP instr.) increments this value,
281 /// and every return call (i.e., RET, RTI, JMP R7 instr.) decrements this value.
282 frame_no: u64,
283
284 /// Function signatures for all traps.
285 trap_defns: HashMap<u8, ParameterList>,
286
287 /// Function signatures for all subroutines.
288 ///
289 /// The simulator does not compute this by default.
290 /// It has to be defined externally by the [`FrameStack::set_subroutine_def`] method.
291 sr_defns: HashMap<u16, ParameterList>,
292
293 /// The frames.
294 ///
295 /// If `None`, this means frames are not being tracked and frame information is ignored.
296 /// If `Some`, frames will be added and removed as subroutines/traps are entered and exited.
297 frames: Option<Vec<Frame>>
298}
299
300impl FrameStack {
301 /// Creates a new frame stack.
302 pub(super) fn new(debug_frames: bool) -> Self {
303 Self {
304 frame_no: 0,
305 trap_defns: HashMap::from_iter([
306 (0x20, ParameterList::with_pass_by_register(&[], Some(R0))),
307 (0x21, ParameterList::with_pass_by_register(&[("char", R0)], None)),
308 (0x22, ParameterList::with_pass_by_register(&[("addr", R0)], None)),
309 (0x23, ParameterList::with_pass_by_register(&[], Some(R0))),
310 (0x24, ParameterList::with_pass_by_register(&[("addr", R0)], None)),
311 (0x25, ParameterList::with_pass_by_register(&[], None)),
312 ]),
313 sr_defns: Default::default(),
314 frames: debug_frames.then(Vec::new)
315 }
316 }
317
318 /// Gets the parameter definition for a trap (if it is defined).
319 pub fn get_trap_def(&self, vect: u8) -> Option<&ParameterList> {
320 self.trap_defns.get(&vect)
321 }
322 /// Gets the parameter definition for a subroutine (if it is defined).
323 ///
324 /// Note that the simulator does not automatically make subroutine definitions.
325 /// Subroutine definitions have to be manually set by the [`FrameStack::set_subroutine_def`] method.
326 pub fn get_subroutine_def(&self, addr: u16) -> Option<&ParameterList> {
327 self.sr_defns.get(&addr)
328 }
329 /// Sets the parameter definition for a subroutine.
330 ///
331 /// This will overwrite any preexisting definition for a given subroutine.
332 pub fn set_subroutine_def(&mut self, addr: u16, params: ParameterList) {
333 self.sr_defns.insert(addr, params);
334 }
335 /// Gets the current number of frames entered.
336 pub fn len(&self) -> u64 {
337 self.frame_no
338 }
339
340 /// Tests whether the frame stack is at top level execution.
341 pub fn is_empty(&self) -> bool {
342 self.frame_no == 0
343 }
344
345 /// Gets the list of current frames (if debug frames are enabled).
346 pub fn frames(&self) -> Option<&[Frame]> {
347 self.frames.as_deref()
348 }
349
350 /// Pushes a new frame to the frame stack.
351 ///
352 /// This should be called at the instruction where a subroutine or trap call occurs.
353 ///
354 /// Note that the `callee` parameter depends on the type of frame:
355 /// - For subroutines, the `callee` parameter represents the start of the subroutine.
356 /// - For traps and interrupts, the `callee` parameter represents the vect (0x00-0xFF for traps, 0x100-0x1FF for interrupts).
357 pub(super) fn push_frame(&mut self, caller: u16, callee: u16, frame_type: FrameType, regs: &RegFile, mem: &MemArray) {
358 self.frame_no += 1;
359 if let Some(frames) = self.frames.as_mut() {
360 let m_plist = match frame_type {
361 FrameType::Subroutine => self.sr_defns.get(&callee),
362 FrameType::Trap => {
363 u8::try_from(callee).ok()
364 .and_then(|addr| self.trap_defns.get(&addr))
365 },
366 FrameType::Interrupt => {
367 // FIXME: Interrupt semantics are not well defined.
368 self.sr_defns.get(&callee)
369 },
370 };
371
372 let (fp, args) = match m_plist {
373 Some(plist @ ParameterList::CallingConvention { .. }) => {
374 // Strictness: We'll let the simulator handle strictness around this,
375 // because we don't want to trigger an error if frame information is incorrect.
376 let fp = regs[R6] - Word::new_init(4);
377 (Some(fp), plist.get_arguments(regs, mem, fp.get()))
378 },
379 Some(plist @ ParameterList::PassByRegister { .. }) => {
380 // pass by register doesn't use the fp parameter
381 // so it doesn't matter value is used for the fp arg
382 (None, plist.get_arguments(regs, mem, 0))
383 },
384 None => (None, vec![])
385 };
386
387 frames.push(Frame {
388 caller_addr: caller,
389 callee_addr: callee,
390 frame_type,
391 frame_ptr: fp,
392 arguments: args,
393 })
394 }
395 }
396
397 /// Pops a frame from the frame stack.
398 ///
399 /// This should be called at the instruction where a return occurs.
400 pub(super) fn pop_frame(&mut self) {
401 self.frame_no = self.frame_no.saturating_sub(1);
402 if let Some(frames) = self.frames.as_mut() {
403 frames.pop();
404 }
405 }
406}
407impl Default for FrameStack {
408 fn default() -> Self {
409 Self::new(false)
410 }
411}