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>(
41    executor: &mut E,
42    test: &FeatureTest,
43) -> ExecutionResult {
44    let mut engine = match ShapeEngine::new() {
45        Ok(e) => e,
46        Err(e) => return ExecutionResult::Error(format!("Engine init failed: {}", e)),
47    };
48
49    if let Err(e) = engine.load_stdlib() {
50        return ExecutionResult::Error(format!("Stdlib load failed: {}", e));
51    }
52
53    run_async(async {
54        // Execute the test code
55        match engine.execute_repl(executor, test.code).await {
56            Ok(result) => {
57                if !test.function.is_empty() && test.function != "main" {
58                    // Call the specific function
59                    let call = format!("{}()", test.function);
60                    match engine.execute_repl(executor, &call).await {
61                        Ok(r) => ExecutionResult::Success(format!("{:?}", r.value)),
62                        Err(e) => ExecutionResult::Error(format!("{}", e)),
63                    }
64                } else {
65                    ExecutionResult::Success(format!("{:?}", result.value))
66                }
67            }
68            Err(e) => ExecutionResult::Error(format!("{}", e)),
69        }
70    })
71}
72
73/// Legacy interpreter backend - now uses VM internally
74/// Kept for parity testing infrastructure compatibility
75pub struct InterpreterBackend;
76
77impl BackendExecutor for InterpreterBackend {
78    fn name(&self) -> &'static str {
79        "Interpreter (VM)"
80    }
81
82    fn execute(&self, test: &FeatureTest) -> ExecutionResult {
83        // Interpreter is retired - use VM for all execution
84        execute_with_executor(&mut crate::BytecodeExecutor::new(), test)
85    }
86
87    fn is_available(&self) -> bool {
88        true
89    }
90}
91
92/// Bytecode VM backend
93pub struct VMBackend;
94
95impl BackendExecutor for VMBackend {
96    fn name(&self) -> &'static str {
97        "VM"
98    }
99
100    fn execute(&self, test: &FeatureTest) -> ExecutionResult {
101        execute_with_executor(&mut crate::BytecodeExecutor::new(), test)
102    }
103
104    fn is_available(&self) -> bool {
105        true
106    }
107}
108
109/// JIT backend - works for math operations, returns NotSupported for others
110pub struct JITBackend;
111
112impl BackendExecutor for JITBackend {
113    fn name(&self) -> &'static str {
114        "JIT"
115    }
116
117    fn execute(&self, test: &FeatureTest) -> ExecutionResult {
118        // Check if the test uses features JIT doesn't support
119        for unsupported in self.unsupported_features() {
120            if test.covers.contains(unsupported) {
121                return ExecutionResult::NotSupported(unsupported);
122            }
123        }
124
125        // JIT execution via the VM with JIT compilation
126        // For now, delegate to VM since JIT is integrated there
127        execute_with_executor(&mut crate::BytecodeExecutor::new(), test)
128    }
129
130    fn is_available(&self) -> bool {
131        true // JIT is available
132    }
133
134    fn unsupported_features(&self) -> &'static [&'static str] {
135        // Features that JIT doesn't support yet
136        &["pattern_def", "stream_handler", "async_block", "try_catch"]
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_interpreter_available() {
146        let backend = InterpreterBackend;
147        assert!(backend.is_available());
148        assert_eq!(backend.name(), "Interpreter (VM)");
149    }
150
151    #[test]
152    fn test_vm_available() {
153        let backend = VMBackend;
154        assert!(backend.is_available());
155        assert_eq!(backend.name(), "VM");
156    }
157
158    #[test]
159    fn test_jit_available() {
160        let backend = JITBackend;
161        assert!(backend.is_available());
162        assert_eq!(backend.name(), "JIT");
163    }
164
165    #[test]
166    fn test_jit_unsupported_features() {
167        let backend = JITBackend;
168        let unsupported = backend.unsupported_features();
169        assert!(unsupported.contains(&"pattern_def"));
170        assert!(unsupported.contains(&"stream_handler"));
171    }
172}