helix/dna/mds/
runtime.rs

1use std::collections::{HashMap, VecDeque};
2use std::path::Path;
3use crate::hel::binary::{HelixBinary, Value};
4use crate::hel::error::{RuntimeError, RuntimeErrorKind};
5use crate::atp::types::HelixConfig;
6use std::path::PathBuf;
7use std::process::Command;
8use anyhow::{Result, Context};
9use crate::dna::compiler::Compiler;
10use crate::dna::mds::optimizer::OptimizationLevel;
11pub use crate::mds::codegen::{PipelineNodeIR, ReferenceType, StepDefinition, SecretType, ConstantValue};
12
13pub struct HelixVM {
14    stack: Vec<Value>,
15    memory: HashMap<u32, Value>,
16    registers: VMRegisters,
17    config: HelixConfig,
18    call_stack: VecDeque<CallFrame>,
19    execution_state: ExecutionState,
20    debug_mode: bool,
21    breakpoints: HashMap<usize, Breakpoint>,
22}
23#[derive(Debug, Default)]
24pub struct VMRegisters {
25    pub program_counter: usize,
26    pub stack_pointer: usize,
27    pub frame_pointer: usize,
28    pub return_address: usize,
29    pub flags: VMFlags,
30}
31#[derive(Debug, Default)]
32pub struct VMFlags {
33    pub zero: bool,
34    pub overflow: bool,
35    pub error: bool,
36    pub halted: bool,
37}
38#[derive(Debug)]
39pub struct CallFrame {
40    pub return_address: usize,
41    pub frame_pointer: usize,
42    pub local_vars: HashMap<u32, Value>,
43}
44#[derive(Debug, PartialEq)]
45pub enum ExecutionState {
46    Ready,
47    Running,
48    Paused,
49    Halted,
50    Error(String),
51}
52#[derive(Debug)]
53pub struct Breakpoint {
54    pub active: bool,
55    pub condition: Option<String>,
56    pub hit_count: usize,
57}
58
59pub type VMResult<T> = Result<T, RuntimeError>;
60
61impl HelixVM {
62    pub fn new() -> Self {
63        Self {
64            stack: Vec::new(),
65            memory: HashMap::new(),
66            registers: VMRegisters::default(),
67            config: HelixConfig::default(),
68            call_stack: VecDeque::new(),
69            execution_state: ExecutionState::Ready,
70            debug_mode: false,
71            breakpoints: HashMap::new(),
72        }
73    }
74    pub fn with_debug(mut self) -> Self {
75        self.debug_mode = true;
76        self
77    }
78    pub fn execute_binary(&mut self, binary: &HelixBinary) -> VMResult<HelixConfig> {
79        let serializer = crate::dna::mds::serializer::BinarySerializer::new(false);
80        let ir = serializer
81            .deserialize_to_ir(binary)
82            .map_err(|e| RuntimeError {
83                kind: RuntimeErrorKind::InvalidInstruction,
84                message: format!("Failed to deserialize binary: {}", e),
85                stack_trace: vec![],
86            })?;
87        self.execution_state = ExecutionState::Running;
88        self.registers.program_counter = 0;
89        while self.registers.program_counter < ir.instructions.len()
90            && self.execution_state == ExecutionState::Running
91        {
92            if self.debug_mode {
93                if let Some(bp) = self
94                    .breakpoints
95                    .get_mut(&self.registers.program_counter)
96                {
97                    if bp.active {
98                        bp.hit_count += 1;
99                        self.execution_state = ExecutionState::Paused;
100                        break;
101                    }
102                }
103            }
104            let instruction = &ir.instructions[self.registers.program_counter];
105            self.execute_instruction(instruction)?;
106        }
107        Ok(self.config.clone())
108    }
109    fn execute_instruction(
110        &mut self,
111        instruction: &super::codegen::Instruction,
112    ) -> VMResult<()> {
113        use super::codegen::Instruction as IR;
114        match instruction {
115            IR::DeclareAgent(id) => {
116                self.declare_agent(*id)?;
117            }
118            IR::DeclareWorkflow(id) => {
119                self.declare_workflow(*id)?;
120            }
121            IR::DeclareContext(id) => {
122                self.declare_context(*id)?;
123            }
124            IR::DeclareCrew(id) => {
125                self.declare_crew(*id)?;
126            }
127            IR::SetProperty { target, key, value } => {
128                self.set_property(*target, *key, value)?;
129            }
130            IR::SetCapability { agent, capability } => {
131                self.set_capability(*agent, *capability)?;
132            }
133            IR::SetSecret { context, key, secret } => {
134                self.set_secret(*context, *key, secret)?;
135            }
136            IR::DefineStep { workflow, step } => {
137                self.define_step(*workflow, step)?;
138            }
139            IR::DefinePipeline { workflow, nodes } => {
140                self.define_pipeline(*workflow, nodes)?;
141            }
142            IR::ResolveReference { ref_type, index } => {
143                self.resolve_reference(ref_type, *index)?;
144            }
145            IR::SetMetadata { key, value } => {
146                self.set_metadata(*key, *value)?;
147            }
148        }
149        self.registers.program_counter += 1;
150        Ok(())
151    }
152    fn declare_agent(&mut self, _id: u32) -> VMResult<()> {
153        Ok(())
154    }
155    fn declare_workflow(&mut self, _id: u32) -> VMResult<()> {
156        Ok(())
157    }
158    fn declare_context(&mut self, _id: u32) -> VMResult<()> {
159        Ok(())
160    }
161    fn declare_crew(&mut self, _id: u32) -> VMResult<()> {
162        Ok(())
163    }
164    fn set_property(
165        &mut self,
166        _target: u32,
167        _key: u32,
168        _value: &ConstantValue,
169    ) -> VMResult<()> {
170        Ok(())
171    }
172    fn set_capability(&mut self, _agent: u32, _capability: u32) -> VMResult<()> {
173        Ok(())
174    }
175    fn set_secret(
176        &mut self,
177        _context: u32,
178        _key: u32,
179        _secret: &SecretType,
180    ) -> VMResult<()> {
181        Ok(())
182    }
183    fn define_step(
184        &mut self,
185        _workflow: u32,
186        _step: &StepDefinition,
187    ) -> VMResult<()> {
188        Ok(())
189    }
190    fn define_pipeline(
191        &mut self,
192        _workflow: u32,
193        _nodes: &[PipelineNodeIR],
194    ) -> VMResult<()> {
195        Ok(())
196    }
197    fn resolve_reference(
198        &mut self,
199        _ref_type: &ReferenceType,
200        _index: u32,
201    ) -> VMResult<()> {
202        Ok(())
203    }
204    fn set_metadata(&mut self, _key: u32, _value: u32) -> VMResult<()> {
205        Ok(())
206    }
207    pub fn push(&mut self, value: Value) -> VMResult<()> {
208        if self.stack.len() >= 1024 {
209            return Err(RuntimeError {
210                kind: RuntimeErrorKind::StackOverflow,
211                message: "Stack overflow".to_string(),
212                stack_trace: self.get_stack_trace(),
213            });
214        }
215        self.stack.push(value);
216        self.registers.stack_pointer += 1;
217        Ok(())
218    }
219    pub fn pop(&mut self) -> VMResult<Value> {
220        if self.stack.is_empty() {
221            return Err(RuntimeError {
222                kind: RuntimeErrorKind::StackUnderflow,
223                message: "Stack underflow".to_string(),
224                stack_trace: self.get_stack_trace(),
225            });
226        }
227        self.registers.stack_pointer -= 1;
228        Ok(self.stack.pop().unwrap())
229    }
230    pub fn load_memory(&self, address: u32) -> VMResult<&Value> {
231        self.memory
232            .get(&address)
233            .ok_or_else(|| RuntimeError {
234                kind: RuntimeErrorKind::MemoryAccessViolation,
235                message: format!("Invalid memory access at address {}", address),
236                stack_trace: self.get_stack_trace(),
237            })
238    }
239    pub fn store_memory(&mut self, address: u32, value: Value) -> VMResult<()> {
240        self.memory.insert(address, value);
241        Ok(())
242    }
243    pub fn set_breakpoint(&mut self, address: usize) {
244        self.breakpoints
245            .insert(
246                address,
247                Breakpoint {
248                    active: true,
249                    condition: None,
250                    hit_count: 0,
251                },
252            );
253    }
254    pub fn remove_breakpoint(&mut self, address: usize) {
255        self.breakpoints.remove(&address);
256    }
257    pub fn continue_execution(&mut self) {
258        if self.execution_state == ExecutionState::Paused {
259            self.execution_state = ExecutionState::Running;
260        }
261    }
262    pub fn step(&mut self) {
263        if self.execution_state == ExecutionState::Paused {
264            self.execution_state = ExecutionState::Running;
265        }
266    }
267    pub fn state(&self) -> &ExecutionState {
268        &self.execution_state
269    }
270    fn get_stack_trace(&self) -> Vec<String> {
271        let mut trace = Vec::new();
272        trace.push(format!("PC: {}", self.registers.program_counter));
273        for (i, frame) in self.call_stack.iter().enumerate() {
274            trace.push(format!("Frame {}: return address {}", i, frame.return_address));
275        }
276        trace
277    }
278    pub fn stats(&self) -> VMStats {
279        VMStats {
280            instructions_executed: self.registers.program_counter,
281            stack_size: self.stack.len(),
282            memory_usage: self.memory.len(),
283            call_depth: self.call_stack.len(),
284        }
285    }
286}
287#[derive(Debug)]
288pub struct VMStats {
289    pub instructions_executed: usize,
290    pub stack_size: usize,
291    pub memory_usage: usize,
292    pub call_depth: usize,
293}
294impl Default for HelixVM {
295    fn default() -> Self {
296        Self::new()
297    }
298}
299pub struct VMExecutor {
300    vm: HelixVM,
301}
302impl VMExecutor {
303    pub fn new() -> Self {
304        Self { vm: HelixVM::new() }
305    }
306    pub fn execute_file<P: AsRef<Path>>(&mut self, path: P) -> VMResult<HelixConfig> {
307        let loader = crate::dna::mds::loader::BinaryLoader::new();
308        let binary = loader
309            .load_file(path.as_ref())
310            .map_err(|e| RuntimeError {
311                kind: RuntimeErrorKind::ResourceNotFound,
312                message: format!("Failed to load binary: {}", e),
313                stack_trace: vec![],
314            })?;
315        self.vm.execute_binary(&binary)
316    }
317    pub fn execute_with_debug<P: AsRef<Path>>(
318        &mut self,
319        path: P,
320    ) -> VMResult<HelixConfig> {
321        self.vm = HelixVM::new().with_debug();
322        self.execute_file(path)
323    }
324    pub fn vm(&mut self) -> &mut HelixVM {
325        &mut self.vm
326    }
327}
328impl Default for VMExecutor {
329    fn default() -> Self {
330        Self::new()
331    }
332}
333pub struct VMConfig {
334    pub max_stack_size: usize,
335    pub max_memory: usize,
336    pub max_call_depth: usize,
337    pub enable_gc: bool,
338    pub gc_threshold: usize,
339}
340impl Default for VMConfig {
341    fn default() -> Self {
342        Self {
343            max_stack_size: 1024,
344            max_memory: 65536,
345            max_call_depth: 256,
346            enable_gc: false,
347            gc_threshold: 1000,
348        }
349    }
350}
351#[cfg(test)]
352mod tests {
353    use super::*;
354    #[test]
355    fn test_vm_creation() {
356        let vm = HelixVM::new();
357        assert_eq!(vm.execution_state, ExecutionState::Ready);
358        assert!(vm.stack.is_empty());
359        assert!(vm.memory.is_empty());
360    }
361    #[test]
362    fn test_stack_operations() {
363        let mut vm = HelixVM::new();
364        vm.push(Value::Int(42)).unwrap();
365        assert_eq!(vm.stack.len(), 1);
366        assert_eq!(vm.registers.stack_pointer, 1);
367        let value = vm.pop().unwrap();
368        match value {
369            Value::Int(42) => {}
370            _ => panic!("Expected Int(42)"),
371        }
372        assert!(vm.stack.is_empty());
373        assert_eq!(vm.registers.stack_pointer, 0);
374    }
375    #[test]
376    fn test_memory_operations() {
377        let mut vm = HelixVM::new();
378        vm.store_memory(100, Value::Bool(true)).unwrap();
379        let value = vm.load_memory(100).unwrap();
380        match value {
381            Value::Bool(true) => {}
382            _ => panic!("Expected Bool(true)"),
383        }
384    }
385    #[test]
386    fn test_stack_overflow() {
387        let mut vm = HelixVM::new();
388        for _ in 0..1024 {
389            vm.push(Value::Int(1)).unwrap();
390        }
391        let result = vm.push(Value::Int(2));
392        assert!(result.is_err());
393        if let Err(e) = result {
394            assert_eq!(e.kind, RuntimeErrorKind::StackOverflow);
395        }
396    }
397    #[test]
398    fn test_stack_underflow() {
399        let mut vm = HelixVM::new();
400        let result = vm.pop();
401        assert!(result.is_err());
402        if let Err(e) = result {
403            assert_eq!(e.kind, RuntimeErrorKind::StackUnderflow);
404        }
405    }
406    #[test]
407    fn test_breakpoints() {
408        let mut vm = HelixVM::new().with_debug();
409        vm.set_breakpoint(10);
410        assert!(vm.breakpoints.contains_key(& 10));
411        vm.remove_breakpoint(10);
412        assert!(! vm.breakpoints.contains_key(& 10));
413    }
414    #[test]
415    fn test_vm_stats() {
416        let vm = HelixVM::new();
417        let stats = vm.stats();
418        assert_eq!(stats.instructions_executed, 0);
419        assert_eq!(stats.stack_size, 0);
420        assert_eq!(stats.memory_usage, 0);
421        assert_eq!(stats.call_depth, 0);
422    }
423}
424
425
426pub fn run_project(
427    input: Option<PathBuf>,
428    args: Vec<String>,
429    optimize: u8,
430    verbose: bool,
431) -> Result<()> {
432    let project_dir = find_project_root()?;
433    let input_file = match input {
434        Some(path) => path,
435        None => {
436            let main_file = project_dir.join("src").join("main.hlx");
437            if main_file.exists() {
438                main_file
439            } else {
440                return Err(
441                    anyhow::anyhow!(
442                        "No input file specified and no src/main.hlx found.\n\
443                    Specify a file with: helix run <file.hlx>"
444                    ),
445                );
446            }
447        }
448    };
449    if verbose {
450        println!("🚀 Running HELIX project:");
451        println!("  Input: {}", input_file.display());
452        println!("  Optimization: Level {}", optimize);
453        if !args.is_empty() {
454            println!("  Arguments: {:?}", args);
455        }
456    }
457    let output_file = compile_for_run(&input_file, optimize, verbose)?;
458    execute_binary(&output_file, args, verbose)?;
459    Ok(())
460}
461fn compile_for_run(input: &PathBuf, optimize: u8, verbose: bool) -> Result<PathBuf> {
462    let project_dir = find_project_root()?;
463    let target_dir = project_dir.join("target");
464    std::fs::create_dir_all(&target_dir).context("Failed to create target directory")?;
465    let input_stem = input
466        .file_stem()
467        .and_then(|s| s.to_str())
468        .ok_or_else(|| anyhow::anyhow!("Invalid input filename"))?;
469    let output_file = target_dir.join(format!("{}.hlxb", input_stem));
470    if verbose {
471        println!("📦 Compiling for execution...");
472    }
473    let compiler = Compiler::builder()
474        .optimization_level(OptimizationLevel::from(optimize))
475        .compression(true)
476        .cache(true)
477        .verbose(verbose)
478        .build();
479    let binary = compiler.compile_file(input).context("Failed to compile HELIX file")?;
480    let serializer = crate::dna::mds::serializer::BinarySerializer::new(true);
481    serializer
482        .write_to_file(&binary, &output_file)
483        .context("Failed to write compiled binary")?;
484    if verbose {
485        println!("✅ Compiled successfully: {}", output_file.display());
486        println!("  Size: {} bytes", binary.size());
487    }
488    Ok(output_file)
489}
490fn execute_binary(
491    binary_path: &PathBuf,
492    args: Vec<String>,
493    verbose: bool,
494) -> Result<()> {
495    if verbose {
496        println!("▶️  Executing binary: {}", binary_path.display());
497    }
498    let mut cmd = Command::new("echo");
499    cmd.arg("HELIX Runtime not yet implemented");
500    cmd.arg("Binary compiled successfully:");
501    cmd.arg(binary_path.to_string_lossy().as_ref());
502    if !args.is_empty() {
503        cmd.arg("Arguments:");
504        for arg in &args {
505            cmd.arg(arg);
506        }
507    }
508    let output = cmd.output().context("Failed to execute binary")?;
509    if !output.status.success() {
510        return Err(
511            anyhow::anyhow!(
512                "Binary execution failed with exit code: {}", output.status.code()
513                .unwrap_or(- 1)
514            ),
515        );
516    }
517    if !output.stdout.is_empty() {
518        print!("{}", String::from_utf8_lossy(& output.stdout));
519    }
520    if !output.stderr.is_empty() {
521        eprint!("{}", String::from_utf8_lossy(& output.stderr));
522    }
523    Ok(())
524}
525fn find_project_root() -> Result<PathBuf> {
526    let mut current_dir = std::env::current_dir()
527        .context("Failed to get current directory")?;
528    loop {
529        let manifest_path = current_dir.join("project.hlx");
530        if manifest_path.exists() {
531            return Ok(current_dir);
532        }
533        if let Some(parent) = current_dir.parent() {
534            current_dir = parent.to_path_buf();
535        } else {
536            break;
537        }
538    }
539    Err(anyhow::anyhow!("No HELIX project found. Run 'helix init' first."))
540}