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}