Skip to main content

sema_vm/
emit.rs

1use sema_core::{Span, Value};
2
3use crate::chunk::Chunk;
4use crate::opcodes::Op;
5
6/// Builder for constructing bytecode chunks.
7pub struct Emitter {
8    chunk: Chunk,
9}
10
11impl Emitter {
12    pub fn new() -> Self {
13        Emitter {
14            chunk: Chunk::new(),
15        }
16    }
17
18    pub fn emit_op(&mut self, op: Op) {
19        self.chunk.code.push(op as u8);
20    }
21
22    pub fn emit_u16(&mut self, val: u16) {
23        self.chunk.code.extend_from_slice(&val.to_le_bytes());
24    }
25
26    pub fn emit_u32(&mut self, val: u32) {
27        self.chunk.code.extend_from_slice(&val.to_le_bytes());
28    }
29
30    pub fn emit_i32(&mut self, val: i32) {
31        self.chunk.code.extend_from_slice(&val.to_le_bytes());
32    }
33
34    /// Add a constant to the pool, deduplicating by value equality.
35    /// Returns the u16 index into the constant pool.
36    pub fn add_const(&mut self, val: Value) -> u16 {
37        for (i, existing) in self.chunk.consts.iter().enumerate() {
38            if *existing == val {
39                return i as u16;
40            }
41        }
42        let idx = self.chunk.consts.len();
43        self.chunk.consts.push(val);
44        idx as u16
45    }
46
47    /// Emit `Op::Const` followed by the u16 constant index.
48    pub fn emit_const(&mut self, val: Value) {
49        let idx = self.add_const(val);
50        self.emit_op(Op::Const);
51        self.emit_u16(idx);
52    }
53
54    /// Record a source span at the current PC position.
55    pub fn emit_span(&mut self, span: Span) {
56        self.chunk.spans.push((self.current_pc(), span));
57    }
58
59    /// Current code length as u32.
60    pub fn current_pc(&self) -> u32 {
61        self.chunk.code.len() as u32
62    }
63
64    /// Emit a jump instruction with a placeholder i32 offset.
65    /// Returns the PC of the placeholder for later backpatching.
66    pub fn emit_jump(&mut self, op: Op) -> u32 {
67        self.emit_op(op);
68        let placeholder_pc = self.current_pc();
69        self.emit_i32(0);
70        placeholder_pc
71    }
72
73    /// Backpatch the i32 at `placeholder_pc` with the relative offset
74    /// from the end of the jump instruction to the current PC.
75    pub fn patch_jump(&mut self, placeholder_pc: u32) {
76        let jump_end = placeholder_pc + 4; // end of the i32 operand
77        let offset = self.current_pc() as i32 - jump_end as i32;
78        let bytes = offset.to_le_bytes();
79        let pc = placeholder_pc as usize;
80        self.chunk.code[pc] = bytes[0];
81        self.chunk.code[pc + 1] = bytes[1];
82        self.chunk.code[pc + 2] = bytes[2];
83        self.chunk.code[pc + 3] = bytes[3];
84    }
85
86    /// Consume the emitter and return the finished Chunk.
87    pub fn into_chunk(self) -> Chunk {
88        self.chunk
89    }
90}
91
92impl Default for Emitter {
93    fn default() -> Self {
94        Self::new()
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_emit_const() {
104        let mut e = Emitter::new();
105        e.emit_const(Value::int(42));
106        e.emit_op(Op::Return);
107        let chunk = e.into_chunk();
108        assert_eq!(chunk.code[0], Op::Const as u8);
109        assert_eq!(chunk.code[1], 0);
110        assert_eq!(chunk.code[2], 0);
111        assert_eq!(chunk.code[3], Op::Return as u8);
112        assert_eq!(chunk.consts.len(), 1);
113        assert_eq!(chunk.consts[0], Value::int(42));
114    }
115
116    #[test]
117    fn test_emit_jump_and_patch() {
118        let mut e = Emitter::new();
119        e.emit_op(Op::Nil); // PC 0
120        let patch = e.emit_jump(Op::JumpIfFalse); // PC 1 (op), placeholder at PC 2-5
121        e.emit_op(Op::True); // PC 6
122        e.emit_op(Op::Return); // PC 7
123        e.patch_jump(patch); // target is current_pc = 8, jump_end = 6, offset = 8 - 6 = 2
124        e.emit_op(Op::False); // PC 8
125        e.emit_op(Op::Return); // PC 9
126        let chunk = e.into_chunk();
127        let offset = i32::from_le_bytes(chunk.code[2..6].try_into().unwrap());
128        // jump_end = patch(2) + 4 = 6, target = 8 at time of patch, offset = 8 - 6 = 2
129        assert_eq!(offset, 2);
130    }
131
132    #[test]
133    fn test_const_dedup() {
134        let mut e = Emitter::new();
135        let idx1 = e.add_const(Value::int(42));
136        let idx2 = e.add_const(Value::int(42));
137        assert_eq!(idx1, idx2);
138        assert_eq!(e.into_chunk().consts.len(), 1);
139    }
140
141    #[test]
142    fn test_emit_span() {
143        let mut e = Emitter::new();
144        e.emit_span(Span::point(1, 0));
145        e.emit_op(Op::Nil);
146        e.emit_span(Span::point(2, 4));
147        e.emit_op(Op::Return);
148        let chunk = e.into_chunk();
149        assert_eq!(chunk.spans.len(), 2);
150        assert_eq!(chunk.spans[0].0, 0);
151        assert_eq!(chunk.spans[0].1.line, 1);
152        assert_eq!(chunk.spans[0].1.col, 0);
153        assert_eq!(chunk.spans[1].0, 1);
154        assert_eq!(chunk.spans[1].1.line, 2);
155        assert_eq!(chunk.spans[1].1.col, 4);
156    }
157
158    #[test]
159    fn test_current_pc() {
160        let mut e = Emitter::new();
161        assert_eq!(e.current_pc(), 0);
162        e.emit_op(Op::Nil);
163        assert_eq!(e.current_pc(), 1);
164        e.emit_u16(42);
165        assert_eq!(e.current_pc(), 3);
166        e.emit_u32(100);
167        assert_eq!(e.current_pc(), 7);
168    }
169}