Skip to main content

runmat_core/session/
mod.rs

1use anyhow::Result;
2use runmat_builtins::{self, Value};
3use runmat_gc::{gc_configure, gc_stats, GcConfig};
4#[cfg(feature = "jit")]
5use tracing::warn;
6use tracing::{debug, info, info_span};
7
8use runmat_hir::{LoweringContext, LoweringResult, SourceId};
9use runmat_lexer::{tokenize_detailed, Token as LexToken};
10use runmat_parser::{parse_with_options, ParserOptions};
11use runmat_runtime::{build_runtime_error, gather_if_needed_async, RuntimeError};
12use runmat_runtime::{
13    runtime_export_workspace_state, runtime_import_workspace_state, WorkspaceReplayMode,
14};
15use runmat_time::Instant;
16#[cfg(feature = "jit")]
17use runmat_turbine::TurbineEngine;
18use std::collections::{HashMap, HashSet};
19use std::future::Future;
20use std::path::PathBuf;
21use std::sync::{
22    atomic::{AtomicBool, Ordering},
23    Arc, Mutex,
24};
25use uuid::Uuid;
26
27use crate::execution::{
28    ExecutionStats, ExecutionStreamEntry, ExecutionStreamKind, InputRequest, InputRequestKind,
29    InputResponse, SharedAsyncInputHandler, StdinEvent, StdinEventKind,
30};
31use crate::fusion::{build_fusion_snapshot, FusionPlanSnapshot};
32use crate::profiling::{gather_profiling, reset_provider_telemetry};
33use crate::source_pool::{line_col_from_offset, SourcePool};
34use crate::telemetry::{TelemetryPlatformInfo, TelemetrySink};
35use crate::workspace::{
36    determine_display_label_from_context, execution_display_context, format_type_info,
37    gather_gpu_preview_values, gpu_dtype_label, gpu_size_bytes, last_emit_var_index,
38    last_store_var_index, slice_value_for_preview, workspace_entry, FinalStmtEmitDisposition,
39    MaterializedVariable, WorkspaceEntry, WorkspaceExportMode, WorkspaceMaterializeOptions,
40    WorkspaceMaterializeTarget, WorkspacePreview, WorkspaceResidency, WorkspaceSnapshot,
41    MATERIALIZE_DEFAULT_LIMIT,
42};
43use crate::{
44    approximate_size_bytes, matlab_class_name, numeric_dtype_label, preview_numeric_values,
45    value_shape, CompatMode, RunError,
46};
47
48mod compile;
49mod config;
50mod init;
51mod run;
52mod workspace;
53
54/// Host-agnostic RunMat execution session (parser + interpreter + optional JIT).
55pub struct RunMatSession {
56    /// JIT compiler engine (optional for fallback mode)
57    #[cfg(feature = "jit")]
58    jit_engine: Option<TurbineEngine>,
59    /// Verbose output for debugging
60    verbose: bool,
61    /// Execution statistics
62    stats: ExecutionStats,
63    /// Current variable array for bytecode execution
64    variable_array: Vec<Value>,
65    /// Current workspace bindings with stable ABI identity and current VM slot.
66    workspace_bindings: HashMap<String, SessionWorkspaceBinding>,
67    /// Persistent workspace values keyed by variable name
68    workspace_values: HashMap<String, Value>,
69    /// Stable ABI identity for this interactive workspace.
70    abi_workspace_handle: crate::abi::WorkspaceHandle,
71    /// Source identity for the active execution request (if source-scoped).
72    active_source_identity: Option<crate::abi::SourceIdentity>,
73    /// Semantic function registry persisted across interactive inputs.
74    function_registry: runmat_vm::FunctionRegistry,
75    next_semantic_function_id: usize,
76    /// MATLAB-compatible search path persisted by this session.
77    search_path: Arc<runmat_runtime::builtins::common::path_state::SearchPath>,
78    /// Canonically compiled functions discovered through the runtime search path.
79    dynamic_function_cache: Arc<Mutex<HashMap<PathBuf, DynamicFunctionCacheEntry>>>,
80    /// Interned source pool for user-defined functions
81    source_pool: SourcePool,
82    /// Cooperative cancellation flag shared with the runtime.
83    interrupt_flag: Arc<AtomicBool>,
84    /// Tracks whether an execution is currently active.
85    is_executing: bool,
86    /// Optional async input handler (Phase 2). When set, stdin interactions are awaited
87    /// internally by `ExecuteFuture` rather than being surfaced as "pending requests".
88    async_input_handler: Option<SharedAsyncInputHandler>,
89    /// Maximum number of call stack frames to retain for diagnostics.
90    callstack_limit: usize,
91    /// Namespace prefix for runtime/semantic error identifiers.
92    error_namespace: String,
93    /// Active source name for diagnostics (set from execution requests).
94    active_source_name: String,
95    /// Resolved active source path for file-aware builtins and source discovery.
96    active_source_fullpath_name: Option<String>,
97    pub(crate) telemetry_consent: bool,
98    pub(crate) telemetry_client_id: Option<String>,
99    pub(crate) telemetry_platform: TelemetryPlatformInfo,
100    pub(crate) telemetry_sink: Option<Arc<dyn TelemetrySink>>,
101    workspace_preview_tokens: HashMap<Uuid, WorkspaceMaterializeTicket>,
102    workspace_version: u64,
103    emit_fusion_plan: bool,
104    compat_mode: CompatMode,
105    top_level_await_enabled: bool,
106    dynamic_eval_enabled: bool,
107    /// Persisted numeric display format for this session (survives across executions).
108    format_mode: runmat_builtins::FormatMode,
109    /// Persisted diary logging state for this session (survives across executions).
110    diary_state: runmat_runtime::console::DiaryStateSnapshot,
111    /// Preloaded companion statements discovered asynchronously by the request path.
112    pending_companion_source_discovery: Option<compile::CompanionSourceDiscovery>,
113}
114
115pub(crate) struct PreparedExecution {
116    ast: runmat_parser::Program,
117    lowering: LoweringResult,
118    analysis: runmat_mir::analysis::AnalysisStore,
119    pub(crate) bytecode: runmat_vm::Bytecode,
120    function_registry_after_success: runmat_vm::FunctionRegistry,
121    next_semantic_function_id_after_success: usize,
122}
123
124impl PreparedExecution {
125    #[cfg(test)]
126    pub(crate) fn lowering(&self) -> &LoweringResult {
127        &self.lowering
128    }
129
130    #[cfg(test)]
131    pub(crate) fn analysis(&self) -> &runmat_mir::analysis::AnalysisStore {
132        &self.analysis
133    }
134}
135
136#[derive(Debug, Clone)]
137pub(crate) struct SessionWorkspaceBinding {
138    pub(crate) key: crate::abi::WorkspaceBindingKey,
139    pub(crate) slot: usize,
140}
141
142#[derive(Debug, Clone)]
143struct WorkspaceMaterializeTicket {
144    name: String,
145}
146
147#[derive(Clone)]
148struct DynamicFunctionCacheEntry {
149    source_text: String,
150    registry: Arc<runmat_vm::FunctionRegistry>,
151}
152
153struct ActiveExecutionGuard {
154    flag: *mut bool,
155}
156
157impl ActiveExecutionGuard {
158    fn new(session: &mut RunMatSession) -> Result<Self> {
159        if session.is_executing {
160            Err(anyhow::anyhow!(
161                "RunMatSession is already executing another script"
162            ))
163        } else {
164            session.is_executing = true;
165            Ok(Self {
166                flag: &mut session.is_executing,
167            })
168        }
169    }
170}
171
172impl Drop for ActiveExecutionGuard {
173    fn drop(&mut self) {
174        unsafe {
175            if let Some(flag) = self.flag.as_mut() {
176                *flag = false;
177            }
178        }
179    }
180}
181
182struct SessionDiaryStateGuard {
183    session_state: *mut runmat_runtime::console::DiaryStateSnapshot,
184    previous_state: Option<runmat_runtime::console::DiaryStateSnapshot>,
185}
186
187impl SessionDiaryStateGuard {
188    fn new(session: &mut RunMatSession) -> Self {
189        let previous_state =
190            runmat_runtime::console::replace_diary_state(session.diary_state.clone());
191        Self {
192            session_state: &mut session.diary_state,
193            previous_state: Some(previous_state),
194        }
195    }
196}
197
198impl Drop for SessionDiaryStateGuard {
199    fn drop(&mut self) {
200        let current_state = runmat_runtime::console::diary_state_snapshot();
201        unsafe {
202            if let Some(session_state) = self.session_state.as_mut() {
203                *session_state = current_state;
204            }
205        }
206        if let Some(previous_state) = self.previous_state.take() {
207            runmat_runtime::console::replace_diary_state(previous_state);
208        }
209    }
210}
211
212impl Default for RunMatSession {
213    fn default() -> Self {
214        Self::new().expect("Failed to create default RunMat session")
215    }
216}