Skip to main content

runmat_vm/runtime/
call_stack.rs

1use crate::bytecode::Bytecode;
2use miette::SourceSpan;
3use runmat_runtime::{CallFrame, RuntimeError};
4use runmat_thread_local::runmat_thread_local;
5use std::cell::{Cell, RefCell};
6
7pub const DEFAULT_CALLSTACK_LIMIT: usize = 200;
8pub const DEFAULT_ERROR_NAMESPACE: &str = "RunMat";
9
10#[derive(Default, Clone)]
11struct CallStackState {
12    frames: Vec<CallFrame>,
13    depth: usize,
14}
15
16pub struct CallFrameGuard;
17
18impl Drop for CallFrameGuard {
19    fn drop(&mut self) {
20        pop_call_frame();
21    }
22}
23
24runmat_thread_local! {
25    static CALL_STACK: RefCell<CallStackState> = const {
26        RefCell::new(CallStackState {
27            frames: Vec::new(),
28            depth: 0,
29        })
30    };
31    static CALL_STACK_LIMIT: Cell<usize> = const { Cell::new(DEFAULT_CALLSTACK_LIMIT) };
32    static ERROR_NAMESPACE: RefCell<String> = const {
33        RefCell::new(String::new())
34    };
35}
36
37pub fn callstack_limit() -> usize {
38    CALL_STACK_LIMIT.with(|limit| limit.get())
39}
40
41pub fn error_namespace() -> String {
42    let ns = ERROR_NAMESPACE.with(|ns| ns.borrow().clone());
43    if ns.trim().is_empty() {
44        DEFAULT_ERROR_NAMESPACE.to_string()
45    } else {
46        ns
47    }
48}
49
50pub fn set_error_namespace(namespace: &str) {
51    let namespace = if namespace.trim().is_empty() {
52        DEFAULT_ERROR_NAMESPACE
53    } else {
54        namespace
55    };
56    ERROR_NAMESPACE.with(|ns| {
57        *ns.borrow_mut() = namespace.to_string();
58    });
59}
60
61pub fn set_call_stack_limit(limit: usize) {
62    CALL_STACK_LIMIT.with(|cell| cell.set(limit));
63    CALL_STACK.with(|stack| {
64        let mut stack = stack.borrow_mut();
65        if limit == 0 {
66            stack.frames.clear();
67        } else if stack.frames.len() > limit {
68            while stack.frames.len() > limit {
69                stack.frames.remove(0);
70            }
71        }
72    });
73}
74
75pub fn push_call_frame(name: &str, bytecode: &Bytecode, pc: usize) -> CallFrameGuard {
76    let span = bytecode
77        .instr_spans
78        .get(pc)
79        .map(|span| (span.start, span.end));
80    let frame = CallFrame {
81        function: name.to_string(),
82        source_id: bytecode.source_id.map(|id| id.0),
83        span,
84    };
85    CALL_STACK.with(|stack| {
86        let mut stack = stack.borrow_mut();
87        stack.depth = stack.depth.saturating_add(1);
88        let limit = callstack_limit();
89        if limit == 0 {
90            return;
91        }
92        if stack.frames.len() == limit {
93            stack.frames.remove(0);
94        }
95        stack.frames.push(frame);
96    });
97    CallFrameGuard
98}
99
100pub fn pop_call_frame() {
101    CALL_STACK.with(|stack| {
102        let mut stack = stack.borrow_mut();
103        if stack.depth > 0 {
104            stack.depth -= 1;
105        }
106        if !stack.frames.is_empty() {
107            stack.frames.pop();
108        }
109    });
110}
111
112pub fn attach_call_frames(
113    bytecode: &Bytecode,
114    current_function_name: &str,
115    mut err: RuntimeError,
116) -> RuntimeError {
117    if !err.context.call_frames.is_empty() || !err.context.call_stack.is_empty() {
118        return err;
119    }
120    let (mut frames, depth) = CALL_STACK.with(|stack| {
121        let stack = stack.borrow();
122        let frames = stack.frames.clone();
123        (frames, stack.depth)
124    });
125    let limit = callstack_limit();
126    if frames.is_empty() {
127        if limit == 0 {
128            return err;
129        }
130        let span = err.span.as_ref().map(|span: &SourceSpan| {
131            let start = span.offset();
132            let end = start + span.len();
133            (start, end)
134        });
135        if span.is_some() || !current_function_name.is_empty() {
136            frames.push(CallFrame {
137                function: current_function_name.to_string(),
138                source_id: bytecode.source_id.map(|id| id.0),
139                span,
140            });
141        }
142    }
143    let elided = if frames.is_empty() {
144        0
145    } else {
146        depth.saturating_sub(frames.len())
147    };
148    err.context.call_frames = frames;
149    err.context.call_frames_elided = elided;
150    err
151}