venus_core/salsa_db/
conversions.rs

1//! Type conversions for Salsa tracking.
2//!
3//! These types provide Salsa-compatible representations of core Venus types.
4//! They implement the traits required by Salsa (Clone, PartialEq, Eq, Hash)
5//! and provide bidirectional conversion with their source types.
6
7use std::path::PathBuf;
8
9use crate::graph::{CellId, CellInfo, Dependency, SourceSpan};
10
11/// Serializable cell data for Salsa tracking.
12///
13/// This is a simplified version of [`CellInfo`] that implements
14/// the traits required by Salsa (Clone, PartialEq, Eq, Hash).
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub struct CellData {
17    /// Cell index (assigned during parsing)
18    pub id: usize,
19    /// Function name
20    pub name: String,
21    /// Human-readable display name
22    pub display_name: String,
23    /// Parameter names (dependency references)
24    pub param_names: Vec<String>,
25    /// Parameter types
26    pub param_types: Vec<String>,
27    /// Whether each parameter is a reference
28    pub param_is_ref: Vec<bool>,
29    /// Whether each parameter is mutable
30    pub param_is_mut: Vec<bool>,
31    /// Return type
32    pub return_type: String,
33    /// Documentation
34    pub doc_comment: Option<String>,
35    /// Source code
36    pub source_code: String,
37    /// Source file path
38    pub source_file: PathBuf,
39    /// Source location (start_line, start_col, end_line, end_col)
40    pub span: (usize, usize, usize, usize),
41}
42
43impl From<CellInfo> for CellData {
44    fn from(info: CellInfo) -> Self {
45        Self {
46            id: info.id.as_usize(),
47            name: info.name,
48            display_name: info.display_name,
49            param_names: info
50                .dependencies
51                .iter()
52                .map(|d| d.param_name.clone())
53                .collect(),
54            param_types: info
55                .dependencies
56                .iter()
57                .map(|d| d.param_type.clone())
58                .collect(),
59            param_is_ref: info.dependencies.iter().map(|d| d.is_ref).collect(),
60            param_is_mut: info.dependencies.iter().map(|d| d.is_mut).collect(),
61            return_type: info.return_type,
62            doc_comment: info.doc_comment,
63            source_code: info.source_code,
64            source_file: info.source_file,
65            span: (
66                info.span.start_line,
67                info.span.start_col,
68                info.span.end_line,
69                info.span.end_col,
70            ),
71        }
72    }
73}
74
75impl From<CellData> for CellInfo {
76    fn from(data: CellData) -> Self {
77        let dependencies: Vec<Dependency> = data
78            .param_names
79            .into_iter()
80            .zip(data.param_types)
81            .zip(data.param_is_ref)
82            .zip(data.param_is_mut)
83            .map(|(((name, ty), is_ref), is_mut)| Dependency {
84                param_name: name,
85                param_type: ty,
86                is_ref,
87                is_mut,
88            })
89            .collect();
90
91        Self {
92            id: CellId::new(data.id),
93            name: data.name,
94            display_name: data.display_name,
95            dependencies,
96            return_type: data.return_type,
97            doc_comment: data.doc_comment,
98            source_code: data.source_code,
99            source_file: data.source_file,
100            span: SourceSpan {
101                start_line: data.span.0,
102                start_col: data.span.1,
103                end_line: data.span.2,
104                end_col: data.span.3,
105            },
106        }
107    }
108}
109
110/// Compiled cell data for Salsa tracking.
111///
112/// This is a Salsa-compatible version of [`crate::compile::CompiledCell`] that implements
113/// the traits required by Salsa (Clone, PartialEq, Eq, Hash).
114#[derive(Debug, Clone, PartialEq, Eq, Hash)]
115pub struct CompiledCellData {
116    /// Cell index
117    pub cell_id: usize,
118
119    /// Cell name
120    pub name: String,
121
122    /// Path to the compiled dynamic library
123    pub dylib_path: PathBuf,
124
125    /// Entry point symbol name
126    pub entry_symbol: String,
127
128    /// Hash of the cell source (for cache invalidation)
129    pub source_hash: u64,
130
131    /// Hash of dependencies (for cache invalidation)
132    pub deps_hash: u64,
133
134    /// Compilation time in milliseconds
135    pub compile_time_ms: u64,
136}
137
138impl From<crate::compile::CompiledCell> for CompiledCellData {
139    fn from(compiled: crate::compile::CompiledCell) -> Self {
140        Self {
141            cell_id: compiled.cell_id.as_usize(),
142            name: compiled.name,
143            dylib_path: compiled.dylib_path,
144            entry_symbol: compiled.entry_symbol,
145            source_hash: compiled.source_hash,
146            deps_hash: compiled.deps_hash,
147            compile_time_ms: compiled.compile_time_ms,
148        }
149    }
150}
151
152impl CompiledCellData {
153    /// Convert back to [`crate::compile::CompiledCell`].
154    pub fn to_compiled_cell(&self) -> crate::compile::CompiledCell {
155        crate::compile::CompiledCell {
156            cell_id: CellId::new(self.cell_id),
157            name: self.name.clone(),
158            dylib_path: self.dylib_path.clone(),
159            entry_symbol: self.entry_symbol.clone(),
160            source_hash: self.source_hash,
161            deps_hash: self.deps_hash,
162            compile_time_ms: self.compile_time_ms,
163        }
164    }
165}
166
167/// Compilation result wrapper for Salsa tracking.
168#[derive(Debug, Clone, PartialEq, Eq, Hash)]
169pub enum CompilationStatus {
170    /// Compilation succeeded
171    Success(CompiledCellData),
172    /// Used cached result
173    Cached(CompiledCellData),
174    /// Compilation failed
175    Failed(String),
176}
177
178impl CompilationStatus {
179    /// Get the compiled cell data if successful.
180    pub fn compiled(&self) -> Option<&CompiledCellData> {
181        match self {
182            Self::Success(data) | Self::Cached(data) => Some(data),
183            Self::Failed(_) => None,
184        }
185    }
186
187    /// Check if compilation was successful.
188    pub fn is_success(&self) -> bool {
189        matches!(self, Self::Success(_) | Self::Cached(_))
190    }
191}
192
193/// Cell execution output data for Salsa tracking.
194///
195/// This type stores the serialized output of a cell execution in a
196/// Salsa-compatible format. The actual output bytes are stored along
197/// with metadata for type checking and debugging.
198#[derive(Debug, Clone, PartialEq, Eq, Hash)]
199pub struct CellOutputData {
200    /// Cell index that produced this output
201    pub cell_id: usize,
202
203    /// Serialized output bytes (bincode format)
204    pub bytes: Vec<u8>,
205
206    /// Type hash for validation on deserialization
207    pub type_hash: u64,
208
209    /// Type name for debugging
210    pub type_name: String,
211
212    /// Hash of input values used to produce this output.
213    /// Used to detect when inputs have changed and output is stale.
214    pub inputs_hash: u64,
215
216    /// Execution time in milliseconds
217    pub execution_time_ms: u64,
218}
219
220impl CellOutputData {
221    /// Create a new cell output from a BoxedOutput.
222    pub fn from_boxed(
223        cell_id: usize,
224        boxed: &crate::state::BoxedOutput,
225        inputs_hash: u64,
226        execution_time_ms: u64,
227    ) -> Self {
228        Self {
229            cell_id,
230            bytes: boxed.bytes().to_vec(),
231            type_hash: boxed.type_hash(),
232            type_name: boxed.type_name().to_string(),
233            inputs_hash,
234            execution_time_ms,
235        }
236    }
237
238    /// Convert to a BoxedOutput for deserialization.
239    pub fn to_boxed(&self) -> crate::state::BoxedOutput {
240        crate::state::BoxedOutput::from_raw_with_type(
241            self.bytes.clone(),
242            self.type_hash,
243            self.type_name.clone(),
244        )
245    }
246
247    /// Check if this output is valid for the given inputs hash.
248    pub fn is_valid_for(&self, inputs_hash: u64) -> bool {
249        self.inputs_hash == inputs_hash
250    }
251}
252
253/// Execution status for a cell.
254#[derive(Debug, Clone, PartialEq, Eq, Hash)]
255pub enum ExecutionStatus {
256    /// Cell has not been executed yet
257    Pending,
258    /// Cell is currently executing
259    Running,
260    /// Cell executed successfully
261    Success(CellOutputData),
262    /// Cell execution failed
263    Failed(String),
264}
265
266impl ExecutionStatus {
267    /// Get the output data if execution was successful.
268    pub fn output(&self) -> Option<&CellOutputData> {
269        match self {
270            Self::Success(data) => Some(data),
271            _ => None,
272        }
273    }
274
275    /// Check if execution was successful.
276    pub fn is_success(&self) -> bool {
277        matches!(self, Self::Success(_))
278    }
279
280    /// Check if execution failed.
281    pub fn is_failed(&self) -> bool {
282        matches!(self, Self::Failed(_))
283    }
284}
285
286/// Combined graph analysis results for Salsa tracking.
287///
288/// This struct caches both execution order and parallel levels computed
289/// from a single graph construction, eliminating redundant graph builds.
290/// Both fields are computed together since they require the same GraphEngine.
291#[derive(Debug, Clone, PartialEq, Eq, Hash)]
292pub struct GraphAnalysis {
293    /// Topological execution order (cell indices)
294    pub execution_order: Vec<usize>,
295
296    /// Parallel execution levels (groups of cell indices that can run concurrently)
297    pub parallel_levels: Vec<Vec<usize>>,
298}
299
300impl GraphAnalysis {
301    /// Create an empty analysis (no cells or graph error).
302    pub fn empty() -> Self {
303        Self {
304            execution_order: Vec::new(),
305            parallel_levels: Vec::new(),
306        }
307    }
308
309    /// Check if the analysis is empty (no cells to execute).
310    pub fn is_empty(&self) -> bool {
311        self.execution_order.is_empty()
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318
319    #[test]
320    fn test_cell_data_conversion() {
321        let info = CellInfo {
322            id: CellId::new(0),
323            name: "test".to_string(),
324            display_name: "Test Cell".to_string(),
325            dependencies: vec![
326                Dependency {
327                    param_name: "x".to_string(),
328                    param_type: "i32".to_string(),
329                    is_ref: true,
330                    is_mut: false,
331                },
332                Dependency {
333                    param_name: "y".to_string(),
334                    param_type: "Vec<u8>".to_string(),
335                    is_ref: true,
336                    is_mut: true, // mutable reference
337                },
338            ],
339            return_type: "i32".to_string(),
340            doc_comment: Some("Test cell".to_string()),
341            source_code: "{ 42 }".to_string(),
342            source_file: PathBuf::from("test.rs"),
343            span: SourceSpan {
344                start_line: 1,
345                start_col: 0,
346                end_line: 1,
347                end_col: 10,
348            },
349        };
350
351        // Convert to CellData
352        let data: CellData = info.clone().into();
353        assert_eq!(data.name, "test");
354        assert_eq!(data.display_name, "Test Cell");
355        assert_eq!(data.param_names, vec!["x", "y"]);
356        assert_eq!(data.param_types, vec!["i32", "Vec<u8>"]);
357        assert_eq!(data.param_is_ref, vec![true, true]);
358        assert_eq!(data.param_is_mut, vec![false, true]);
359
360        // Convert back to CellInfo - verify no data loss
361        let back: CellInfo = data.into();
362        assert_eq!(back.name, "test");
363        assert_eq!(back.dependencies.len(), 2);
364        assert_eq!(back.dependencies[0].param_name, "x");
365        assert!(back.dependencies[0].is_ref);
366        assert!(!back.dependencies[0].is_mut);
367        assert_eq!(back.dependencies[1].param_name, "y");
368        assert!(back.dependencies[1].is_ref);
369        assert!(back.dependencies[1].is_mut); // Preserved!
370    }
371
372    #[test]
373    fn test_compiled_cell_data_conversion() {
374        use crate::compile::CompiledCell;
375
376        let compiled = CompiledCell {
377            cell_id: CellId::new(0),
378            name: "test_cell".to_string(),
379            dylib_path: PathBuf::from("/path/to/cell.so"),
380            entry_symbol: "venus_cell_test_cell".to_string(),
381            source_hash: 12345,
382            deps_hash: 67890,
383            compile_time_ms: 100,
384        };
385
386        // Convert to CompiledCellData
387        let data: CompiledCellData = compiled.clone().into();
388        assert_eq!(data.cell_id, 0);
389        assert_eq!(data.name, "test_cell");
390        assert_eq!(data.dylib_path, PathBuf::from("/path/to/cell.so"));
391        assert_eq!(data.entry_symbol, "venus_cell_test_cell");
392        assert_eq!(data.source_hash, 12345);
393        assert_eq!(data.deps_hash, 67890);
394
395        // Convert back
396        let back = data.to_compiled_cell();
397        assert_eq!(back.cell_id.as_usize(), 0);
398        assert_eq!(back.name, "test_cell");
399    }
400
401    #[test]
402    fn test_compilation_status() {
403        let data = CompiledCellData {
404            cell_id: 0,
405            name: "test".to_string(),
406            dylib_path: PathBuf::from("/test.so"),
407            entry_symbol: "venus_cell_test".to_string(),
408            source_hash: 1,
409            deps_hash: 2,
410            compile_time_ms: 50,
411        };
412
413        let success = CompilationStatus::Success(data.clone());
414        assert!(success.is_success());
415        assert!(success.compiled().is_some());
416
417        let cached = CompilationStatus::Cached(data);
418        assert!(cached.is_success());
419        assert!(cached.compiled().is_some());
420
421        let failed = CompilationStatus::Failed("error".to_string());
422        assert!(!failed.is_success());
423        assert!(failed.compiled().is_none());
424    }
425}