Skip to main content

mailang_bytecode/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3extern crate alloc;
4
5use alloc::boxed::Box;
6use alloc::rc::Rc;
7use alloc::string::String;
8use alloc::string::ToString;
9use alloc::vec;
10use alloc::vec::Vec;
11use core::cell::RefCell;
12
13#[cfg(all(feature = "serde", feature = "std"))]
14use serde::{Deserialize, Serialize};
15
16pub mod disasm;
17pub mod format;
18pub mod register;
19pub use disasm::{disassemble_register, disassemble_stack};
20pub use format::{decode, encode, BytecodeFormatError, FORMAT_MAGIC, FORMAT_VERSION};
21pub use register::{parameter_slots, RegChunk, RegOp, StackToRegister};
22
23/// Opcode is `Copy` and serialized as a single byte in the binary format.
24/// Keep variants in a stable order; append-only.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26#[cfg_attr(
27    all(feature = "serde", feature = "std"),
28    derive(Serialize, Deserialize)
29)]
30#[repr(u8)]
31pub enum Opcode {
32    // Stack operations
33    Push = 0,
34    Pop,
35    Dup,
36
37    // Load/Store
38    LoadLocal,
39    StoreLocal,
40    LoadGlobal,
41    StoreGlobal,
42    LoadUpvalue,
43    StoreUpvalue,
44
45    // Arithmetic
46    Add,
47    Sub,
48    Mul,
49    Div,
50    Mod,
51    Pow,
52    Neg,
53
54    // Bitwise
55    BitAnd,
56    BitOr,
57    BitXor,
58    BitNot,
59    Shl,
60    Shr,
61
62    // Comparison
63    Eq,
64    Ne,
65    Lt,
66    Le,
67    Gt,
68    Ge,
69    // Fused int immediate ops (hot path: n-1, n<=1, ...)
70    AddImm,
71    SubImm,
72    MulImm,
73    EqImm,
74    NeImm,
75    LtImm,
76    LeImm,
77    GtImm,
78    GeImm,
79
80    // Logical
81    And,
82    Or,
83    Not,
84
85    // Control flow
86    Jump,
87    JumpIfFalse,
88    JumpIfTrue,
89    Call,
90    TailCall,
91    Return,
92
93    // Object operations
94    GetProperty,
95    SetProperty,
96    Invoke,
97
98    // Array/Map
99    BuildArray,
100    BuildMap,
101    IndexGet,
102    IndexSet,
103
104    // Class/Trait
105    CreateClass,
106    CreateInstance,
107    GetMethod,
108
109    // Pattern matching
110    MatchPattern,
111
112    // Closure creation
113    MakeClosure,
114
115    // Error handling
116    Throw,
117    TryBegin,
118    TryEnd,
119    WrapOk,
120    WrapErr,
121    WrapSome,
122    UnwrapOk,
123    UnwrapErr,
124    UnwrapSome,
125    /// `?` — Ok/Some → inner; Err/Null early-return from current frame.
126    Try,
127
128    // Special
129    Nop,
130    Halt,
131    /// Direct call to a known chunk. operand = (chunk_index << 16) | arity.
132    /// Stack: [func_slot_placeholder, args...]
133    CallDirect,
134    /// Push the number of arguments actually passed to the current frame.
135    Argc,
136    /// Keep the top value, drop the `operand` values directly below it.
137    /// Used by `match` arms to discard the scrutinee temporary and the
138    /// pattern bindings under the arm's result. Appended last so the
139    /// discriminants of every earlier opcode stay stable.
140    Slide,
141}
142
143impl Opcode {
144    /// Stable discriminant used by the binary bytecode format.
145    pub fn as_u8(self) -> u8 {
146        self as u8
147    }
148
149    pub fn from_u8(v: u8) -> Option<Self> {
150        Some(match v {
151            0 => Opcode::Push,
152            1 => Opcode::Pop,
153            2 => Opcode::Dup,
154            3 => Opcode::LoadLocal,
155            4 => Opcode::StoreLocal,
156            5 => Opcode::LoadGlobal,
157            6 => Opcode::StoreGlobal,
158            7 => Opcode::LoadUpvalue,
159            8 => Opcode::StoreUpvalue,
160            9 => Opcode::Add,
161            10 => Opcode::Sub,
162            11 => Opcode::Mul,
163            12 => Opcode::Div,
164            13 => Opcode::Mod,
165            14 => Opcode::Pow,
166            15 => Opcode::Neg,
167            16 => Opcode::BitAnd,
168            17 => Opcode::BitOr,
169            18 => Opcode::BitXor,
170            19 => Opcode::BitNot,
171            20 => Opcode::Shl,
172            21 => Opcode::Shr,
173            22 => Opcode::Eq,
174            23 => Opcode::Ne,
175            24 => Opcode::Lt,
176            25 => Opcode::Le,
177            26 => Opcode::Gt,
178            27 => Opcode::Ge,
179            28 => Opcode::AddImm,
180            29 => Opcode::SubImm,
181            30 => Opcode::MulImm,
182            31 => Opcode::EqImm,
183            32 => Opcode::NeImm,
184            33 => Opcode::LtImm,
185            34 => Opcode::LeImm,
186            35 => Opcode::GtImm,
187            36 => Opcode::GeImm,
188            37 => Opcode::And,
189            38 => Opcode::Or,
190            39 => Opcode::Not,
191            40 => Opcode::Jump,
192            41 => Opcode::JumpIfFalse,
193            42 => Opcode::JumpIfTrue,
194            43 => Opcode::Call,
195            44 => Opcode::TailCall,
196            45 => Opcode::Return,
197            46 => Opcode::GetProperty,
198            47 => Opcode::SetProperty,
199            48 => Opcode::Invoke,
200            49 => Opcode::BuildArray,
201            50 => Opcode::BuildMap,
202            51 => Opcode::IndexGet,
203            52 => Opcode::IndexSet,
204            53 => Opcode::CreateClass,
205            54 => Opcode::CreateInstance,
206            55 => Opcode::GetMethod,
207            56 => Opcode::MatchPattern,
208            57 => Opcode::MakeClosure,
209            58 => Opcode::Throw,
210            59 => Opcode::TryBegin,
211            60 => Opcode::TryEnd,
212            61 => Opcode::WrapOk,
213            62 => Opcode::WrapErr,
214            63 => Opcode::WrapSome,
215            64 => Opcode::UnwrapOk,
216            65 => Opcode::UnwrapErr,
217            66 => Opcode::UnwrapSome,
218            67 => Opcode::Try,
219            68 => Opcode::Nop,
220            69 => Opcode::Halt,
221            70 => Opcode::CallDirect,
222            71 => Opcode::Argc,
223            72 => Opcode::Slide,
224            _ => return None,
225        })
226    }
227}
228
229/// Fat payloads are boxed so `Value` stays small on the operand stack.
230#[derive(Debug, Clone, PartialEq)]
231#[cfg_attr(
232    all(feature = "serde", feature = "std"),
233    derive(Serialize, Deserialize)
234)]
235pub struct FunctionObj {
236    pub name: Rc<str>,
237    /// Maximum number of parameters (all params, including those with defaults).
238    pub arity: usize,
239    /// Parameters that must be supplied (those without defaults).
240    pub required: usize,
241    pub chunk_index: usize,
242}
243
244#[derive(Debug, Clone, PartialEq)]
245#[cfg_attr(
246    all(feature = "serde", feature = "std"),
247    derive(Serialize, Deserialize)
248)]
249pub struct ClosureObj {
250    pub function_index: usize,
251    pub arity: usize,
252    pub required: usize,
253    pub upvalues: Vec<usize>,
254}
255
256#[derive(Debug, Clone, PartialEq)]
257#[cfg_attr(
258    all(feature = "serde", feature = "std"),
259    derive(Serialize, Deserialize)
260)]
261pub struct ClassObj {
262    pub name: Rc<str>,
263    pub methods: Rc<Vec<(String, usize)>>,
264    pub superclass: Option<Rc<str>>,
265    pub properties: Rc<Vec<(String, Value)>>,
266}
267
268#[derive(Debug, Clone)]
269#[cfg_attr(
270    all(feature = "serde", feature = "std"),
271    derive(Serialize, Deserialize)
272)]
273pub enum Value {
274    Null,
275    Bool(bool),
276    Int(i64),
277    Float(f64),
278    Str(Rc<str>),
279    Char(char),
280    Array(Rc<RefCell<Vec<Value>>>),
281    Map(Rc<RefCell<Vec<(Value, Value)>>>),
282    Tuple(Rc<RefCell<Vec<Value>>>),
283    Function(Rc<FunctionObj>),
284    Closure(Rc<ClosureObj>),
285    Class(Rc<ClassObj>),
286    Instance {
287        class_index: usize,
288        fields: Rc<RefCell<Vec<(String, Value)>>>,
289    },
290    Ok(Box<Value>),
291    Err(Box<Value>),
292    Some(Box<Value>),
293    Builtin {
294        name: Rc<str>,
295        arity: usize,
296    },
297}
298
299impl PartialEq for Value {
300    fn eq(&self, other: &Self) -> bool {
301        match (self, other) {
302            (Value::Null, Value::Null) => true,
303            (Value::Bool(a), Value::Bool(b)) => a == b,
304            (Value::Int(a), Value::Int(b)) => a == b,
305            (Value::Float(a), Value::Float(b)) => a == b,
306            (Value::Str(a), Value::Str(b)) => a == b,
307            (Value::Char(a), Value::Char(b)) => a == b,
308            (Value::Array(a), Value::Array(b)) => *a.borrow() == *b.borrow(),
309            (Value::Map(a), Value::Map(b)) => *a.borrow() == *b.borrow(),
310            (Value::Tuple(a), Value::Tuple(b)) => *a.borrow() == *b.borrow(),
311            (Value::Function(a), Value::Function(b)) => a == b,
312            (Value::Closure(a), Value::Closure(b)) => a == b,
313            (Value::Class(a), Value::Class(b)) => a == b,
314            (
315                Value::Instance {
316                    class_index: c1,
317                    fields: f1,
318                },
319                Value::Instance {
320                    class_index: c2,
321                    fields: f2,
322                },
323            ) => c1 == c2 && *f1.borrow() == *f2.borrow(),
324            (Value::Ok(a), Value::Ok(b)) => a == b,
325            (Value::Err(a), Value::Err(b)) => a == b,
326            (Value::Some(a), Value::Some(b)) => a == b,
327            (
328                Value::Builtin {
329                    name: n1,
330                    arity: a1,
331                },
332                Value::Builtin {
333                    name: n2,
334                    arity: a2,
335                },
336            ) => n1 == n2 && a1 == a2,
337            _ => false,
338        }
339    }
340}
341
342/// Compact, `Copy` instruction — fetched by value on the hot path.
343#[derive(Debug, Clone, Copy, PartialEq)]
344#[cfg_attr(
345    all(feature = "serde", feature = "std"),
346    derive(Serialize, Deserialize)
347)]
348pub struct Instruction {
349    pub opcode: Opcode,
350    pub operand: Option<u32>,
351    pub line: u32,
352}
353
354#[derive(Debug, Clone, PartialEq)]
355#[cfg_attr(
356    all(feature = "serde", feature = "std"),
357    derive(Serialize, Deserialize)
358)]
359pub struct Chunk {
360    pub instructions: Vec<Instruction>,
361    pub constants: Vec<Value>,
362    pub name: String,
363}
364
365impl Chunk {
366    pub fn new(name: String) -> Self {
367        Self {
368            instructions: Vec::new(),
369            constants: Vec::new(),
370            name,
371        }
372    }
373
374    pub fn emit(&mut self, opcode: Opcode, operand: Option<u32>, line: u32) {
375        self.instructions.push(Instruction {
376            opcode,
377            operand,
378            line,
379        });
380    }
381
382    pub fn add_constant(&mut self, value: Value) -> u32 {
383        let index = self.constants.len() as u32;
384        self.constants.push(value);
385        index
386    }
387}
388
389#[derive(Debug, Clone, PartialEq)]
390#[cfg_attr(
391    all(feature = "serde", feature = "std"),
392    derive(Serialize, Deserialize)
393)]
394pub struct Bytecode {
395    pub chunks: Vec<Chunk>,
396    pub main_chunk: usize,
397    /// Global variable names indexed by slot ID. Used for LoadGlobal/StoreGlobal.
398    pub global_names: Vec<String>,
399}
400
401impl Bytecode {
402    pub fn new() -> Self {
403        let main_chunk = Chunk::new("main".to_string());
404        Self {
405            chunks: vec![main_chunk],
406            main_chunk: 0,
407            global_names: Vec::new(),
408        }
409    }
410
411    /// Register a global name and return its slot index.
412    pub fn intern_global(&mut self, name: &str) -> u32 {
413        if let Some(pos) = self.global_names.iter().position(|n| n == name) {
414            return pos as u32;
415        }
416        let idx = self.global_names.len() as u32;
417        self.global_names.push(name.to_string());
418        idx
419    }
420}
421
422impl Default for Bytecode {
423    fn default() -> Self {
424        Self::new()
425    }
426}