runmat_vm/runtime/
call_stack.rs1use 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}