tiny_vm/
tiny_vm.rs

1// SPDX-License-Identifier: MIT
2//
3// Copyright (c) 2023, Johannes Stoelp <dev@memzero.de>
4
5//! TinyVm example.
6//!
7//! This example introduces a simple 16 bit virtual machine the [`TinyVm`]. The VM consists of
8//! three registers defined in [`TinyReg`], a separate _data_ and _instruction_ memory and a small
9//! set of instructions [`TinyInsn`], sufficient to implement a guest program to compute the
10//! Fibonacci sequence.
11//!
12//! The `TinyVm` implements a simple _just-in-time (JIT)_ compiler to demonstrate the
13//! [`juicebox_asm`] crate. Additionally, it implements a reference _interpreter_.
14//!
15//! ```
16//! fn main() {
17//!   let mut prog = Vec::new();
18//!   prog.push(TinyInsn::LoadImm(TinyReg::A, 100));
19//!   prog.push(TinyInsn::Add(TinyReg::B, TinyReg::A));
20//!   prog.push(TinyInsn::Addi(TinyReg::C, 100));
21//!   prog.push(TinyInsn::Halt);
22//!
23//!   let mut vm = TinyVm::new(prog);
24//!   vm.interp();
25//!
26//!   assert_eq!(100, vm.read_reg(TinyReg::A));
27//!   assert_eq!(100, vm.read_reg(TinyReg::B));
28//!   assert_eq!(100, vm.read_reg(TinyReg::C));
29//!   assert_eq!(4, vm.icnt);
30//!   assert_eq!(4, vm.pc);
31//!
32//!   vm.pc = 0;
33//!   vm.jit();
34//!
35//!   assert_eq!(100, vm.read_reg(TinyReg::A));
36//!   assert_eq!(200, vm.read_reg(TinyReg::B));
37//!   assert_eq!(200, vm.read_reg(TinyReg::C));
38//!   assert_eq!(8, vm.icnt);
39//!   assert_eq!(4, vm.pc);
40//! }
41//! ```
42
43use juicebox_asm::insn::*;
44use juicebox_asm::Runtime;
45use juicebox_asm::{Asm, Imm16, Imm64, Mem16, Reg16, Reg64};
46
47/// A guest physical address.
48pub struct PhysAddr(pub u16);
49
50impl Into<usize> for PhysAddr {
51    fn into(self) -> usize {
52        self.0 as usize
53    }
54}
55
56/// The registers for the [`TinyVm`].
57#[derive(Debug, PartialEq, Clone, Copy)]
58pub enum TinyReg {
59    A,
60    B,
61    C,
62}
63
64impl TinyReg {
65    #[inline]
66    fn idx(&self) -> usize {
67        *self as usize
68    }
69}
70
71/// The instructions for the [`TinyVm`].
72#[derive(Debug, PartialEq, Clone, Copy)]
73pub enum TinyInsn {
74    /// Halt the VM.
75    Halt,
76    /// Load the immediate value into the register `reg = imm`.
77    LoadImm(TinyReg, u16),
78    /// Load a value from the memory (absolute addressing) into the register `reg = mem[imm]`.
79    Load(TinyReg, u16),
80    /// Store a value from the register into the memory (absolute addressing) `mem[imm] = reg`.
81    Store(TinyReg, u16),
82    /// Add the register to the register `reg1 += reg2`.
83    Add(TinyReg, TinyReg),
84    /// Add the immediate to the register `reg += imm`.
85    Addi(TinyReg, i16),
86    /// Jump unconditional (absolute addressing) `pc = disp`.
87    Branch(usize),
88    /// Jump if the register is zero (absolute addressing) `pc = (reg == 0) ? disp : pc++`.
89    BranchZero(TinyReg, usize),
90}
91
92/// Value returned from a [`JitFn`].
93#[repr(C)]
94struct JitRet(u64, u64);
95
96/// Function signature defining the simple JIT ABI used in this example.
97/// A `JitFn` represents the entry point to a jit compiled _basic block_ of the guest software.
98///
99/// ```text
100/// JIT entry:
101///     arg0: pointer to guest registers
102///     arg1: pointer to guest data memory
103///
104/// JIT exit:
105///      JitRet(0, N): Halt instruction, executed N instructions.
106///      JitRet(N, R): N!=0
107///                    End of basic block, executed N instructions,
108///                    must re-enter at `pc = R`.
109/// ```
110type JitFn = extern "C" fn(*mut u16, *mut u8) -> JitRet;
111
112/// The `TinyVm` virtual machine state.
113pub struct TinyVm {
114    /// Data memory, covering full 16 bit guest address space.
115    ///
116    /// For simplicity add additional trailing 1 byte to support an unaligned access to 0xffff
117    /// without any special handling.
118    dmem: [u8; 0x1_0000 + 1],
119    /// Instruction memory.
120    imem: Vec<TinyInsn>,
121    /// VM registers.
122    regs: [u16; 3],
123    /// VM program counter.
124    pc: usize,
125    /// VM executed instruction counter (perf counter).
126    icnt: usize,
127
128    // -- JIT state.
129    /// Mapping of guest PCs to jitted host code (`JitFn`). This mapping is filled when guest
130    /// _basic blocks_ are jitted.
131    jit_cache: Vec<Option<JitFn>>,
132    /// JIT runtime maintaining the host pages containing the jitted guest code.
133    rt: Runtime,
134}
135
136impl TinyVm {
137    /// Create a new [`TinyVm`] and initialize the instruction memory from `code`.
138    pub fn new(code: Vec<TinyInsn>) -> Self {
139        let mut jit_cache = Vec::with_capacity(code.len());
140        jit_cache.resize(code.len(), None);
141
142        TinyVm {
143            dmem: [0; 0x1_0000 + 1],
144            imem: code,
145            regs: [0; 3],
146            pc: 0,
147            icnt: 0,
148            // -- JIT state.
149            jit_cache,
150            rt: Runtime::new(),
151            // Confifigure the runtime to generates perf meta data.
152            //rt: Runtime::with_profile(),
153        }
154    }
155
156    /// Read guest register.
157    #[inline]
158    pub fn read_reg(&self, reg: TinyReg) -> u16 {
159        self.regs[reg.idx()]
160    }
161
162    /// Write guest register.
163    #[inline]
164    pub fn write_reg(&mut self, reg: TinyReg, val: u16) {
165        self.regs[reg.idx()] = val;
166    }
167
168    /// Read guest data memory.
169    #[inline]
170    pub fn read_mem(&self, paddr: PhysAddr) -> u16 {
171        // dmem covers whole 16 bit address space + 1 byte for unaligned access at 0xffff.
172        let bytes = self.dmem[paddr.into()..][..2].try_into().unwrap();
173        u16::from_le_bytes(bytes)
174    }
175
176    /// Write guest data memory.
177    #[inline]
178    pub fn write_mem(&mut self, paddr: PhysAddr, val: u16) {
179        let bytes = val.to_le_bytes();
180        self.dmem[paddr.into()..][..2].copy_from_slice(&bytes);
181    }
182
183    /// Dump the VM state to stdout.
184    pub fn dump(&self) {
185        println!("-- TinyVm state --");
186        println!("  ICNT: {}", self.icnt);
187        println!("  PC  : {:02x}", self.pc - 1);
188        println!(
189            "  A:{:04x} B:{:04x} C:{:04x}",
190            self.read_reg(TinyReg::A),
191            self.read_reg(TinyReg::B),
192            self.read_reg(TinyReg::C),
193        );
194    }
195
196    /// Run in interpreter mode until the next [`TinyInsn::Halt`] instruction is hit.
197    pub fn interp(&mut self) {
198        'outer: loop {
199            let insn = self.imem[self.pc];
200            //println!("[0x{:02x}] {:?}", self.pc, insn);
201
202            self.pc = self.pc.wrapping_add(1);
203            self.icnt += 1;
204
205            match insn {
206                TinyInsn::Halt => {
207                    break 'outer;
208                }
209                TinyInsn::LoadImm(a, imm) => {
210                    self.write_reg(a, imm);
211                }
212                TinyInsn::Load(a, addr) => {
213                    let val = self.read_mem(PhysAddr(addr));
214                    self.write_reg(a, val);
215                }
216                TinyInsn::Store(a, addr) => {
217                    let val = self.read_reg(a);
218                    self.write_mem(PhysAddr(addr), val);
219                }
220                TinyInsn::Add(a, b) => {
221                    let res = self.read_reg(a).wrapping_add(self.read_reg(b));
222                    self.write_reg(a, res);
223                }
224                TinyInsn::Addi(a, imm) => {
225                    let res = self.read_reg(a).wrapping_add(imm as u16);
226                    self.write_reg(a, res);
227                }
228                TinyInsn::Branch(disp) => {
229                    self.pc = disp;
230                }
231                TinyInsn::BranchZero(a, disp) => {
232                    if self.read_reg(a) == 0 {
233                        self.pc = disp;
234                    }
235                }
236            }
237        }
238    }
239
240    /// Run in JIT mode until the next [`TinyInsn::Halt`] instruction is hit. Translate guest
241    /// _basic blocks_ on demand.
242    pub fn jit(&mut self) {
243        'outer: loop {
244            let bb_fn = if let Some(bb_fn) = self.jit_cache[self.pc] {
245                bb_fn
246            } else {
247                let bb_fn = self.translate_next_bb();
248                self.jit_cache[self.pc] = Some(bb_fn);
249                //println!("[0x{:02x}] translated bb at {:p}", self.pc, bb_fn);
250                bb_fn
251            };
252
253            match bb_fn(self.regs.as_mut_ptr(), self.dmem.as_mut_ptr()) {
254                // HALT instruction hit.
255                JitRet(0, insn) => {
256                    self.pc += insn as usize;
257                    self.icnt += insn as usize;
258                    break 'outer;
259                }
260                // End of basic block, re-enter.
261                JitRet(insn, reenter_pc) => {
262                    self.pc = reenter_pc as usize;
263                    self.icnt += insn as usize;
264                }
265            }
266        }
267    }
268
269    #[cfg(all(any(target_arch = "x86_64", target_os = "linux")))]
270    /// Translate the bb at the current pc and return a JitFn pointer to it.
271    fn translate_next_bb(&mut self) -> JitFn {
272        let mut bb = Asm::new();
273        let mut pc = self.pc;
274
275        'outer: loop {
276            let insn = self.imem[pc];
277
278            pc = pc.wrapping_add(1);
279
280            // JIT abi: JitFn -> JitRet
281            //
282            // According to SystemV abi:
283            //   enter
284            //     rdi => regs
285            //     rsi => dmem
286            //   exit
287            //     rax => JitRet.0
288            //     rdx => JitRet.1
289
290            // Generate memory operand into regs for guest register.
291            let reg_op = |r: TinyReg| {
292                Mem16::indirect_disp(Reg64::rdi, (r.idx() * 2).try_into().expect("only 3 regs"))
293            };
294
295            // Generate memory operand into dmem for guest phys address.
296            let mem_op = |paddr: u16| Mem16::indirect_disp(Reg64::rsi, paddr.into());
297
298            // Compute instructions in translated basic block.
299            let bb_icnt = || -> u64 { (pc - self.pc).try_into().unwrap() };
300
301            let reenter_pc = |pc: usize| -> u64 { pc.try_into().unwrap() };
302
303            match insn {
304                TinyInsn::Halt => {
305                    bb.mov(Reg64::rax, Imm64::from(0));
306                    bb.mov(Reg64::rdx, Imm64::from(bb_icnt()));
307                    bb.ret();
308                    break 'outer;
309                }
310                TinyInsn::LoadImm(a, imm) => {
311                    bb.mov(reg_op(a), Imm16::from(imm));
312                }
313                TinyInsn::Load(a, addr) => {
314                    bb.mov(Reg16::ax, mem_op(addr));
315                    bb.mov(reg_op(a), Reg16::ax);
316                }
317                TinyInsn::Store(a, addr) => {
318                    bb.mov(Reg16::ax, reg_op(a));
319                    bb.mov(mem_op(addr), Reg16::ax);
320                }
321                TinyInsn::Add(a, b) => {
322                    bb.mov(Reg16::ax, reg_op(b));
323                    bb.add(reg_op(a), Reg16::ax);
324                }
325                TinyInsn::Addi(a, imm) => {
326                    bb.add(reg_op(a), Imm16::from(imm));
327                }
328                TinyInsn::Branch(disp) => {
329                    bb.mov(Reg64::rax, Imm64::from(bb_icnt()));
330                    bb.mov(Reg64::rdx, Imm64::from(reenter_pc(disp)));
331                    bb.ret();
332                    break 'outer;
333                }
334                TinyInsn::BranchZero(a, disp) => {
335                    bb.cmp(reg_op(a), Imm16::from(0u16));
336                    bb.mov(Reg64::rax, Imm64::from(bb_icnt()));
337                    // Default fall-through PC (branch not taken).
338                    bb.mov(Reg64::rdx, Imm64::from(reenter_pc(pc)));
339
340                    // Conditionally update PC if condition is ZERO (branch taken).
341                    bb.mov(Reg64::r11, Imm64::from(reenter_pc(disp)));
342                    bb.cmovz(Reg64::rdx, Reg64::r11);
343
344                    bb.ret();
345                    break 'outer;
346                }
347            }
348        }
349
350        unsafe { self.rt.add_code::<JitFn>(bb.into_code()) }
351    }
352}
353
354/// A minial fixup utility to implement jump labels when constructing guest programs.
355pub struct Fixup {
356    pc: usize,
357}
358
359impl Fixup {
360    /// Create a new `Fixup` at the current pc.
361    pub fn new(pc: usize) -> Self {
362        Fixup { pc }
363    }
364
365    /// Bind the `Fixup` to the current location of `prog` and resolve the `Fixup`.
366    pub fn bind(self, prog: &mut Vec<TinyInsn>) {
367        let plen = prog.len();
368        let insn = prog.get_mut(self.pc).expect(&format!(
369            "Trying to apply Fixup, but Fixup is out of range pc={} prog.len={}",
370            self.pc, plen
371        ));
372
373        match insn {
374            TinyInsn::Branch(disp) | TinyInsn::BranchZero(_, disp) => {
375                *disp = plen;
376            }
377            _ => {
378                unimplemented!("Trying to fixup non-branch instruction '{:?}'", *insn);
379            }
380        }
381    }
382}
383
384/// Generate a guest program to compute the fiibonacci sequence for `n`.
385pub fn make_tinyvm_fib(start_n: u16) -> Vec<TinyInsn> {
386    // Reference implementation:
387    //
388    // int fib(int n)
389    //   int tmp = 0;
390    //   int prv = 1;
391    //   int sum = 0;
392    // loop:
393    //   if (n == 0) goto end;
394    //   tmp = sum;
395    //   sum += prv;
396    //   prv = tmp;
397    //   --n;
398    //   goto loop;
399    // end:
400    //   return sum;
401
402    // Variables live in memory, bin to fixed addresses.
403    let tmp = 0u16;
404    let prv = 2u16;
405    let sum = 4u16;
406    // Loop counter mapped to register.
407    let n = TinyReg::C;
408
409    let mut prog = Vec::with_capacity(32);
410
411    // n = start_n
412    prog.push(TinyInsn::LoadImm(n, start_n));
413
414    // tmp = sum = 0
415    prog.push(TinyInsn::LoadImm(TinyReg::A, 0));
416    prog.push(TinyInsn::Store(TinyReg::A, tmp));
417    prog.push(TinyInsn::Store(TinyReg::A, sum));
418
419    // prv = 1
420    prog.push(TinyInsn::LoadImm(TinyReg::A, 1));
421    prog.push(TinyInsn::Store(TinyReg::A, prv));
422
423    // Create loop_start label.
424    let loop_start = prog.len();
425
426    // Create fixup to capture PC that need to be patched later.
427    let end_fixup = Fixup::new(prog.len());
428
429    // if (n == 0) goto end
430    prog.push(TinyInsn::BranchZero(n, 0xdead));
431
432    // tmp = sum
433    prog.push(TinyInsn::Load(TinyReg::A, sum));
434    prog.push(TinyInsn::Store(TinyReg::A, tmp));
435
436    // sum += prv
437    prog.push(TinyInsn::Load(TinyReg::B, prv));
438    prog.push(TinyInsn::Add(TinyReg::A, TinyReg::B));
439    prog.push(TinyInsn::Store(TinyReg::A, sum));
440
441    // prv = tmp
442    prog.push(TinyInsn::Load(TinyReg::A, tmp));
443    prog.push(TinyInsn::Store(TinyReg::A, prv));
444
445    // --n
446    prog.push(TinyInsn::Addi(n, -1));
447
448    // goto loop_start
449    prog.push(TinyInsn::Branch(loop_start));
450
451    // Bind end fixup to current PC, to patch branch to jump to here.
452    end_fixup.bind(&mut prog);
453
454    // TinyReg::A = sum
455    prog.push(TinyInsn::Load(TinyReg::A, sum));
456
457    // Halt the VM.
458    prog.push(TinyInsn::Halt);
459
460    prog
461}
462
463/// Generate a test program for the jit.
464pub fn make_tinyvm_jit_test() -> Vec<TinyInsn> {
465    let mut prog = Vec::with_capacity(32);
466
467    prog.push(TinyInsn::Branch(1));
468    prog.push(TinyInsn::LoadImm(TinyReg::A, 0x0010));
469    prog.push(TinyInsn::LoadImm(TinyReg::B, 0x0));
470
471    let start = prog.len();
472    let end = Fixup::new(prog.len());
473    prog.push(TinyInsn::BranchZero(TinyReg::A, 0xdead));
474    prog.push(TinyInsn::LoadImm(TinyReg::C, 0x1));
475    prog.push(TinyInsn::Add(TinyReg::B, TinyReg::C));
476    prog.push(TinyInsn::Addi(TinyReg::A, -1));
477    prog.push(TinyInsn::Branch(start));
478    end.bind(&mut prog);
479    prog.push(TinyInsn::LoadImm(TinyReg::A, 0xabcd));
480    prog.push(TinyInsn::Store(TinyReg::A, 0xffff));
481    prog.push(TinyInsn::Load(TinyReg::C, 0xffff));
482    prog.push(TinyInsn::Halt);
483    prog.push(TinyInsn::Halt);
484    prog.push(TinyInsn::Halt);
485    prog.push(TinyInsn::Halt);
486
487    prog
488}
489
490/// Generate a simple count down loop to crunch some instructions.
491pub fn make_tinyvm_jit_perf() -> Vec<TinyInsn> {
492    let mut prog = Vec::with_capacity(32);
493
494    prog.push(TinyInsn::Halt);
495    prog.push(TinyInsn::LoadImm(TinyReg::A, 0xffff));
496    prog.push(TinyInsn::LoadImm(TinyReg::B, 1));
497    prog.push(TinyInsn::LoadImm(TinyReg::C, 2));
498    prog.push(TinyInsn::Addi(TinyReg::A, -1));
499    prog.push(TinyInsn::BranchZero(TinyReg::A, 0));
500    prog.push(TinyInsn::Branch(2));
501    prog
502}
503
504fn main() {
505    let use_jit = match std::env::args().nth(1) {
506        Some(a) if a == "-h" || a == "--help" => {
507            println!("Usage: tiny_vm [mode]");
508            println!("");
509            println!("Options:");
510            println!("    mode    if mode is 'jit' then run in jit mode, else in interpreter mode");
511            std::process::exit(0);
512        }
513        Some(a) if a == "jit" => true,
514        _ => false,
515    };
516
517    let mut vm = TinyVm::new(make_tinyvm_fib(42));
518
519    if use_jit {
520        println!("Run in jit mode..");
521        vm.jit();
522    } else {
523        println!("Run in interpreter mode..");
524        vm.interp();
525    }
526    vm.dump();
527}
528
529#[cfg(test)]
530mod test {
531    use super::*;
532
533    fn fib_rs(n: u64) -> u64 {
534        if n < 2 {
535            n
536        } else {
537            let mut fib_n_m1 = 0;
538            let mut fib_n = 1;
539            for _ in 1..n {
540                let tmp = fib_n + fib_n_m1;
541                fib_n_m1 = fib_n;
542                fib_n = tmp;
543            }
544            fib_n
545        }
546    }
547
548    #[test]
549    fn test_fib_interp() {
550        for n in 0..92 {
551            let mut vm = TinyVm::new(make_tinyvm_fib(n));
552            vm.interp();
553
554            assert_eq!((fib_rs(n as u64) & 0xffff) as u16, vm.read_reg(TinyReg::A));
555        }
556    }
557
558    #[test]
559    fn test_fib_jit() {
560        for n in 0..92 {
561            let mut vm = TinyVm::new(make_tinyvm_fib(n));
562            vm.jit();
563
564            assert_eq!((fib_rs(n as u64) & 0xffff) as u16, vm.read_reg(TinyReg::A));
565        }
566    }
567
568    #[test]
569    fn test_fib_icnt() {
570        let mut vm1 = TinyVm::new(make_tinyvm_fib(91));
571        vm1.interp();
572        let mut vm2 = TinyVm::new(make_tinyvm_fib(91));
573        vm2.jit();
574
575        assert_eq!(vm1.icnt, vm2.icnt);
576        assert_eq!(vm1.pc, vm2.pc);
577    }
578
579    #[test]
580    fn test_jit_load_imm() {
581        let mut prog = Vec::new();
582        prog.push(TinyInsn::LoadImm(TinyReg::A, 0x1111));
583        prog.push(TinyInsn::LoadImm(TinyReg::B, 0x2222));
584        prog.push(TinyInsn::LoadImm(TinyReg::C, 0x3333));
585        prog.push(TinyInsn::Halt);
586
587        let mut vm = TinyVm::new(prog);
588        vm.jit();
589
590        assert_eq!(0x1111, vm.read_reg(TinyReg::A));
591        assert_eq!(0x2222, vm.read_reg(TinyReg::B));
592        assert_eq!(0x3333, vm.read_reg(TinyReg::C));
593
594        assert_eq!(4, vm.icnt);
595        assert_eq!(4, vm.pc);
596    }
597
598    #[test]
599    fn test_jit_add() {
600        let mut prog = Vec::new();
601        prog.push(TinyInsn::LoadImm(TinyReg::A, 0));
602        prog.push(TinyInsn::Addi(TinyReg::A, 123));
603
604        prog.push(TinyInsn::LoadImm(TinyReg::B, 100));
605        prog.push(TinyInsn::LoadImm(TinyReg::C, 200));
606        prog.push(TinyInsn::Add(TinyReg::B, TinyReg::C));
607        prog.push(TinyInsn::Halt);
608
609        let mut vm = TinyVm::new(prog);
610        vm.jit();
611
612        assert_eq!(123, vm.read_reg(TinyReg::A));
613        assert_eq!(300, vm.read_reg(TinyReg::B));
614        assert_eq!(200, vm.read_reg(TinyReg::C));
615
616        assert_eq!(6, vm.icnt);
617        assert_eq!(6, vm.pc);
618    }
619
620    #[test]
621    fn test_jit_load_store() {
622        let mut prog = Vec::new();
623        prog.push(TinyInsn::Load(TinyReg::A, 0xffff));
624
625        prog.push(TinyInsn::LoadImm(TinyReg::B, 0xf00d));
626        prog.push(TinyInsn::Store(TinyReg::B, 0x8000));
627        prog.push(TinyInsn::Halt);
628
629        let mut vm = TinyVm::new(prog);
630        vm.write_mem(PhysAddr(0xffff), 0xaabb);
631        vm.jit();
632
633        assert_eq!(0xaabb, vm.read_reg(TinyReg::A));
634        assert_eq!(0xf00d, vm.read_mem(PhysAddr(0x8000)));
635
636        assert_eq!(4, vm.icnt);
637        assert_eq!(4, vm.pc);
638    }
639
640    #[test]
641    fn test_jit_branch() {
642        let mut prog = Vec::new();
643        prog.push(TinyInsn::Branch(2));
644        prog.push(TinyInsn::Halt);
645        prog.push(TinyInsn::Branch(6));
646        prog.push(TinyInsn::LoadImm(TinyReg::A, 1));
647        prog.push(TinyInsn::LoadImm(TinyReg::B, 2));
648        prog.push(TinyInsn::LoadImm(TinyReg::C, 3));
649        prog.push(TinyInsn::Branch(1));
650
651        let mut vm = TinyVm::new(prog);
652        vm.jit();
653
654        assert_eq!(0, vm.read_reg(TinyReg::A));
655        assert_eq!(0, vm.read_reg(TinyReg::B));
656        assert_eq!(0, vm.read_reg(TinyReg::C));
657
658        assert_eq!(4, vm.icnt);
659        assert_eq!(2, vm.pc);
660    }
661
662    #[test]
663    fn test_jit_branch_zero() {
664        let mut prog = Vec::new();
665        prog.push(TinyInsn::LoadImm(TinyReg::A, 1));
666        prog.push(TinyInsn::BranchZero(TinyReg::A, 5));
667        prog.push(TinyInsn::LoadImm(TinyReg::A, 0));
668        prog.push(TinyInsn::BranchZero(TinyReg::A, 5));
669        prog.push(TinyInsn::LoadImm(TinyReg::B, 22));
670        prog.push(TinyInsn::Halt);
671
672        let mut vm = TinyVm::new(prog);
673        vm.jit();
674
675        assert_eq!(0, vm.read_reg(TinyReg::A));
676        assert_eq!(0, vm.read_reg(TinyReg::B));
677        assert_eq!(0, vm.read_reg(TinyReg::C));
678
679        assert_eq!(5, vm.icnt);
680        assert_eq!(6, vm.pc);
681    }
682
683    #[test]
684    fn test_mixed() {
685        let mut prog = Vec::new();
686        prog.push(TinyInsn::LoadImm(TinyReg::A, 100));
687        prog.push(TinyInsn::Add(TinyReg::B, TinyReg::A));
688        prog.push(TinyInsn::Addi(TinyReg::C, 100));
689        prog.push(TinyInsn::Halt);
690
691        let mut vm = TinyVm::new(prog);
692        vm.interp();
693
694        assert_eq!(100, vm.read_reg(TinyReg::A));
695        assert_eq!(100, vm.read_reg(TinyReg::B));
696        assert_eq!(100, vm.read_reg(TinyReg::C));
697        assert_eq!(4, vm.icnt);
698        assert_eq!(4, vm.pc);
699
700        vm.pc = 0;
701        vm.jit();
702
703        assert_eq!(100, vm.read_reg(TinyReg::A));
704        assert_eq!(200, vm.read_reg(TinyReg::B));
705        assert_eq!(200, vm.read_reg(TinyReg::C));
706        assert_eq!(8, vm.icnt);
707        assert_eq!(4, vm.pc);
708    }
709}