Skip to main content

shape_vm/feature_tests/
backends.rs

1//! Backend executor trait for parity testing
2//!
3//! Provides a unified interface for executing code across different backends
4//! (Interpreter, VM, JIT) to enable three-way parity testing.
5
6use super::FeatureTest;
7use super::parity::ExecutionResult;
8use shape_runtime::engine::{ProgramExecutor, ShapeEngine};
9
10/// Trait for executing Shape code across different backends
11pub trait BackendExecutor: Send + Sync {
12    /// Human-readable name of this backend
13    fn name(&self) -> &'static str;
14
15    /// Execute a feature test and return the result
16    fn execute(&self, test: &FeatureTest) -> ExecutionResult;
17
18    /// Check if this backend is available/enabled
19    fn is_available(&self) -> bool;
20
21    /// Get list of features this backend doesn't support yet
22    fn unsupported_features(&self) -> &'static [&'static str] {
23        &[]
24    }
25}
26
27/// Helper to run async code in a blocking context
28fn run_async<F, T>(future: F) -> T
29where
30    F: std::future::Future<Output = T>,
31{
32    tokio::runtime::Builder::new_current_thread()
33        .enable_all()
34        .build()
35        .unwrap()
36        .block_on(future)
37}
38
39/// Execute code with an executor
40fn execute_with_executor<E: ProgramExecutor>(executor: &mut E, test: &FeatureTest) -> ExecutionResult {
41    let mut engine = match ShapeEngine::new() {
42        Ok(e) => e,
43        Err(e) => return ExecutionResult::Error(format!("Engine init failed: {}", e)),
44    };
45
46    if let Err(e) = engine.load_stdlib() {
47        return ExecutionResult::Error(format!("Stdlib load failed: {}", e));
48    }
49
50    run_async(async {
51        // Execute the test code
52        match engine.execute_repl(executor, test.code).await {
53            Ok(result) => {
54                if !test.function.is_empty() && test.function != "main" {
55                    // Call the specific function
56                    let call = format!("{}()", test.function);
57                    match engine.execute_repl(executor, &call).await {
58                        Ok(r) => ExecutionResult::Success(format!("{:?}", r.value)),
59                        Err(e) => ExecutionResult::Error(format!("{}", e)),
60                    }
61                } else {
62                    ExecutionResult::Success(format!("{:?}", result.value))
63                }
64            }
65            Err(e) => ExecutionResult::Error(format!("{}", e)),
66        }
67    })
68}
69
70/// Legacy interpreter backend - now uses VM internally
71/// Kept for parity testing infrastructure compatibility
72pub struct InterpreterBackend;
73
74impl BackendExecutor for InterpreterBackend {
75    fn name(&self) -> &'static str {
76        "Interpreter (VM)"
77    }
78
79    fn execute(&self, test: &FeatureTest) -> ExecutionResult {
80        // Interpreter is retired - use VM for all execution
81        execute_with_executor(&mut crate::BytecodeExecutor::new(), test)
82    }
83
84    fn is_available(&self) -> bool {
85        true
86    }
87}
88
89/// Bytecode VM backend
90pub struct VMBackend;
91
92impl BackendExecutor for VMBackend {
93    fn name(&self) -> &'static str {
94        "VM"
95    }
96
97    fn execute(&self, test: &FeatureTest) -> ExecutionResult {
98        execute_with_executor(&mut crate::BytecodeExecutor::new(), test)
99    }
100
101    fn is_available(&self) -> bool {
102        true
103    }
104}
105
106/// JIT backend - works for math operations, returns NotSupported for others
107pub struct JITBackend;
108
109impl BackendExecutor for JITBackend {
110    fn name(&self) -> &'static str {
111        "JIT"
112    }
113
114    fn execute(&self, test: &FeatureTest) -> ExecutionResult {
115        // Check if the test uses features JIT doesn't support
116        for unsupported in self.unsupported_features() {
117            if test.covers.contains(unsupported) {
118                return ExecutionResult::NotSupported(unsupported);
119            }
120        }
121
122        // JIT execution via the VM with JIT compilation
123        // For now, delegate to VM since JIT is integrated there
124        execute_with_executor(&mut crate::BytecodeExecutor::new(), test)
125    }
126
127    fn is_available(&self) -> bool {
128        true // JIT is available
129    }
130
131    fn unsupported_features(&self) -> &'static [&'static str] {
132        // Features that JIT doesn't support yet
133        &["pattern_def", "stream_handler", "async_block", "try_catch"]
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_interpreter_available() {
143        let backend = InterpreterBackend;
144        assert!(backend.is_available());
145        assert_eq!(backend.name(), "Interpreter (VM)");
146    }
147
148    #[test]
149    fn test_vm_available() {
150        let backend = VMBackend;
151        assert!(backend.is_available());
152        assert_eq!(backend.name(), "VM");
153    }
154
155    #[test]
156    fn test_jit_available() {
157        let backend = JITBackend;
158        assert!(backend.is_available());
159        assert_eq!(backend.name(), "JIT");
160    }
161
162    #[test]
163    fn test_jit_unsupported_features() {
164        let backend = JITBackend;
165        let unsupported = backend.unsupported_features();
166        assert!(unsupported.contains(&"pattern_def"));
167        assert!(unsupported.contains(&"stream_handler"));
168    }
169}