Skip to main content

mimium_lang/runtime/vm/
program.rs

1use std::path::PathBuf;
2
3use super::{ConstPos, Instruction, RawVal};
4use crate::compiler::IoChannelInfo;
5use crate::interner::TypeNodeId;
6use crate::mir;
7pub use mir::OpenUpValue;
8use state_tree::tree::StateTreeSkeleton;
9
10/// Function prototype definition in the bytecode program.
11
12/// Jump table for switch/match expressions.
13/// Uses a dense array for O(1) lookup when case values are within range.
14/// The last element of `offsets` is the default offset for out-of-range values.
15#[derive(Debug, Clone, PartialEq, Default)]
16pub struct JumpTable {
17    /// Minimum case value (used to calculate array index: index = value - min)
18    pub min: i64,
19    /// Dense array of offsets. Index = (value - min).
20    /// The last element is the default offset for values outside [min, min + len - 2].
21    pub offsets: Vec<i16>,
22}
23
24#[derive(Debug, Clone, PartialEq)]
25pub struct FuncProto {
26    pub nparam: usize,
27    pub nret: usize,
28    pub upindexes: Vec<OpenUpValue>,
29    pub bytecodes: Vec<Instruction>,
30    pub constants: Vec<RawVal>,
31    pub delay_sizes: Vec<u64>,
32    /// Jump tables for switch/match expressions.
33    pub jump_tables: Vec<JumpTable>,
34    /// StateTree skeleton information inherited from MIR for this function's state layout
35    pub state_skeleton: StateTreeSkeleton<mir::StateType>,
36}
37
38impl Default for FuncProto {
39    fn default() -> Self {
40        Self {
41            nparam: 0,
42            nret: 0,
43            upindexes: Vec::new(),
44            bytecodes: Vec::new(),
45            constants: Vec::new(),
46            delay_sizes: Vec::new(),
47            jump_tables: Vec::new(),
48            state_skeleton: StateTreeSkeleton::FnCall(vec![]), // Initialize as empty FnCall
49        }
50    }
51}
52impl FuncProto {
53    pub fn new(nparam: usize, nret: usize) -> Self {
54        Self {
55            nparam,
56            nret,
57            state_skeleton: StateTreeSkeleton::FnCall(vec![]), // Initialize as empty FnCall
58            ..Default::default()
59        }
60    }
61    /// Adds new constant to the program. Returns index in the array.
62    pub fn add_new_constant(&mut self, cval: RawVal) -> ConstPos {
63        self.constants.binary_search(&cval).unwrap_or_else(|_err| {
64            self.constants.push(cval);
65            self.constants.len() - 1
66        }) as _
67    }
68}
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct WordSize(pub u64);
71/// Complete bytecode programs.
72#[derive(Debug, Default, Clone, PartialEq)]
73pub struct Program {
74    pub global_fn_table: Vec<(String, FuncProto)>,
75    pub ext_fun_table: Vec<(String, TypeNodeId)>,
76    pub global_vals: Vec<WordSize>,
77    pub strings: Vec<String>,
78    pub file_path: Option<PathBuf>,
79    pub iochannels: Option<IoChannelInfo>,
80    //hold absolute index of dsp function because the symbol interner can't be used in the audio thread
81    pub dsp_index: Option<usize>,
82    /// Type table for CloneUserSum instruction.
83    /// Maps u8 index to TypeNodeId to keep instruction size within 64 bits.
84    pub type_table: Vec<TypeNodeId>,
85}
86impl Program {
87    pub fn get_fun_index(&self, name: &str) -> Option<usize> {
88        self.global_fn_table
89            .iter()
90            .position(|(label, _f)| label.as_str() == name)
91    }
92    pub fn get_dsp_fn(&self) -> Option<&FuncProto> {
93        self.get_fun_index("dsp")
94            .and_then(|idx| self.global_fn_table.get(idx).map(|(_, f)| f))
95    }
96
97    /// Get the StateTreeSkeleton for the dsp function (commonly used for audio processing)
98    pub fn get_dsp_state_skeleton(&self) -> Option<&StateTreeSkeleton<mir::StateType>> {
99        let dsp_i = self.dsp_index?;
100        self.global_fn_table
101            .get(dsp_i)
102            .map(|(_, f)| &f.state_skeleton)
103    }
104
105    /// Add a type to the type table and return its index.
106    /// If the type already exists, returns the existing index.
107    /// Returns None if the type table is full (max 256 entries).
108    pub fn add_type_to_table(&mut self, ty: TypeNodeId) -> Option<u8> {
109        // Check if type already exists
110        if let Some(idx) = self.type_table.iter().position(|&t| t == ty) {
111            return Some(idx as u8);
112        }
113        // Add new type if table is not full
114        if self.type_table.len() < 256 {
115            self.type_table.push(ty);
116            Some((self.type_table.len() - 1) as u8)
117        } else {
118            None // Type table overflow
119        }
120    }
121
122    /// Get TypeNodeId from type table by index.
123    pub fn get_type_from_table(&self, idx: u8) -> Option<TypeNodeId> {
124        self.type_table.get(idx as usize).copied()
125    }
126    pub fn add_new_str(&mut self, s: String) -> usize {
127        self.strings
128            .iter()
129            .position(|c| s == *c)
130            .unwrap_or_else(|| {
131                self.strings.push(s);
132                self.strings.len() - 1
133            })
134    }
135}
136
137impl std::fmt::Display for Program {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        for fns in self.global_fn_table.iter() {
140            let _ = writeln!(f, "{}", fns.0);
141            let _ = writeln!(f, "nparams:{} nret: {}", fns.1.nparam, fns.1.nret);
142            let _ = write!(f, "upindexes: {:?}  ", fns.1.upindexes);
143            let _ = writeln!(f, "state_skeleton: {:?}", fns.1.state_skeleton);
144            let _ = writeln!(f, "constants:  {:?}", fns.1.constants);
145            let _ = writeln!(f, "jump_tables: {:?}", fns.1.jump_tables);
146            let _ = writeln!(f, "instructions:");
147            for inst in fns.1.bytecodes.iter() {
148                let _ = writeln!(f, "  {inst}");
149            }
150        }
151        let _ = write!(
152            f,
153            "ext_fun:\n{:?}\n",
154            self.ext_fun_table
155                .iter()
156                .fold("".to_string(), |s, (f, _)| if s.is_empty() {
157                    format!("{f}")
158                } else {
159                    format!("{s}, {f}")
160                })
161        );
162        let _ = write!(f, "globals:\n{:?}", self.global_vals);
163        writeln!(
164            f,
165            "strings:  {:?}",
166            self.strings
167                .iter()
168                .map(|s| s.to_string())
169                .collect::<Vec<_>>()
170        )
171    }
172}