swamp_vm_instr_build/
lib.rs

1use swamp_vm_types::opcode::OpCode;
2use swamp_vm_types::{
3    BinaryInstruction, FrameMemoryAddress, FrameMemorySize, InstructionPosition, MemoryAddress,
4    MemoryOffset, MemorySize,
5};
6pub const INT_SIZE: u16 = 4;
7pub const FLOAT_SIZE: u16 = 4;
8pub const BOOL_SIZE: u16 = 1;
9pub const PTR_SIZE: u16 = 2;
10pub const STR_SIZE: u16 = VEC_SIZE; // TODO: FIX THIS
11pub const VEC_SIZE: u16 = 2 + 2 + 2;
12
13#[derive(Debug)]
14pub struct PatchPosition(pub InstructionPosition);
15
16pub struct InstructionBuilder {
17    pub instructions: Vec<BinaryInstruction>,
18    pub comments: Vec<String>,
19}
20
21impl InstructionBuilder {}
22
23impl Default for InstructionBuilder {
24    fn default() -> Self {
25        Self::new()
26    }
27}
28
29impl InstructionBuilder {
30    #[must_use]
31    pub const fn new() -> Self {
32        Self {
33            instructions: Vec::new(),
34            comments: Vec::new(),
35        }
36    }
37
38    #[must_use]
39    pub fn position(&self) -> InstructionPosition {
40        InstructionPosition(self.instructions.len() as u16)
41    }
42
43    pub fn add_conditional_jump_placeholder(
44        &mut self,
45        condition_addr: FrameMemoryAddress,
46        comment: &str,
47    ) -> PatchPosition {
48        let position = self.position();
49
50        self.add_instruction(OpCode::Bz, &[condition_addr.0, 0], comment);
51
52        PatchPosition(position)
53    }
54
55    pub fn add_call_placeholder(&mut self, comment: &str) -> PatchPosition {
56        let position = self.position();
57        self.add_instruction(OpCode::Call, &[0], comment);
58        PatchPosition(position)
59    }
60
61    pub fn add_jump_placeholder(&mut self, comment: &str) -> PatchPosition {
62        let position = self.position();
63
64        self.add_instruction(OpCode::Jmp, &[0], comment);
65
66        PatchPosition(position)
67    }
68
69    pub fn add_enter(&mut self, size: FrameMemorySize, comment: &str) {
70        self.add_instruction(OpCode::Enter, &[size.0], comment);
71    }
72
73    // Mov is more of a copy. Keeping the name Mov because it is old school and idiomatic.
74    pub fn add_mov(
75        &mut self,
76        target: FrameMemoryAddress,
77        source: FrameMemoryAddress,
78        size: MemorySize,
79        comment: &str,
80    ) {
81        self.add_instruction(OpCode::Mov, &[target.0, source.0, size.0], comment);
82    }
83
84    pub fn add_ret(&mut self, comment: &str) {
85        self.add_instruction(OpCode::Ret, &[], comment);
86    }
87
88    pub fn add_hlt(&mut self, comment: &str) {
89        self.add_instruction(OpCode::Hlt, &[], comment);
90    }
91
92    pub fn add_call(&mut self, function_ip: &InstructionPosition, comment: &str) {
93        self.add_instruction(OpCode::Call, &[function_ip.0], comment);
94    }
95
96    pub fn add_alloc(&mut self, target: FrameMemoryAddress, size: MemorySize, comment: &str) {
97        self.add_instruction(OpCode::Alloc, &[target.0, size.0], comment);
98    }
99
100    /// # Panics
101    ///
102    pub fn patch_jump(
103        &mut self,
104        patch_position: PatchPosition,
105        target_position: &InstructionPosition,
106    ) {
107        const JMP_IF_NOT: u8 = OpCode::Bz as u8;
108        const JMP: u8 = OpCode::Jmp as u8;
109
110        let instruction = &mut self.instructions[patch_position.0.0 as usize];
111
112        match instruction.opcode {
113            JMP_IF_NOT => {
114                // For conditional jump, target ip addr is the second operand
115                instruction.operands[1] = target_position.0 as u16 - 1;
116            }
117            JMP => {
118                // For conditional jump, target ip addr is the first operand
119                // TODO: maybe have them both at the first operand?
120                instruction.operands[0] = target_position.0 as u16 - 1;
121            }
122            _ => panic!("Attempted to patch a non-jump instruction at position {patch_position:?}"),
123        }
124    }
125
126    // It takes ownership of the patch position
127    pub fn patch_jump_here(&mut self, jump_position: PatchPosition) {
128        self.patch_jump(jump_position, &self.position());
129    }
130
131    /// # Panics
132    ///
133    pub fn patch_call(&mut self, patch_position: PatchPosition, ip: &InstructionPosition) {
134        const CALL: u8 = OpCode::Call as u8;
135
136        let instruction = &mut self.instructions[patch_position.0.0 as usize];
137
138        match instruction.opcode {
139            CALL => {
140                instruction.operands[0] = ip.0 as u16 - 1;
141            }
142            _ => panic!("Attempted to patch a non-call instruction at position {patch_position:?}"),
143        }
144    }
145
146    pub fn add_jmp(&mut self, ip: InstructionPosition, comment: &str) {
147        self.add_instruction(OpCode::Jmp, &[ip.0 - 1], comment);
148    }
149
150    pub fn add_ld32(&mut self, dst_offset: FrameMemoryAddress, value: i32, comment: &str) {
151        let value_u32 = value as u32;
152
153        let lower_bits = (value_u32 & 0xFFFF) as u16;
154        let upper_bits = (value_u32 >> 16) as u16;
155
156        self.add_instruction(
157            OpCode::Ld32,
158            &[dst_offset.0, lower_bits, upper_bits],
159            comment,
160        );
161    }
162
163    pub fn add_ld8(&mut self, dst_offset: FrameMemoryAddress, value: u8, comment: &str) {
164        self.add_instruction(OpCode::Ld8, &[dst_offset.0, value as u16], comment);
165    }
166
167    pub fn add_load_frame_address(
168        &mut self,
169        dest: FrameMemoryAddress,
170        addr: FrameMemoryAddress,
171        comment: &str,
172    ) {
173        self.add_ld_u16(dest, addr.0, comment);
174    }
175
176    pub fn add_ld_u16(&mut self, dest: FrameMemoryAddress, data: u16, comment: &str) {
177        self.add_instruction(OpCode::Ld16, &[dest.0, data], comment);
178    }
179
180    pub fn add_stx(
181        &mut self,
182        indirect_target: FrameMemoryAddress,
183        offset: MemoryOffset,
184        source_address: FrameMemoryAddress,
185        size: MemorySize,
186        comment: &str,
187    ) {
188        self.add_instruction(
189            OpCode::Stx,
190            &[indirect_target.0, offset.0, source_address.0, size.0],
191            comment,
192        );
193    }
194
195    pub fn add_add_i32(
196        &mut self,
197        dst_offset: FrameMemoryAddress,
198        lhs_offset: FrameMemoryAddress,
199        rhs_offset: FrameMemoryAddress,
200        comment: &str,
201    ) {
202        self.add_instruction(
203            OpCode::AddI32,
204            &[dst_offset.0, lhs_offset.0, rhs_offset.0],
205            comment,
206        );
207    }
208
209    pub fn add_jmp_if(
210        &mut self,
211        condition_offset: FrameMemoryAddress,
212        jmp_target: &InstructionPosition,
213        comment: &str,
214    ) {
215        self.add_instruction(OpCode::Bnz, &[condition_offset.0, jmp_target.0], comment);
216    }
217
218    pub fn add_jmp_if_not(
219        &mut self,
220        condition_offset: MemoryAddress,
221        jmp_target: InstructionPosition,
222        comment: &str,
223    ) {
224        self.add_instruction(OpCode::Bz, &[condition_offset.0, jmp_target.0], comment);
225    }
226
227    pub fn add_lt_i32(
228        &mut self,
229        dst_offset: FrameMemoryAddress,
230        lhs_offset: FrameMemoryAddress,
231        rhs_offset: FrameMemoryAddress,
232        comment: &str,
233    ) {
234        self.add_instruction(
235            OpCode::LtI32,
236            &[dst_offset.0, lhs_offset.0, rhs_offset.0],
237            comment,
238        );
239    }
240
241    pub fn add_lt_u16(
242        &mut self,
243        dest: FrameMemoryAddress,
244        a: FrameMemoryAddress,
245        b: FrameMemoryAddress,
246        comment: &str,
247    ) {
248        self.add_instruction(OpCode::LtU16, &[dest.0, a.0, b.0], comment);
249    }
250
251    fn add_instruction(&mut self, op_code: OpCode, operands: &[u16], comment: &str) {
252        let mut array: [u16; 4] = [0; 4];
253        let len = operands.len().min(4);
254        array[..len].copy_from_slice(&operands[..len]);
255        self.instructions.push(BinaryInstruction {
256            opcode: op_code as u8,
257            opcode_count: operands.len() as u8,
258            operands: array,
259        });
260        self.comments.push(comment.to_string());
261    }
262}