Skip to main content

compiler/
op_code.rs

1use std::collections::HashMap;
2
3use byteorder;
4use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
5
6use strum::{EnumCount, EnumIter, FromRepr};
7
8// why not type, see https://stackoverflow.com/a/35569079/1713757
9#[derive(Hash, Eq, Debug, Clone, PartialEq, PartialOrd)]
10pub struct Instructions {
11    pub data: Vec<u8>,
12}
13
14pub struct OpcodeDefinition {
15    pub(crate) name: &'static str,
16    operand_width: Vec<i32>,
17}
18
19impl OpcodeDefinition {
20    pub fn name(&self) -> &'static str {
21        self.name
22    }
23
24    pub fn operand_widths(&self) -> &[i32] {
25        &self.operand_width
26    }
27}
28
29#[repr(u8)]
30#[derive(Debug, Hash, Eq, Clone, Copy, PartialEq, EnumCount, EnumIter, FromRepr)]
31pub enum Opcode {
32    OpConst,
33    OpAdd,
34    OpPop,
35    OpSub,
36    OpMul,
37    OpDiv,
38    OpTrue,
39    OpFalse,
40    OpEqual,
41    OpNotEqual,
42    OpGreaterThan,
43    OpMinus,
44    OpBang,
45    OpJumpNotTruthy,
46    OpJump,
47    OpNull,
48    OpGetGlobal,
49    OpSetGlobal,
50    OpArray,
51    OpHash,
52    OpIndex,
53    OpCall,
54    OpReturnValue,
55    OpReturn,
56    OpGetLocal,
57    OpSetLocal,
58    OpGetBuiltin,
59    OpClosure,
60    OpGetFree,
61    OpCurrentClosure,
62    OpClass,
63    OpMethod,
64    OpGetProperty,
65    OpSetProperty,
66    OpNew,
67    // Appended to preserve the byte values of all existing opcodes.
68    OpLessThan,
69    OpDebugger,
70}
71
72lazy_static! {
73    pub static ref DEFINITIONS: HashMap<Opcode, OpcodeDefinition> = {
74        let mut m = HashMap::new();
75        m.insert(
76            Opcode::OpConst,
77            OpcodeDefinition {
78                name: "OpConst",
79                operand_width: vec![2],
80            },
81        );
82        m.insert(
83            Opcode::OpAdd,
84            OpcodeDefinition {
85                name: "OpAdd",
86                operand_width: vec![],
87            },
88        );
89        m.insert(
90            Opcode::OpPop,
91            OpcodeDefinition {
92                name: "OpPop",
93                operand_width: vec![],
94            },
95        );
96        m.insert(
97            Opcode::OpSub,
98            OpcodeDefinition {
99                name: "OpSub",
100                operand_width: vec![],
101            },
102        );
103        m.insert(
104            Opcode::OpMul,
105            OpcodeDefinition {
106                name: "OpMul",
107                operand_width: vec![],
108            },
109        );
110        m.insert(
111            Opcode::OpDiv,
112            OpcodeDefinition {
113                name: "OpDiv",
114                operand_width: vec![],
115            },
116        );
117        m.insert(
118            Opcode::OpTrue,
119            OpcodeDefinition {
120                name: "OpTrue",
121                operand_width: vec![],
122            },
123        );
124        m.insert(
125            Opcode::OpFalse,
126            OpcodeDefinition {
127                name: "OpFalse",
128                operand_width: vec![],
129            },
130        );
131        m.insert(
132            Opcode::OpEqual,
133            OpcodeDefinition {
134                name: "OpEqual",
135                operand_width: vec![],
136            },
137        );
138        m.insert(
139            Opcode::OpNotEqual,
140            OpcodeDefinition {
141                name: "OpNotEqual",
142                operand_width: vec![],
143            },
144        );
145        m.insert(
146            Opcode::OpGreaterThan,
147            OpcodeDefinition {
148                name: "OpGreatThan",
149                operand_width: vec![],
150            },
151        );
152        m.insert(
153            Opcode::OpLessThan,
154            OpcodeDefinition {
155                name: "OpLessThan",
156                operand_width: vec![],
157            },
158        );
159        m.insert(
160            Opcode::OpMinus,
161            OpcodeDefinition {
162                name: "OpMinus",
163                operand_width: vec![],
164            },
165        );
166        m.insert(
167            Opcode::OpBang,
168            OpcodeDefinition {
169                name: "OpBang",
170                operand_width: vec![],
171            },
172        );
173        m.insert(
174            Opcode::OpJumpNotTruthy,
175            OpcodeDefinition {
176                name: "OpJumpNotTruthy",
177                operand_width: vec![2],
178            },
179        );
180        m.insert(
181            Opcode::OpJump,
182            OpcodeDefinition {
183                name: "OpJump",
184                operand_width: vec![2],
185            },
186        );
187        m.insert(
188            Opcode::OpNull,
189            OpcodeDefinition {
190                name: "OpNull",
191                operand_width: vec![],
192            },
193        );
194        m.insert(
195            Opcode::OpGetGlobal,
196            OpcodeDefinition {
197                name: "OpGetGlobal",
198                operand_width: vec![2],
199            },
200        );
201        m.insert(
202            Opcode::OpSetGlobal,
203            OpcodeDefinition {
204                name: "OpSetGlobal",
205                operand_width: vec![2],
206            },
207        );
208        m.insert(
209            Opcode::OpArray,
210            OpcodeDefinition {
211                name: "OpArray",
212                operand_width: vec![2],
213            },
214        );
215        m.insert(
216            Opcode::OpHash,
217            OpcodeDefinition {
218                name: "OpHash",
219                operand_width: vec![2],
220            },
221        );
222        m.insert(
223            Opcode::OpIndex,
224            OpcodeDefinition {
225                name: "OpIndex",
226                operand_width: vec![],
227            },
228        );
229        m.insert(
230            Opcode::OpCall,
231            OpcodeDefinition {
232                name: "OpCall",
233                operand_width: vec![1],
234            },
235        );
236        m.insert(
237            Opcode::OpReturn,
238            OpcodeDefinition {
239                name: "OpReturn",
240                operand_width: vec![],
241            },
242        );
243        m.insert(
244            Opcode::OpReturnValue,
245            OpcodeDefinition {
246                name: "OpReturnValue",
247                operand_width: vec![],
248            },
249        );
250        m.insert(
251            Opcode::OpGetLocal,
252            OpcodeDefinition {
253                name: "OpGetLocal",
254                operand_width: vec![1],
255            },
256        );
257        m.insert(
258            Opcode::OpSetLocal,
259            OpcodeDefinition {
260                name: "OpSetLocal",
261                operand_width: vec![1],
262            },
263        );
264        m.insert(
265            Opcode::OpGetBuiltin,
266            OpcodeDefinition {
267                name: "OpGetBuiltin",
268                operand_width: vec![1],
269            },
270        );
271        m.insert(
272            Opcode::OpClosure,
273            OpcodeDefinition {
274                name: "OpClosure",
275                operand_width: vec![2, 1],
276            },
277        );
278        m.insert(
279            Opcode::OpGetFree,
280            OpcodeDefinition {
281                name: "OpGetFree",
282                operand_width: vec![1],
283            },
284        );
285        m.insert(
286            Opcode::OpCurrentClosure,
287            OpcodeDefinition {
288                name: "OpCurrentClosure",
289                operand_width: vec![],
290            },
291        );
292        m.insert(
293            Opcode::OpClass,
294            OpcodeDefinition {
295                name: "OpClass",
296                operand_width: vec![2],
297            },
298        );
299        m.insert(
300            Opcode::OpMethod,
301            OpcodeDefinition {
302                name: "OpMethod",
303                operand_width: vec![2, 1],
304            },
305        );
306        m.insert(
307            Opcode::OpGetProperty,
308            OpcodeDefinition {
309                name: "OpGetProperty",
310                operand_width: vec![2],
311            },
312        );
313        m.insert(
314            Opcode::OpSetProperty,
315            OpcodeDefinition {
316                name: "OpSetProperty",
317                operand_width: vec![2],
318            },
319        );
320        m.insert(
321            Opcode::OpNew,
322            OpcodeDefinition {
323                name: "OpNew",
324                operand_width: vec![1],
325            },
326        );
327        m.insert(
328            Opcode::OpDebugger,
329            OpcodeDefinition {
330                name: "OpDebugger",
331                operand_width: vec![],
332            },
333        );
334        return m;
335    };
336}
337
338/// # Panics
339///
340/// If an operand does not fit its opcode's fixed width. `Compiler` rejects
341/// those with a `CompileError` before reaching here, so a panic means the
342/// caller built the instruction by hand. The alternative — truncating with
343/// `as u8` / `as u16` — is a silent miscompile, so this is checked in release
344/// too rather than left to `debug_assert!`.
345pub fn make_instructions(op: Opcode, operands: &[usize]) -> Instructions {
346    let mut instructions = Vec::new();
347    instructions.push(op as u8);
348    let widths = &DEFINITIONS.get(&op).unwrap().operand_width;
349
350    for (o, w) in operands.iter().zip(widths) {
351        match w {
352            2 => {
353                assert!(
354                    *o <= u16::MAX as usize,
355                    "{:?} operand {} does not fit in 2 bytes; the compiler must \
356                     reject it before emitting rather than truncate it here",
357                    op,
358                    o
359                );
360                instructions.write_u16::<BigEndian>(*o as u16).unwrap();
361            }
362            1 => {
363                assert!(
364                    *o <= u8::MAX as usize,
365                    "{:?} operand {} does not fit in 1 byte; the compiler must \
366                     reject it before emitting rather than truncate it here",
367                    op,
368                    o
369                );
370                instructions.write_u8(*o as u8).unwrap();
371            }
372            _ => {
373                panic!("unsupported operand width {}", w)
374            }
375        }
376    }
377
378    return Instructions {
379        data: instructions,
380    };
381}
382
383pub fn read_operands(def: &OpcodeDefinition, ins: &[u8]) -> (Vec<usize>, usize) {
384    let mut operands = Vec::with_capacity(def.operand_width.len());
385    let mut offset = 0;
386
387    for w in &def.operand_width {
388        match w {
389            2 => {
390                operands.push(BigEndian::read_u16(&ins[offset..offset + 2]) as usize);
391                offset += 2;
392            }
393            1 => {
394                operands.push(ins[offset] as usize);
395                offset += 1;
396            }
397            0 => {}
398            _ => {
399                panic!("unsupported operand width {} for read", w)
400            }
401        }
402    }
403
404    return (operands, offset);
405}
406
407pub fn concat_instructions(expected: &Vec<Instructions>) -> Instructions {
408    let mut out = Instructions {
409        data: vec![],
410    };
411
412    for instruction in expected {
413        out = out.merge_instructions(instruction)
414    }
415
416    return out;
417}
418
419impl Instructions {
420    // prettify bytecodes
421    pub fn string(&self) -> String {
422        let mut ret = String::new();
423        let mut i = 0;
424        while i < self.data.len() {
425            let op: u8 = self.data[i];
426            let Some(opcode) = Opcode::from_repr(op) else {
427                ret.push_str(&format!("{:04} <unknown opcode 0x{:02x}>\n", i, op));
428                i += 1;
429                continue;
430            };
431
432            let definition = DEFINITIONS.get(&opcode).unwrap();
433            let width: usize = definition.operand_width.iter().map(|w| *w as usize).sum();
434            if i + 1 + width > self.data.len() {
435                ret.push_str(&format!("{:04} {} <truncated operands>\n", i, definition.name));
436                break;
437            }
438            let (operands, read_size) = read_operands(definition, &self.data[i + 1..]);
439            ret.push_str(&format!("{:04} {}\n", i, Self::fmt_instructions(definition, &operands)));
440            i = i + 1 + read_size;
441        }
442
443        return ret;
444    }
445
446    fn fmt_instructions(def: &OpcodeDefinition, operands: &[usize]) -> String {
447        match def.operand_width.len() {
448            2 => format!("{} {} {}", def.name, operands[0], operands[1]),
449            1 => format!("{} {}", def.name, operands[0]),
450            0 => def.name.to_string(),
451            _ => {
452                panic!("unsupported operand width {}", def.operand_width.len());
453            }
454        }
455    }
456
457    pub fn merge_instructions(&self, other: &Instructions) -> Instructions {
458        let ins = [self, other];
459        // Maybe extend_from_slice, but I have not make it work
460        // https://stackoverflow.com/a/69578632/1713757
461        return Instructions {
462            data: ins
463                .iter()
464                .fold(vec![], |sum, &i| [sum.as_slice(), i.data.as_slice()].concat()),
465        };
466    }
467}