mijit/beetle/
mod.rs

1//! A partial implementation of the [Beetle] virtual machine in Mijit.
2//! This serves as an illustrative example as an integration test.
3//! 
4//! [Beetle]: https://github.com/rrthomas/beetle
5
6use memoffset::{offset_of};
7
8use super::code::{UnaryOp, BinaryOp, Width, Register, REGISTERS, GLOBAL, EBB, Marshal};
9use UnaryOp::*;
10use BinaryOp::*;
11use Width::*;
12use super::target::{Word, Target};
13use super::jit::{EntryId, Jit};
14use super::code::builder::{build, build_block, Builder};
15
16mod registers;
17pub use registers::{Registers, M0Registers};
18
19/// The number of bytes in a cell.
20pub const CELL: i32 = 4;
21
22/// The number of bits in a word.
23pub const CELL_BITS: i32 = CELL * 8;
24
25//-----------------------------------------------------------------------------
26
27const R1: Register = REGISTERS[1];
28const R2: Register = REGISTERS[2];
29const R3: Register = REGISTERS[3];
30const BEP: Register = REGISTERS[4];
31const BI: Register = REGISTERS[5];
32const BA: Register = REGISTERS[6];
33const BSP: Register = REGISTERS[7];
34const BRP: Register = REGISTERS[8];
35const M0: Register = REGISTERS[9];
36const REGS: Register = REGISTERS[10];
37
38/// Returns the address of `Registers.$field`.
39macro_rules! register {
40    ($field: ident) => {
41        (REGS, offset_of!(Registers, $field) as i32 + 8, Four)
42    }
43}
44
45/// The return code used to indicate normal exit from the hot code.
46const NOT_IMPLEMENTED: i64 = 0;
47/// Dummy return code which should never actually occur.
48const UNDEFINED: i64 = i64::MAX;
49
50//-----------------------------------------------------------------------------
51
52/// Computes into `BI` the native address corresponding to `addr`.
53fn native_address(b: &mut Builder<EntryId>, addr: Register) {
54    b.binary64(Add, BI, M0, addr);
55}
56
57/// Loads `dest` from `addr`. `BI` is corrupted.
58fn load(b: &mut Builder<EntryId>, dest: Register, addr: Register) {
59    native_address(b, addr);
60    b.load(dest, (BI, 0, Four));
61    b.send(M0, BI);
62}
63
64/// Stores `dest` at `addr`. `BI` is corrupted.
65fn store(b: &mut Builder<EntryId>, src: Register, addr: Register) {
66    native_address(b, addr);
67    b.store(src, (BI, 0, Four));
68    b.send(M0, BI);
69}
70
71/// Pops `dest` from the stack at `sp`. `BI` is corrupted.
72fn pop(b: &mut Builder<EntryId>, dest: Register, sp: Register) {
73    load(b, dest, sp);
74    b.const_binary32(Add, sp, sp, CELL);
75}
76
77/// Pushes `src` to the stack at `sp`. `BI` is corrupted.
78fn push(b: &mut Builder<EntryId>, src: Register, sp: Register) {
79    b.const_binary32(Sub, sp, sp, CELL);
80    store(b, src, sp);
81}
82
83/// The performance-critical part of the virtual machine.
84#[derive(Debug)]
85pub struct Beetle<T: Target> {
86    pub jit: Jit<T>,
87    pub root: EntryId,
88}
89
90impl<T: Target> Beetle<T> {
91    #[allow(clippy::too_many_lines)]
92    pub fn new(target: T) -> Self {
93        let mut jit = Jit::new(target);
94        let marshal = Marshal {
95            prologue: build_block(|b| {
96                b.move_(REGS, GLOBAL);
97                b.load(BEP, register!(ep));
98                b.load(BI, register!(i));
99                b.load(BA, register!(a));
100                b.load(BSP, register!(sp));
101                b.load(BRP, register!(rp));
102                b.load(M0, (REGS, 0, Eight));
103            }),
104            epilogue: build_block(|b| {
105                b.store(BEP, register!(ep));
106                b.store(BI, register!(i));
107                b.store(BA, register!(a));
108                b.store(BSP, register!(sp));
109                b.store(BRP, register!(rp));
110                // No need to save `M0`, but we must use it. Dummy op.
111                b.send(REGS, M0);
112                b.move_(GLOBAL, REGS);
113            }),
114        };
115        let root = jit.new_entry(&marshal, UNDEFINED);
116
117        // Immediate branch.
118        let branchi = jit.new_entry(&marshal, UNDEFINED);
119        jit.define(branchi, &build(|mut b| {
120            b.const_binary32(Mul, R1, BA, CELL);
121            b.binary32(Add, BEP, BEP, R1);
122            pop(&mut b, BA, BEP);
123            b.jump(root)
124        }));
125        
126        // Not implemented.
127        let not_implemented2 = jit.new_entry(&marshal, NOT_IMPLEMENTED);
128        let not_implemented = jit.new_entry(&marshal, UNDEFINED);
129        jit.define(not_implemented, &build(|mut b| {
130            b.const_binary32(Lsl, BA, BA, 8);
131            b.binary32(Or, BA, BA, BI);
132            b.jump(not_implemented2)
133        }));
134
135        // Op-code dispatch routines.
136        let mut actions: Box<[EBB<EntryId>]> = (0..0x63).map(|_| {
137            build(|b| b.jump(not_implemented))
138        }).collect();
139
140        // NEXT
141        actions[0x00] = build(|mut b| {
142            pop(&mut b, BA, BEP);
143            b.jump(root)
144        });
145
146        // DUP
147        actions[0x01] = build(|mut b| {
148            load(&mut b, R2, BSP);
149            push(&mut b, R2, BSP);
150            b.jump(root)
151        });
152
153        // DROP
154        actions[0x02] = build(|mut b| {
155            b.const_binary32(Add, BSP, BSP, CELL);
156            b.jump(root)
157        });
158
159        // SWAP
160        actions[0x03] = build(|mut b| {
161            pop(&mut b, R2, BSP);
162            load(&mut b, R3, BSP);
163            store(&mut b, R2, BSP);
164            push(&mut b, R3, BSP);
165            b.jump(root)
166        });
167
168        // OVER
169        actions[0x04] = build(|mut b| {
170            b.const_binary32(Add, R1, BSP, CELL);
171            load(&mut b, R2, R1);
172            push(&mut b, R2, BSP);
173            b.jump(root)
174        });
175
176        // ROT
177        actions[0x05] = build(|mut b| {
178            load(&mut b, R2, BSP);
179            b.const_binary32(Add, R1, BSP, CELL);
180            load(&mut b, R3, R1);
181            store(&mut b, R2, R1);
182            b.const_binary32(Add, R1, BSP, 2 * CELL);
183            load(&mut b, R2, R1);
184            store(&mut b, R3, R1);
185            store(&mut b, R2, BSP);
186            b.jump(root)
187        });
188
189        // -ROT
190        actions[0x06] = build(|mut b| {
191            load(&mut b, R2, BSP);
192            b.const_binary32(Add, R1, BSP, 2 * CELL);
193            load(&mut b, R3, R1);
194            store(&mut b, R2, R1);
195            b.const_binary32(Add, R1, BSP, CELL);
196            load(&mut b, R2, R1);
197            store(&mut b, R3, R1);
198            store(&mut b, R2, BSP);
199            b.jump(root)
200        });
201
202        // TUCK
203        actions[0x07] = build(|mut b| {
204            load(&mut b, R2, BSP);
205            b.const_binary32(Add, R1, BSP, CELL);
206            load(&mut b, R3, R1);
207            store(&mut b, R2, R1);
208            store(&mut b, R3, BSP);
209            push(&mut b, R2, BSP);
210            b.jump(root)
211        });
212
213        // NIP
214        actions[0x08] = build(|mut b| {
215            pop(&mut b, R2, BSP);
216            store(&mut b, R2, BSP);
217            b.jump(root)
218        });
219
220        // <
221        actions[0x0F] = build(|mut b| {
222            pop(&mut b, R2, BSP);
223            load(&mut b, R3, BSP);
224            b.binary32(Lt, R2, R3, R2);
225            store(&mut b, R2, BSP);
226            b.jump(root)
227        });
228
229        // >
230        actions[0x10] = build(|mut b| {
231            pop(&mut b, R2, BSP);
232            load(&mut b, R3, BSP);
233            b.binary32(Lt, R2, R2, R3);
234            store(&mut b, R2, BSP);
235            b.jump(root)
236        });
237
238        // =
239        actions[0x11] = build(|mut b| {
240            pop(&mut b, R2, BSP);
241            load(&mut b, R3, BSP);
242            b.binary32(Eq, R2, R3, R2);
243            store(&mut b, R2, BSP);
244            b.jump(root)
245        });
246
247        // <>
248        actions[0x12] = build(|mut b| {
249            pop(&mut b, R2, BSP);
250            load(&mut b, R3, BSP);
251            b.binary32(Eq, R2, R3, R2);
252            b.unary32(Not, R2, R2);
253            store(&mut b, R2, BSP);
254            b.jump(root)
255        });
256
257        // 0<
258        actions[0x13] = build(|mut b| {
259            load(&mut b, R2, BSP);
260            b.const_binary32(Lt, R2, R2, 0);
261            store(&mut b, R2, BSP);
262            b.jump(root)
263        });
264
265        // 0>
266        actions[0x14] = build(|mut b| {
267            load(&mut b, R2, BSP);
268            b.const_(R3, 0);
269            b.binary32(Lt, R2, R3, R2);
270            store(&mut b, R2, BSP);
271            b.jump(root)
272        });
273
274        // 0=
275        actions[0x15] = build(|mut b| {
276            load(&mut b, R2, BSP);
277            b.const_binary32(Eq, R2, R2, 0);
278            store(&mut b, R2, BSP);
279            b.jump(root)
280        });
281
282        // 0<>
283        actions[0x16] = build(|mut b| {
284            load(&mut b, R2, BSP);
285            b.const_binary32(Eq, R2, R2, 0);
286            b.unary32(Not, R2, R2);
287            store(&mut b, R2, BSP);
288            b.jump(root)
289        });
290
291        // U<
292        actions[0x17] = build(|mut b| {
293            pop(&mut b, R2, BSP);
294            load(&mut b, R3, BSP);
295            b.binary32(Ult, R2, R3, R2);
296            store(&mut b, R2, BSP);
297            b.jump(root)
298        });
299
300        // U>
301        actions[0x18] = build(|mut b| {
302            pop(&mut b, R2, BSP);
303            load(&mut b, R3, BSP);
304            b.binary32(Ult, R2, R2, R3);
305            store(&mut b, R2, BSP);
306            b.jump(root)
307        });
308
309        // 0
310        actions[0x19] = build(|mut b| {
311            b.const_(R2, 0);
312            push(&mut b, R2, BSP);
313            b.jump(root)
314        });
315
316        // 1
317        actions[0x1A] = build(|mut b| {
318            b.const_(R2, 1);
319            push(&mut b, R2, BSP);
320            b.jump(root)
321        });
322
323        // -1
324        actions[0x1B] = build(|mut b| {
325            b.const_(R2, -1i32 as u32 as i64);
326            push(&mut b, R2, BSP);
327            b.jump(root)
328        });
329
330        // +
331        actions[0x1E] = build(|mut b| {
332            pop(&mut b, R2, BSP);
333            load(&mut b, R3, BSP);
334            b.binary32(Add, R2, R3, R2);
335            store(&mut b, R2, BSP);
336            b.jump(root)
337        });
338
339        // -
340        actions[0x1F] = build(|mut b| {
341            pop(&mut b, R2, BSP);
342            load(&mut b, R3, BSP);
343            b.binary32(Sub, R2, R3, R2);
344            store(&mut b, R2, BSP);
345            b.jump(root)
346        });
347
348        // >-<
349        actions[0x20] = build(|mut b| {
350            pop(&mut b, R2, BSP);
351            load(&mut b, R3, BSP);
352            b.binary32(Sub, R2, R2, R3);
353            store(&mut b, R2, BSP);
354            b.jump(root)
355        });
356
357        // 1+
358        actions[0x21] = build(|mut b| {
359            load(&mut b, R2, BSP);
360            b.const_binary32(Add, R2, R2, 1);
361            store(&mut b, R2, BSP);
362            b.jump(root)
363        });
364
365        // 1-
366        actions[0x22] = build(|mut b| {
367            load(&mut b, R2, BSP);
368            b.const_binary32(Sub, R2, R2, 1);
369            store(&mut b, R2, BSP);
370            b.jump(root)
371        });
372
373        // *
374        actions[0x25] = build(|mut b| {
375            pop(&mut b, R2, BSP);
376            load(&mut b, R3, BSP);
377            b.binary32(Mul, R2, R3, R2);
378            store(&mut b, R2, BSP);
379            b.jump(root)
380        });
381
382        // ABS
383        actions[0x2D] = build(|mut b| {
384            load(&mut b, R2, BSP);
385            b.unary32(Abs, R2, R2);
386            store(&mut b, R2, BSP);
387            b.jump(root)
388        });
389
390        // NEGATE
391        actions[0x2E] = build(|mut b| {
392            load(&mut b, R2, BSP);
393            b.unary32(Negate, R2, R2);
394            store(&mut b, R2, BSP);
395            b.jump(root)
396        });
397
398        // MAX
399        actions[0x2F] = build(|mut b| {
400            pop(&mut b, R2, BSP);
401            load(&mut b, R3, BSP);
402            b.binary32(Max, R2, R3, R2);
403            store(&mut b, R2, BSP);
404            b.jump(root)
405        });
406
407        actions[0x30] = // MIN
408        build(|mut b| {
409            pop(&mut b, R2, BSP);
410            load(&mut b, R3, BSP);
411            b.binary32(Min, R2, R3, R2);
412            store(&mut b, R2, BSP);
413            b.jump(root)
414        });
415
416        actions[0x31] = // INVERT
417        build(|mut b| {
418            load(&mut b, R2, BSP);
419            b.unary32(Not, R2, R2);
420            store(&mut b, R2, BSP);
421            b.jump(root)
422        });
423
424        // @
425        actions[0x39] = build(|mut b| {
426            load(&mut b, R2, BSP);
427            load(&mut b, R2, R2);
428            store(&mut b, R2, BSP);
429            b.jump(root)
430        });
431
432        // !
433        actions[0x3A] = build(|mut b| {
434            pop(&mut b, R2, BSP);
435            pop(&mut b, R3, BSP);
436            store(&mut b, R3, R2);
437            b.jump(root)
438        });
439
440        // +!
441        actions[0x3D] = build(|mut b| {
442            pop(&mut b, R2, BSP);
443            pop(&mut b, R3, BSP);
444            load(&mut b, R1, R2);
445            b.binary32(Add, R3, R1, R3);
446            store(&mut b, R3, R2);
447            b.jump(root)
448        });
449
450        // BRANCHI
451        actions[0x43] = build(|b| { b.jump(branchi) });
452
453        // ?BRANCHI
454        actions[0x45] = build(|mut b| {
455            pop(&mut b, BI, BSP);
456            b.if_(BI,
457                build(|mut b| {
458                    pop(&mut b, BA, BEP);
459                    b.jump(root)
460                }),
461                build(|b| { b.jump(branchi) }),
462            )
463        });
464
465        // CALLI
466        actions[0x49] = build(|mut b| {
467            push(&mut b, BEP, BRP);
468            b.jump(branchi)
469        });
470
471        // EXIT
472        actions[0x4A] = build(|mut b| {
473            pop(&mut b, BEP, BRP);
474            pop(&mut b, BA, BEP);
475            b.jump(root)
476        });
477
478        // (LITERAL)I
479        actions[0x53] = build(|mut b| {
480            push(&mut b, BA, BSP);
481            pop(&mut b, BA, BEP);
482            b.jump(root)
483        });
484
485        // Main dispatch loop.
486        jit.define(root, &build(|mut b| {
487            b.const_binary32(And, BI, BA, 0xFF);
488            b.const_binary32(Asr, BA, BA, 8);
489            b.index(BI, actions, build(|b| b.jump(not_implemented)))
490        }));
491
492        Self {jit, root}
493    }
494
495    /// # Safety
496    ///
497    /// There is no memory bounds checking in this implementation of Beetle.
498    pub unsafe fn run(&mut self, registers: &mut M0Registers) {
499        let result = self.jit.run(self.root, registers);
500        assert_eq!(result, Word {s: NOT_IMPLEMENTED});
501    }
502}
503
504//-----------------------------------------------------------------------------
505
506#[cfg(test)]
507mod tests;