From 2294180c3778d0fcfa877818e98c420fcd54bb8a Mon Sep 17 00:00:00 2001 From: johannst Date: Tue, 5 Dec 2023 22:08:06 +0000 Subject: deploy: 7e98f5def5942969f97f5f015e7fb8417793d132 --- src/add/add.rs.html | 77 ++ src/fib/fib.rs.html | 157 ++++ src/juicebox_asm/imm.rs.html | 2 +- src/juicebox_asm/insn.rs.html | 14 +- src/juicebox_asm/insn/add.rs.html | 26 +- src/juicebox_asm/insn/call.rs.html | 2 +- src/juicebox_asm/insn/cmp.rs.html | 15 + src/juicebox_asm/insn/dec.rs.html | 2 +- src/juicebox_asm/insn/jmp.rs.html | 2 +- src/juicebox_asm/insn/jnz.rs.html | 2 +- src/juicebox_asm/insn/jz.rs.html | 2 +- src/juicebox_asm/insn/mov.rs.html | 18 +- src/juicebox_asm/insn/nop.rs.html | 2 +- src/juicebox_asm/insn/ret.rs.html | 2 +- src/juicebox_asm/insn/test.rs.html | 14 +- src/juicebox_asm/label.rs.html | 2 +- src/juicebox_asm/lib.rs.html | 124 +++- src/juicebox_asm/prelude.rs.html | 4 +- src/juicebox_asm/reg.rs.html | 2 +- src/juicebox_asm/rt.rs.html | 98 ++- src/tiny_vm/tiny_vm.rs.html | 1395 ++++++++++++++++++++++++++++++++++++ 21 files changed, 1923 insertions(+), 39 deletions(-) create mode 100644 src/add/add.rs.html create mode 100644 src/fib/fib.rs.html create mode 100644 src/juicebox_asm/insn/cmp.rs.html create mode 100644 src/tiny_vm/tiny_vm.rs.html (limited to 'src') diff --git a/src/add/add.rs.html b/src/add/add.rs.html new file mode 100644 index 0000000..112d0f1 --- /dev/null +++ b/src/add/add.rs.html @@ -0,0 +1,77 @@ +add.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+
//! Add example.
+//!
+//! Jit compile a function at runtime (generate native host code) which calls a function defined in
+//! the example based on the SystemV abi to demonstrate the [`juicebox_asm`] crate.
+
+#[cfg(not(any(target_arch = "x86_64", target_os = "linux")))]
+compile_error!("Only supported on x86_64 with SystemV abi");
+
+use juicebox_asm::prelude::*;
+use juicebox_asm::Runtime;
+use Reg64::*;
+
+extern "C" fn add(a: u32, b: u32) -> u32 {
+    a + b
+}
+
+fn main() {
+    let mut asm = Asm::new();
+
+    // SystemV abi:
+    //   rdi -> first argument
+    //   rsi -> second argument
+    //   rax -> return value
+    //
+    asm.mov(rsi, Imm64::from(42));
+    asm.mov(rax, Imm64::from(add as u64));
+    asm.call(rax);
+    asm.ret();
+
+    let code = asm.into_code();
+    std::fs::write("jit.asm", &code).unwrap();
+
+    let mut rt = Runtime::new();
+    let add42 = unsafe { rt.add_code::<extern "C" fn(u32) -> u32>(code) };
+
+    let res = add42(5);
+    assert_eq!(res, 47);
+}
+
\ No newline at end of file diff --git a/src/fib/fib.rs.html b/src/fib/fib.rs.html new file mode 100644 index 0000000..87a1bbf --- /dev/null +++ b/src/fib/fib.rs.html @@ -0,0 +1,157 @@ +fib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+
//! Fibonacci example.
+//!
+//! Jit compile a function at runtime (generate native host code) to compute the fibonacci sequence
+//! to demonstrate the [`juicebox_asm`] crate.
+
+use juicebox_asm::prelude::*;
+use juicebox_asm::Runtime;
+
+const fn fib_rs(n: u64) -> u64 {
+    match n {
+        0 => 0,
+        1 => 1,
+        _ => fib_rs(n - 2) + fib_rs(n - 1),
+    }
+}
+
+fn main() {
+    let mut asm = Asm::new();
+
+    let mut lp = Label::new();
+    let mut end = Label::new();
+
+    // Reference implementation:
+    //
+    // int fib(int n) {
+    //   int tmp = 0;
+    //   int prv = 1;
+    //   int sum = 0;
+    // loop:
+    //   if (n == 0) goto end;
+    //   tmp = sum;
+    //   sum += prv;
+    //   prv = tmp;
+    //   --n;
+    //   goto loop;
+    // end:
+    //   return sum;
+    // }
+
+    // SystemV abi:
+    //   rdi -> first argument
+    //   rax -> return value
+    let n = Reg64::rdi;
+    let sum = Reg64::rax;
+
+    let tmp = Reg64::rcx;
+    let prv = Reg64::rbx;
+
+    asm.mov(tmp, Imm64::from(0));
+    asm.mov(prv, Imm64::from(1));
+    asm.mov(sum, Imm64::from(0));
+
+    asm.bind(&mut lp);
+    asm.test(n, n);
+    asm.jz(&mut end);
+    asm.mov(tmp, sum);
+    asm.add(sum, prv);
+    asm.mov(prv, tmp);
+    asm.dec(n);
+    asm.jmp(&mut lp);
+    asm.bind(&mut end);
+    asm.ret();
+
+    // Write out JIT code for visualization.
+    // Disassemble for example with `ndisasm -b 64 jit.asm`.
+    let code = asm.into_code();
+    std::fs::write("jit.asm", &code).unwrap();
+
+    // Move code into executable page and get function pointer to it.
+    let mut rt = Runtime::new();
+    let fib = unsafe { rt.add_code::<extern "C" fn(u64) -> u64>(code) };
+
+    for n in 0..15 {
+        let fib_jit = fib(n);
+        println!("fib({}) = {}", n, fib_jit);
+        assert_eq!(fib_jit, fib_rs(n));
+    }
+}
+
\ No newline at end of file diff --git a/src/juicebox_asm/imm.rs.html b/src/juicebox_asm/imm.rs.html index acb8add..78f4a8b 100644 --- a/src/juicebox_asm/imm.rs.html +++ b/src/juicebox_asm/imm.rs.html @@ -1,4 +1,4 @@ -imm.rs - source
1
+imm.rs - source
1
 2
 3
 4
diff --git a/src/juicebox_asm/insn.rs.html b/src/juicebox_asm/insn.rs.html
index 24e26cb..550a4ab 100644
--- a/src/juicebox_asm/insn.rs.html
+++ b/src/juicebox_asm/insn.rs.html
@@ -1,4 +1,4 @@
-insn.rs - source
1
+insn.rs - source
1
 2
 3
 4
@@ -53,10 +53,17 @@
 53
 54
 55
+56
+57
+58
+59
+60
+61
 
//! Trait definitions of various instructions.
 
 mod add;
 mod call;
+mod cmp;
 mod dec;
 mod jmp;
 mod jnz;
@@ -76,6 +83,11 @@
     fn call(&mut self, op1: T);
 }
 
+pub trait Cmp<T, U> {
+    /// Emit a compare call instruction.
+    fn cmp(&mut self, op1: T, op2: U);
+}
+
 pub trait Dec<T> {
     /// Emit a decrement instruction.
     fn dec(&mut self, op1: T);
diff --git a/src/juicebox_asm/insn/add.rs.html b/src/juicebox_asm/insn/add.rs.html
index 0a24951..d7f968a 100644
--- a/src/juicebox_asm/insn/add.rs.html
+++ b/src/juicebox_asm/insn/add.rs.html
@@ -1,4 +1,4 @@
-add.rs - source
1
+add.rs - source
1
 2
 3
 4
@@ -11,6 +11,18 @@
 11
 12
 13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
 
use crate::prelude::*;
 
 impl Add<Reg64, Reg64> for Asm {
@@ -24,4 +36,16 @@
         self.encode_rr(0x01, op1, op2);
     }
 }
+
+impl Add<MemOp, Reg16> for Asm {
+    fn add(&mut self, op1: MemOp, op2: Reg16) {
+        self.encode_mr(0x01, op1, op2);
+    }
+}
+
+impl Add<MemOp, Imm16> for Asm {
+    fn add(&mut self, op1: MemOp, op2: Imm16) {
+        self.encode_mi(0x81, 0, op1, op2);
+    }
+}
 
\ No newline at end of file diff --git a/src/juicebox_asm/insn/call.rs.html b/src/juicebox_asm/insn/call.rs.html index 38cb339..f9649ed 100644 --- a/src/juicebox_asm/insn/call.rs.html +++ b/src/juicebox_asm/insn/call.rs.html @@ -1,4 +1,4 @@ -call.rs - source
1
+call.rs - source
1
 2
 3
 4
diff --git a/src/juicebox_asm/insn/cmp.rs.html b/src/juicebox_asm/insn/cmp.rs.html
new file mode 100644
index 0000000..b50d90d
--- /dev/null
+++ b/src/juicebox_asm/insn/cmp.rs.html
@@ -0,0 +1,15 @@
+cmp.rs - source
1
+2
+3
+4
+5
+6
+7
+
use crate::prelude::*;
+
+impl Cmp<MemOp, Imm16> for Asm {
+    fn cmp(&mut self, op1: MemOp, op2: Imm16) {
+        self.encode_mi(0x81, 0x7, op1, op2);
+    }
+}
+
\ No newline at end of file diff --git a/src/juicebox_asm/insn/dec.rs.html b/src/juicebox_asm/insn/dec.rs.html index 2d46c4c..2afecd2 100644 --- a/src/juicebox_asm/insn/dec.rs.html +++ b/src/juicebox_asm/insn/dec.rs.html @@ -1,4 +1,4 @@ -dec.rs - source
1
+dec.rs - source
1
 2
 3
 4
diff --git a/src/juicebox_asm/insn/jmp.rs.html b/src/juicebox_asm/insn/jmp.rs.html
index 77b34c5..55da0e3 100644
--- a/src/juicebox_asm/insn/jmp.rs.html
+++ b/src/juicebox_asm/insn/jmp.rs.html
@@ -1,4 +1,4 @@
-jmp.rs - source
1
+jmp.rs - source
1
 2
 3
 4
diff --git a/src/juicebox_asm/insn/jnz.rs.html b/src/juicebox_asm/insn/jnz.rs.html
index 24a1e4a..45fb92d 100644
--- a/src/juicebox_asm/insn/jnz.rs.html
+++ b/src/juicebox_asm/insn/jnz.rs.html
@@ -1,4 +1,4 @@
-jnz.rs - source
1
+jnz.rs - source
1
 2
 3
 4
diff --git a/src/juicebox_asm/insn/jz.rs.html b/src/juicebox_asm/insn/jz.rs.html
index bbc67d8..4b17a71 100644
--- a/src/juicebox_asm/insn/jz.rs.html
+++ b/src/juicebox_asm/insn/jz.rs.html
@@ -1,4 +1,4 @@
-jz.rs - source
1
+jz.rs - source
1
 2
 3
 4
diff --git a/src/juicebox_asm/insn/mov.rs.html b/src/juicebox_asm/insn/mov.rs.html
index 0508d37..7be5d09 100644
--- a/src/juicebox_asm/insn/mov.rs.html
+++ b/src/juicebox_asm/insn/mov.rs.html
@@ -1,4 +1,4 @@
-mov.rs - source
1
+mov.rs - source
1
 2
 3
 4
@@ -103,6 +103,14 @@
 103
 104
 105
+106
+107
+108
+109
+110
+111
+112
+113
 
use crate::prelude::*;
 
 // -- MOV : reg reg
@@ -208,4 +216,12 @@
         self.encode_oi(0xb0, op1, op2);
     }
 }
+
+// -- MOV : mem imm
+
+impl Mov<MemOp, Imm16> for Asm {
+    fn mov(&mut self, op1: MemOp, op2: Imm16) {
+        self.encode_mi(0xc7, 0, op1, op2);
+    }
+}
 
\ No newline at end of file diff --git a/src/juicebox_asm/insn/nop.rs.html b/src/juicebox_asm/insn/nop.rs.html index 0469ab0..accc13a 100644 --- a/src/juicebox_asm/insn/nop.rs.html +++ b/src/juicebox_asm/insn/nop.rs.html @@ -1,4 +1,4 @@ -nop.rs - source
1
+nop.rs - source
1
 2
 3
 4
diff --git a/src/juicebox_asm/insn/ret.rs.html b/src/juicebox_asm/insn/ret.rs.html
index bb50d9f..97070e8 100644
--- a/src/juicebox_asm/insn/ret.rs.html
+++ b/src/juicebox_asm/insn/ret.rs.html
@@ -1,4 +1,4 @@
-ret.rs - source
1
+ret.rs - source
1
 2
 3
 4
diff --git a/src/juicebox_asm/insn/test.rs.html b/src/juicebox_asm/insn/test.rs.html
index 5245439..d636920 100644
--- a/src/juicebox_asm/insn/test.rs.html
+++ b/src/juicebox_asm/insn/test.rs.html
@@ -1,4 +1,4 @@
-test.rs - source
1
+test.rs - source
1
 2
 3
 4
@@ -11,6 +11,12 @@
 11
 12
 13
+14
+15
+16
+17
+18
+19
 
use crate::prelude::*;
 
 impl Test<Reg64, Reg64> for Asm {
@@ -24,4 +30,10 @@
         self.encode_rr(0x85, op1, op2);
     }
 }
+
+impl Test<MemOp, Imm16> for Asm {
+    fn test(&mut self, op1: MemOp, op2: Imm16) {
+        self.encode_mi(0xf7, 0, op1, op2);
+    }
+}
 
\ No newline at end of file diff --git a/src/juicebox_asm/label.rs.html b/src/juicebox_asm/label.rs.html index e1fb18a..e209289 100644 --- a/src/juicebox_asm/label.rs.html +++ b/src/juicebox_asm/label.rs.html @@ -1,4 +1,4 @@ -label.rs - source
1
+label.rs - source
1
 2
 3
 4
diff --git a/src/juicebox_asm/lib.rs.html b/src/juicebox_asm/lib.rs.html
index 4d6d4fe..83c8f81 100644
--- a/src/juicebox_asm/lib.rs.html
+++ b/src/juicebox_asm/lib.rs.html
@@ -1,4 +1,4 @@
-lib.rs - source
1
+lib.rs - source
1
 2
 3
 4
@@ -408,6 +408,65 @@
 408
 409
 410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
 
//! A simple `x64` jit assembler with a minimal runtime to execute emitted code for fun.
 //!
 //! The following is an fibonacci example implementation.
@@ -471,8 +530,8 @@
 //!     asm.ret();
 //!
 //!     // Move code into executable page and get function pointer to it.
-//!     let rt = Runtime::new(&asm.into_code());
-//!     let fib = unsafe { rt.as_fn::<extern "C" fn(u64) -> u64>() };
+//!     let mut rt = Runtime::new();
+//!     let fib = unsafe { rt.add_code::<extern "C" fn(u64) -> u64>(&asm.into_code()) };
 //!
 //!     for n in 0..15 {
 //!         let fib_jit = fib(n);
@@ -683,6 +742,42 @@
         self.emit(&[opc, modrm]);
     }
 
+    /// Encode a memory-immediate instruction.
+    fn encode_mi<T: Imm>(&mut self, opc: u8, opc_ext: u8, op1: MemOp, op2: T)
+    where
+        Self: EncodeMI<T>,
+    {
+        // MI operand encoding.
+        //   op1 -> modrm.rm
+        //   op2 -> imm
+        let mode = match op1 {
+            MemOp::Indirect(..) => {
+                assert!(!op1.base().need_sib() && !op1.base().is_pc_rel());
+                0b00
+            }
+            MemOp::IndirectDisp(..) => {
+                assert!(!op1.base().need_sib());
+                0b10
+            }
+        };
+
+        let modrm = modrm(
+            mode,             /* mode */
+            opc_ext,          /* reg */
+            op1.base().idx(), /* rm */
+        );
+
+        let prefix = <Self as EncodeMI<T>>::legacy_prefix();
+        let rex = <Self as EncodeMI<T>>::rex(&op1);
+
+        self.emit_optional(&[prefix, rex]);
+        self.emit(&[opc, modrm]);
+        if let MemOp::IndirectDisp(_, disp) = op1 {
+            self.emit(&disp.to_ne_bytes());
+        }
+        self.emit(op2.bytes());
+    }
+
     /// Encode a memory-register instruction.
     fn encode_mr<T: Reg>(&mut self, opc: u8, op1: MemOp, op2: T)
     where
@@ -818,4 +913,27 @@
 }
 impl EncodeMR<Reg32> for Asm {}
 impl EncodeMR<Reg64> for Asm {}
+
+/// Encode helper for memory-immediate instructions.
+trait EncodeMI<T: Imm> {
+    fn legacy_prefix() -> Option<u8> {
+        None
+    }
+
+    fn rex(op1: &MemOp) -> Option<u8> {
+        if op1.base().is_ext() {
+            Some(rex(false, 0, 0, op1.base().idx()))
+        } else {
+            None
+        }
+    }
+}
+
+impl EncodeMI<Imm8> for Asm {}
+impl EncodeMI<Imm16> for Asm {
+    fn legacy_prefix() -> Option<u8> {
+        Some(0x66)
+    }
+}
+impl EncodeMI<Imm32> for Asm {}
 
\ No newline at end of file diff --git a/src/juicebox_asm/prelude.rs.html b/src/juicebox_asm/prelude.rs.html index ac02bf8..e877286 100644 --- a/src/juicebox_asm/prelude.rs.html +++ b/src/juicebox_asm/prelude.rs.html @@ -1,4 +1,4 @@ -prelude.rs - source
1
+prelude.rs - source
1
 2
 3
 4
@@ -17,5 +17,5 @@
 pub use crate::label::Label;
 pub use crate::reg::{Reg16, Reg32, Reg64, Reg8};
 
-pub use crate::insn::{Add, Call, Dec, Jmp, Jnz, Jz, Mov, Test};
+pub use crate::insn::{Add, Call, Cmp, Dec, Jmp, Jnz, Jz, Mov, Test};
 
\ No newline at end of file diff --git a/src/juicebox_asm/reg.rs.html b/src/juicebox_asm/reg.rs.html index fec18cf..a511005 100644 --- a/src/juicebox_asm/reg.rs.html +++ b/src/juicebox_asm/reg.rs.html @@ -1,4 +1,4 @@ -reg.rs - source
1
+reg.rs - source
1
 2
 3
 4
diff --git a/src/juicebox_asm/rt.rs.html b/src/juicebox_asm/rt.rs.html
index 46597b0..244be8b 100644
--- a/src/juicebox_asm/rt.rs.html
+++ b/src/juicebox_asm/rt.rs.html
@@ -1,4 +1,4 @@
-rt.rs - source
1
+rt.rs - source
1
 2
 3
 4
@@ -57,62 +57,120 @@
 57
 58
 59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
 
//! A simple runtime which can be used to execute emitted instructions.
 
-use core::ffi::c_void;
+use core::slice;
 use nix::sys::mman::{mmap, mprotect, munmap, MapFlags, ProtFlags};
 
 /// A simple `mmap`ed runtime with executable pages.
 pub struct Runtime {
-    buf: *mut c_void,
+    buf: *mut u8,
     len: usize,
+    idx: usize,
 }
 
 impl Runtime {
     /// Create a new [Runtime].
-    pub fn new(code: impl AsRef<[u8]>) -> Runtime {
+    pub fn new() -> Runtime {
         // Allocate a single page.
         let len = core::num::NonZeroUsize::new(4096).unwrap();
         let buf = unsafe {
             mmap(
                 None,
                 len,
-                ProtFlags::PROT_WRITE,
+                ProtFlags::PROT_WRITE | ProtFlags::PROT_READ | ProtFlags::PROT_EXEC,
                 MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS,
                 0, /* fd */
                 0, /* off */
             )
-            .unwrap()
+            .unwrap() as *mut u8
         };
-        {
-            // Copy over code.
-            let code = code.as_ref();
-            assert!(code.len() < len.get());
-            unsafe { std::ptr::copy_nonoverlapping(code.as_ptr(), buf.cast(), len.get()) };
-        }
-        unsafe {
-            // Remove write permissions from code buffer and allow to read-execute from it.
-            mprotect(buf, len.get(), ProtFlags::PROT_READ | ProtFlags::PROT_EXEC)
-                .expect("Failed to RX mprotect Runtime code buffer");
-        }
 
         Runtime {
             buf,
             len: len.get(),
+            idx: 0,
+        }
+    }
+
+    /// Write protect the underlying code page(s).
+    pub fn protect(&mut self) {
+        unsafe {
+            // Remove write permissions from code buffer and allow to read-execute from it.
+            mprotect(
+                self.buf.cast(),
+                self.len,
+                ProtFlags::PROT_READ | ProtFlags::PROT_EXEC,
+            )
+            .expect("Failed to RX mprotect Runtime code buffer");
         }
     }
 
+    /// Add block of code to the runtime and get function pointer back.
+    pub unsafe fn add_code<F>(&mut self, code: impl AsRef<[u8]>) -> F {
+        // Get pointer to start of next free byte.
+        assert!(self.idx < self.len);
+        let fn_start = self.buf.add(self.idx);
+
+        // Copy over code.
+        let code = code.as_ref();
+        assert!(code.len() < (self.len - self.idx));
+        unsafe { std::ptr::copy_nonoverlapping(code.as_ptr(), fn_start, code.len()) };
+
+        // Increment index to next free byte.
+        self.idx += code.len();
+
+        // Return function to newly added code.
+        Self::as_fn::<F>(fn_start)
+    }
+
     /// Reinterpret the block of code as `F`.
     #[inline]
-    pub unsafe fn as_fn<F>(&self) -> F {
-        unsafe { std::mem::transmute_copy(&self.buf) }
+    unsafe fn as_fn<F>(fn_start: *mut u8) -> F {
+        unsafe { std::mem::transmute_copy(&fn_start) }
+    }
+
+    /// Dump the currently added code to a file called `jit.asm`. The disassembly can be inspected
+    /// as `ndisasm -b 64 jit.asm`.
+    pub fn dump(&self) {
+        let code = unsafe { slice::from_raw_parts(self.buf, self.idx) };
+        std::fs::write("jit.asm", code).unwrap();
     }
 }
 
 impl Drop for Runtime {
     fn drop(&mut self) {
         unsafe {
-            munmap(self.buf, self.len).expect("Failed to munmap Runtime");
+            munmap(self.buf.cast(), self.len).expect("Failed to munmap Runtime");
         }
     }
 }
diff --git a/src/tiny_vm/tiny_vm.rs.html b/src/tiny_vm/tiny_vm.rs.html
new file mode 100644
index 0000000..b986ed6
--- /dev/null
+++ b/src/tiny_vm/tiny_vm.rs.html
@@ -0,0 +1,1395 @@
+tiny_vm.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+
//! TinyVm example.
+//!
+//! This example introduces as simple 16 bit virtual machine the [`TinyVm`]. The VM consits of
+//! three registers defined in [`TinyReg`], a separate _data_ and _insutrction_ memory and a small
+//! set of instructions [`TinyInsn`], sufficient to implement a guest program to compute the
+//! fiibonacci sequence.
+//!
+//! The `TinyVm` implements a simple _just-in-time (JIT)_ compiler to demonstrate the
+//! [`juicebox_asm`] crate. Additionally, it implements a reference _interpreter_.
+//!
+//! ```
+//! fn main() {
+//!   let mut prog = Vec::new();
+//!   prog.push(TinyInsn::LoadImm(TinyReg::A, 100));
+//!   prog.push(TinyInsn::Add(TinyReg::B, TinyReg::A));
+//!   prog.push(TinyInsn::Addi(TinyReg::C, 100));
+//!   prog.push(TinyInsn::Halt);
+//!
+//!   let mut vm = TinyVm::new(prog);
+//!   vm.interp();
+//!
+//!   assert_eq!(100, vm.read_reg(TinyReg::A));
+//!   assert_eq!(100, vm.read_reg(TinyReg::B));
+//!   assert_eq!(100, vm.read_reg(TinyReg::C));
+//!   assert_eq!(4, vm.icnt);
+//!   assert_eq!(4, vm.pc);
+//!
+//!   vm.pc = 0;
+//!   vm.jit();
+//!
+//!   assert_eq!(100, vm.read_reg(TinyReg::A));
+//!   assert_eq!(200, vm.read_reg(TinyReg::B));
+//!   assert_eq!(200, vm.read_reg(TinyReg::C));
+//!   assert_eq!(8, vm.icnt);
+//!   assert_eq!(4, vm.pc);
+//! }
+//! ```
+
+#[cfg(not(any(target_arch = "x86_64", target_os = "linux")))]
+compile_error!("Only supported on x86_64 with SystemV abi");
+
+use juicebox_asm::prelude::*;
+use juicebox_asm::Runtime;
+
+/// A guest physical address.
+pub struct PhysAddr(pub u16);
+
+impl Into<usize> for PhysAddr {
+    fn into(self) -> usize {
+        self.0 as usize
+    }
+}
+
+/// The registers for the [`TinyVm`].
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum TinyReg {
+    A,
+    B,
+    C,
+}
+
+impl TinyReg {
+    #[inline]
+    fn idx(&self) -> usize {
+        *self as usize
+    }
+}
+
+/// The instructions for the [`TinyVm`].
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum TinyInsn {
+    /// Halt the VM.
+    Halt,
+    /// Load the immediate value into the register `reg = imm`.
+    LoadImm(TinyReg, u16),
+    /// Load a value from the memory (absolute addressing) into the register `reg = mem[imm]`.
+    Load(TinyReg, u16),
+    /// Store a value from the register into the memory (absolute addressing) `mem[imm] = reg`.
+    Store(TinyReg, u16),
+    /// Add the register to the register `reg1 += reg2`.
+    Add(TinyReg, TinyReg),
+    /// Add the immediate to the register `reg += imm`.
+    Addi(TinyReg, i16),
+    /// Jump unconditional (absolute addressing) `pc = disp`.
+    Branch(usize),
+    /// Jump if the register is zero (absolute addressing) `pc = (reg == 0) ? disp : pc++`.
+    BranchZero(TinyReg, usize),
+}
+
+/// Value returned from a [`JitFn`].
+#[repr(C)]
+struct JitRet(u64, u64);
+
+/// Function signature defining the simple JIT ABI used in this example.
+/// A `JitFn` represents the entry point to a jit compiled _basic block_ of the guest software.
+///
+/// ```text
+/// JIT entry:
+///     arg0: pointer to guest registers
+///     arg1: pointer to guest data memory
+///
+/// JIT exit:
+///      JitRet(0, N): Halt instruction, executed N instructions.
+///      JitRet(N, R): N!=0
+///                    End of basic block, executed N instructions,
+///                    must re-enter at `pc = R`.
+/// ```
+type JitFn = extern "C" fn(*mut u16, *mut u8) -> JitRet;
+
+/// The `TinyVm` virtual machine state.
+pub struct TinyVm {
+    /// Data memory, covering full 16 bit guest address space.
+    ///
+    /// For simplicity add additional trailing 1 byte to support an unaligned access to 0xffff
+    /// without any special handling.
+    dmem: [u8; 0x1_0000 + 1],
+    /// Instruction memory.
+    imem: Vec<TinyInsn>,
+    /// VM registers.
+    regs: [u16; 3],
+    /// VM program counter.
+    pc: usize,
+    /// VM executed instruction counter (perf counter).
+    icnt: usize,
+
+    // -- JIT state.
+    /// Mapping of guest PCs to jitted host code (`JitFn`). This mapping is filled when guest
+    /// _basic blocks_ are jitted.
+    jit_cache: Vec<Option<JitFn>>,
+    /// JIT runtime maintaining the host pages containing the jitted guest code.
+    rt: Runtime,
+}
+
+impl TinyVm {
+    /// Create a new [`TinyVm`] and initialize the instruction memory from `code`.
+    pub fn new(code: Vec<TinyInsn>) -> Self {
+        let mut jit_cache = Vec::with_capacity(code.len());
+        jit_cache.resize(code.len(), None);
+
+        TinyVm {
+            dmem: [0; 0x1_0000 + 1],
+            imem: code,
+            regs: [0; 3],
+            pc: 0,
+            icnt: 0,
+            // -- JIT state.
+            jit_cache,
+            rt: Runtime::new(),
+        }
+    }
+
+    /// Read guest register.
+    #[inline]
+    pub fn read_reg(&self, reg: TinyReg) -> u16 {
+        self.regs[reg.idx()]
+    }
+
+    /// Write guest register.
+    #[inline]
+    pub fn write_reg(&mut self, reg: TinyReg, val: u16) {
+        self.regs[reg.idx()] = val;
+    }
+
+    /// Read guest data memory.
+    #[inline]
+    pub fn read_mem(&self, paddr: PhysAddr) -> u16 {
+        // dmem covers whole 16 bit address space + 1 byte for unaligned access at 0xffff.
+        let bytes = self.dmem[paddr.into()..][..2].try_into().unwrap();
+        u16::from_le_bytes(bytes)
+    }
+
+    /// Write guest data memory.
+    #[inline]
+    pub fn write_mem(&mut self, paddr: PhysAddr, val: u16) {
+        let bytes = val.to_le_bytes();
+        self.dmem[paddr.into()..][..2].copy_from_slice(&bytes);
+    }
+
+    /// Dump the VM state to stdout.
+    pub fn dump(&self) {
+        println!("-- TinyVm state --");
+        println!("  ICNT: {}", self.icnt);
+        println!("  PC  : {:02x}", self.pc - 1);
+        println!(
+            "  A:{:04x} B:{:04x} C:{:04x}",
+            self.read_reg(TinyReg::A),
+            self.read_reg(TinyReg::B),
+            self.read_reg(TinyReg::C),
+        );
+    }
+
+    /// Run in interpreter mode until the next [`TinyInsn::Halt`] instruction is hit.
+    pub fn interp(&mut self) {
+        'outer: loop {
+            let insn = self.imem[self.pc];
+            //println!("[0x{:02x}] {:?}", self.pc, insn);
+
+            self.pc = self.pc.wrapping_add(1);
+            self.icnt += 1;
+
+            match insn {
+                TinyInsn::Halt => {
+                    break 'outer;
+                }
+                TinyInsn::LoadImm(a, imm) => {
+                    self.write_reg(a, imm);
+                }
+                TinyInsn::Load(a, addr) => {
+                    let val = self.read_mem(PhysAddr(addr));
+                    self.write_reg(a, val);
+                }
+                TinyInsn::Store(a, addr) => {
+                    let val = self.read_reg(a);
+                    self.write_mem(PhysAddr(addr), val);
+                }
+                TinyInsn::Add(a, b) => {
+                    let res = self.read_reg(a).wrapping_add(self.read_reg(b));
+                    self.write_reg(a, res);
+                }
+                TinyInsn::Addi(a, imm) => {
+                    let res = self.read_reg(a).wrapping_add(imm as u16);
+                    self.write_reg(a, res);
+                }
+                TinyInsn::Branch(disp) => {
+                    self.pc = disp;
+                }
+                TinyInsn::BranchZero(a, disp) => {
+                    if self.read_reg(a) == 0 {
+                        self.pc = disp;
+                    }
+                }
+            }
+        }
+    }
+
+    /// Run in JIT mode until the next [`TinyInsn::Halt`] instruction is hit. Translate guest
+    /// _basic blocks_ on demand.
+    pub fn jit(&mut self) {
+        'outer: loop {
+            if let Some(bb_fn) = self.jit_cache[self.pc] {
+                match bb_fn(self.regs.as_mut_ptr(), self.dmem.as_mut_ptr()) {
+                    JitRet(0, insn) => {
+                        self.pc += insn as usize;
+                        self.icnt += insn as usize;
+                        break 'outer;
+                    }
+                    JitRet(insn, reenter_pc) => {
+                        self.pc = reenter_pc as usize;
+                        self.icnt += insn as usize;
+                    }
+                }
+            } else {
+                let bb_fn = self.translate_next_bb();
+                self.jit_cache[self.pc] = Some(bb_fn);
+                //println!("[0x{:02x}] translated bb at {:p}", self.pc, bb_fn);
+            }
+        }
+    }
+
+    /// Translate the bb at the current pc and return a JitFn pointer to it.
+    fn translate_next_bb(&mut self) -> JitFn {
+        let mut bb = Asm::new();
+        let mut pc = self.pc;
+
+        'outer: loop {
+            let insn = self.imem[pc];
+
+            pc = pc.wrapping_add(1);
+
+            // JIT ABI.
+            //   enter
+            //     rdi => regs
+            //     rsi => dmem
+            //   exit
+            //     rax => JitRet(0,
+            //     rdx =>        1)
+
+            // Generate memory operand into regs for guest register.
+            let reg_op = |r: TinyReg| {
+                MemOp::IndirectDisp(Reg64::rdi, (r.idx() * 2).try_into().expect("only 3 regs"))
+            };
+
+            // Generate memory operand into dmem for guest phys address.
+            let mem_op = |paddr: u16| MemOp::IndirectDisp(Reg64::rsi, paddr.into());
+
+            // Compute instructions in translated basic block.
+            let bb_icnt = || -> u64 { (pc - self.pc).try_into().unwrap() };
+
+            let reenter_pc = |pc: usize| -> u64 { pc.try_into().unwrap() };
+
+            match insn {
+                TinyInsn::Halt => {
+                    bb.mov(Reg64::rax, Imm64::from(0));
+                    bb.mov(Reg64::rdx, Imm64::from(bb_icnt()));
+                    bb.ret();
+                    break 'outer;
+                }
+                TinyInsn::LoadImm(a, imm) => {
+                    bb.mov(reg_op(a), Imm16::from(imm));
+                }
+                TinyInsn::Load(a, addr) => {
+                    bb.mov(Reg16::ax, mem_op(addr));
+                    bb.mov(reg_op(a), Reg16::ax);
+                }
+                TinyInsn::Store(a, addr) => {
+                    bb.mov(Reg16::ax, reg_op(a));
+                    bb.mov(mem_op(addr), Reg16::ax);
+                }
+                TinyInsn::Add(a, b) => {
+                    bb.mov(Reg16::ax, reg_op(b));
+                    bb.add(reg_op(a), Reg16::ax);
+                }
+                TinyInsn::Addi(a, imm) => {
+                    bb.add(reg_op(a), Imm16::from(imm));
+                }
+                TinyInsn::Branch(disp) => {
+                    bb.mov(Reg64::rax, Imm64::from(bb_icnt()));
+                    bb.mov(Reg64::rdx, Imm64::from(reenter_pc(disp)));
+                    bb.ret();
+                    break 'outer;
+                }
+                TinyInsn::BranchZero(a, disp) => {
+                    bb.cmp(reg_op(a), Imm16::from(0u16));
+                    bb.mov(Reg64::rax, Imm64::from(bb_icnt()));
+                    bb.mov(Reg64::rdx, Imm64::from(reenter_pc(disp)));
+
+                    let mut skip_next_pc = Label::new();
+                    // If register is zero, skip setting next pc as reenter pc.
+                    bb.jz(&mut skip_next_pc);
+                    bb.mov(Reg64::rdx, Imm64::from(reenter_pc(pc)));
+                    bb.bind(&mut skip_next_pc);
+                    bb.ret();
+                    break 'outer;
+                }
+            }
+        }
+
+        unsafe { self.rt.add_code::<JitFn>(bb.into_code()) }
+    }
+}
+
+/// A minial fixup utility to implement jump labels when constructing guest programs.
+pub struct Fixup {
+    pc: usize,
+}
+
+impl Fixup {
+    /// Create a new `Fixup` at the current pc.
+    pub fn new(pc: usize) -> Self {
+        Fixup { pc }
+    }
+
+    /// Bind the `Fixup` to the current location of `prog` and resolve the `Fixup`.
+    pub fn bind(self, prog: &mut Vec<TinyInsn>) {
+        let plen = prog.len();
+        let insn = prog.get_mut(self.pc).expect(&format!(
+            "Trying to apply Fixup, but Fixup is out of range pc={} prog.len={}",
+            self.pc, plen
+        ));
+
+        match insn {
+            TinyInsn::Branch(disp) | TinyInsn::BranchZero(_, disp) => {
+                *disp = plen;
+            }
+            _ => {
+                unimplemented!("Trying to fixup non-branch instruction '{:?}'", *insn);
+            }
+        }
+    }
+}
+
+/// Generate a guest program to compute the fiibonacci sequence for `n`.
+pub fn make_tinyvm_fib(start_n: u16) -> Vec<TinyInsn> {
+    // Reference implementation:
+    //
+    // int fib(int n)
+    //   int tmp = 0;
+    //   int prv = 1;
+    //   int sum = 0;
+    // loop:
+    //   if (n == 0) goto end;
+    //   tmp = sum;
+    //   sum += prv;
+    //   prv = tmp;
+    //   --n;
+    //   goto loop;
+    // end:
+    //   return sum;
+
+    // Variables live in memory, bin to fixed addresses.
+    let tmp = 0u16;
+    let prv = 2u16;
+    let sum = 4u16;
+    // Loop counter mapped to register.
+    let n = TinyReg::C;
+
+    let mut prog = Vec::with_capacity(32);
+
+    // n = start_n
+    prog.push(TinyInsn::LoadImm(n, start_n));
+
+    // tmp = sum = 0
+    prog.push(TinyInsn::LoadImm(TinyReg::A, 0));
+    prog.push(TinyInsn::Store(TinyReg::A, tmp));
+    prog.push(TinyInsn::Store(TinyReg::A, sum));
+
+    // prv = 1
+    prog.push(TinyInsn::LoadImm(TinyReg::A, 1));
+    prog.push(TinyInsn::Store(TinyReg::A, prv));
+
+    // Create loop_start label.
+    let loop_start = prog.len();
+
+    // Create fixup to capture PC that need to be patched later.
+    let end_fixup = Fixup::new(prog.len());
+
+    // if (n == 0) goto end
+    prog.push(TinyInsn::BranchZero(n, 0xdead));
+
+    // tmp = sum
+    prog.push(TinyInsn::Load(TinyReg::A, sum));
+    prog.push(TinyInsn::Store(TinyReg::A, tmp));
+
+    // sum += prv
+    prog.push(TinyInsn::Load(TinyReg::B, prv));
+    prog.push(TinyInsn::Add(TinyReg::A, TinyReg::B));
+    prog.push(TinyInsn::Store(TinyReg::A, sum));
+
+    // prv = tmp
+    prog.push(TinyInsn::Load(TinyReg::A, tmp));
+    prog.push(TinyInsn::Store(TinyReg::A, prv));
+
+    // --n
+    prog.push(TinyInsn::Addi(n, -1));
+
+    // goto loop_start
+    prog.push(TinyInsn::Branch(loop_start));
+
+    // Bind end fixup to current PC, to patch branch to jump to here.
+    end_fixup.bind(&mut prog);
+
+    // TinyReg::A = sum
+    prog.push(TinyInsn::Load(TinyReg::A, sum));
+
+    // Halt the VM.
+    prog.push(TinyInsn::Halt);
+
+    prog
+}
+
+/// Generate a test program for the jit.
+pub fn make_tinyvm_jit_test() -> Vec<TinyInsn> {
+    let mut prog = Vec::with_capacity(32);
+
+    prog.push(TinyInsn::Branch(1));
+    prog.push(TinyInsn::LoadImm(TinyReg::A, 0x0010));
+    prog.push(TinyInsn::LoadImm(TinyReg::B, 0x0));
+
+    let start = prog.len();
+    let end = Fixup::new(prog.len());
+    prog.push(TinyInsn::BranchZero(TinyReg::A, 0xdead));
+    prog.push(TinyInsn::LoadImm(TinyReg::C, 0x1));
+    prog.push(TinyInsn::Add(TinyReg::B, TinyReg::C));
+    prog.push(TinyInsn::Addi(TinyReg::A, -1));
+    prog.push(TinyInsn::Branch(start));
+    end.bind(&mut prog);
+    prog.push(TinyInsn::LoadImm(TinyReg::A, 0xabcd));
+    prog.push(TinyInsn::Store(TinyReg::A, 0xffff));
+    prog.push(TinyInsn::Load(TinyReg::C, 0xffff));
+    prog.push(TinyInsn::Halt);
+    prog.push(TinyInsn::Halt);
+    prog.push(TinyInsn::Halt);
+    prog.push(TinyInsn::Halt);
+
+    prog
+}
+
+/// Generate a simple count down loop to crunch some instructions.
+pub fn make_tinyvm_jit_perf() -> Vec<TinyInsn> {
+    let mut prog = Vec::with_capacity(32);
+
+    prog.push(TinyInsn::Halt);
+    prog.push(TinyInsn::LoadImm(TinyReg::A, 0xffff));
+    prog.push(TinyInsn::LoadImm(TinyReg::B, 1));
+    prog.push(TinyInsn::LoadImm(TinyReg::C, 2));
+    prog.push(TinyInsn::Addi(TinyReg::A, -1));
+    prog.push(TinyInsn::BranchZero(TinyReg::A, 0));
+    prog.push(TinyInsn::Branch(2));
+    prog
+}
+
+fn main() {
+    let use_jit = match std::env::args().nth(1) {
+        Some(a) if a == "-h" || a == "--help" => {
+            println!("Usage: tiny_vm [mode]");
+            println!("");
+            println!("Options:");
+            println!("    mode    if mode is 'jit' then run in jit mode, else in interpreter mode");
+            std::process::exit(0);
+        }
+        Some(a) if a == "jit" => true,
+        _ => false,
+    };
+
+    let mut vm = TinyVm::new(make_tinyvm_fib(42));
+
+    if use_jit {
+        println!("Run in jit mode..");
+        vm.jit();
+    } else {
+        println!("Run in interpreter mode..");
+        vm.interp();
+    }
+    vm.dump();
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    fn fib_rs(n: u64) -> u64 {
+        if n < 2 {
+            n
+        } else {
+            let mut fib_n_m1 = 0;
+            let mut fib_n = 1;
+            for _ in 1..n {
+                let tmp = fib_n + fib_n_m1;
+                fib_n_m1 = fib_n;
+                fib_n = tmp;
+            }
+            fib_n
+        }
+    }
+
+    #[test]
+    fn test_fib_interp() {
+        for n in 0..92 {
+            let mut vm = TinyVm::new(make_tinyvm_fib(n));
+            vm.interp();
+
+            assert_eq!((fib_rs(n as u64) & 0xffff) as u16, vm.read_reg(TinyReg::A));
+        }
+    }
+
+    #[test]
+    fn test_fib_jit() {
+        for n in 0..92 {
+            let mut vm = TinyVm::new(make_tinyvm_fib(n));
+            vm.jit();
+
+            assert_eq!((fib_rs(n as u64) & 0xffff) as u16, vm.read_reg(TinyReg::A));
+        }
+    }
+
+    #[test]
+    fn test_fib_icnt() {
+        let mut vm1 = TinyVm::new(make_tinyvm_fib(91));
+        vm1.interp();
+        let mut vm2 = TinyVm::new(make_tinyvm_fib(91));
+        vm2.jit();
+
+        assert_eq!(vm1.icnt, vm2.icnt);
+        assert_eq!(vm1.pc, vm2.pc);
+    }
+
+    #[test]
+    fn test_jit_load_imm() {
+        let mut prog = Vec::new();
+        prog.push(TinyInsn::LoadImm(TinyReg::A, 0x1111));
+        prog.push(TinyInsn::LoadImm(TinyReg::B, 0x2222));
+        prog.push(TinyInsn::LoadImm(TinyReg::C, 0x3333));
+        prog.push(TinyInsn::Halt);
+
+        let mut vm = TinyVm::new(prog);
+        vm.jit();
+
+        assert_eq!(0x1111, vm.read_reg(TinyReg::A));
+        assert_eq!(0x2222, vm.read_reg(TinyReg::B));
+        assert_eq!(0x3333, vm.read_reg(TinyReg::C));
+
+        assert_eq!(4, vm.icnt);
+        assert_eq!(4, vm.pc);
+    }
+
+    #[test]
+    fn test_jit_add() {
+        let mut prog = Vec::new();
+        prog.push(TinyInsn::LoadImm(TinyReg::A, 0));
+        prog.push(TinyInsn::Addi(TinyReg::A, 123));
+
+        prog.push(TinyInsn::LoadImm(TinyReg::B, 100));
+        prog.push(TinyInsn::LoadImm(TinyReg::C, 200));
+        prog.push(TinyInsn::Add(TinyReg::B, TinyReg::C));
+        prog.push(TinyInsn::Halt);
+
+        let mut vm = TinyVm::new(prog);
+        vm.jit();
+
+        assert_eq!(123, vm.read_reg(TinyReg::A));
+        assert_eq!(300, vm.read_reg(TinyReg::B));
+        assert_eq!(200, vm.read_reg(TinyReg::C));
+
+        assert_eq!(6, vm.icnt);
+        assert_eq!(6, vm.pc);
+    }
+
+    #[test]
+    fn test_jit_load_store() {
+        let mut prog = Vec::new();
+        prog.push(TinyInsn::Load(TinyReg::A, 0xffff));
+
+        prog.push(TinyInsn::LoadImm(TinyReg::B, 0xf00d));
+        prog.push(TinyInsn::Store(TinyReg::B, 0x8000));
+        prog.push(TinyInsn::Halt);
+
+        let mut vm = TinyVm::new(prog);
+        vm.write_mem(PhysAddr(0xffff), 0xaabb);
+        vm.jit();
+
+        assert_eq!(0xaabb, vm.read_reg(TinyReg::A));
+        assert_eq!(0xf00d, vm.read_mem(PhysAddr(0x8000)));
+
+        assert_eq!(4, vm.icnt);
+        assert_eq!(4, vm.pc);
+    }
+
+    #[test]
+    fn test_jit_branch() {
+        let mut prog = Vec::new();
+        prog.push(TinyInsn::Branch(2));
+        prog.push(TinyInsn::Halt);
+        prog.push(TinyInsn::Branch(6));
+        prog.push(TinyInsn::LoadImm(TinyReg::A, 1));
+        prog.push(TinyInsn::LoadImm(TinyReg::B, 2));
+        prog.push(TinyInsn::LoadImm(TinyReg::C, 3));
+        prog.push(TinyInsn::Branch(1));
+
+        let mut vm = TinyVm::new(prog);
+        vm.jit();
+
+        assert_eq!(0, vm.read_reg(TinyReg::A));
+        assert_eq!(0, vm.read_reg(TinyReg::B));
+        assert_eq!(0, vm.read_reg(TinyReg::C));
+
+        assert_eq!(4, vm.icnt);
+        assert_eq!(2, vm.pc);
+    }
+
+    #[test]
+    fn test_jit_branch_zero() {
+        let mut prog = Vec::new();
+        prog.push(TinyInsn::LoadImm(TinyReg::A, 1));
+        prog.push(TinyInsn::BranchZero(TinyReg::A, 5));
+        prog.push(TinyInsn::LoadImm(TinyReg::A, 0));
+        prog.push(TinyInsn::BranchZero(TinyReg::A, 5));
+        prog.push(TinyInsn::LoadImm(TinyReg::B, 22));
+        prog.push(TinyInsn::Halt);
+
+        let mut vm = TinyVm::new(prog);
+        vm.jit();
+
+        assert_eq!(0, vm.read_reg(TinyReg::A));
+        assert_eq!(0, vm.read_reg(TinyReg::B));
+        assert_eq!(0, vm.read_reg(TinyReg::C));
+
+        assert_eq!(5, vm.icnt);
+        assert_eq!(6, vm.pc);
+    }
+
+    #[test]
+    fn test_mixed() {
+        let mut prog = Vec::new();
+        prog.push(TinyInsn::LoadImm(TinyReg::A, 100));
+        prog.push(TinyInsn::Add(TinyReg::B, TinyReg::A));
+        prog.push(TinyInsn::Addi(TinyReg::C, 100));
+        prog.push(TinyInsn::Halt);
+
+        let mut vm = TinyVm::new(prog);
+        vm.interp();
+
+        assert_eq!(100, vm.read_reg(TinyReg::A));
+        assert_eq!(100, vm.read_reg(TinyReg::B));
+        assert_eq!(100, vm.read_reg(TinyReg::C));
+        assert_eq!(4, vm.icnt);
+        assert_eq!(4, vm.pc);
+
+        vm.pc = 0;
+        vm.jit();
+
+        assert_eq!(100, vm.read_reg(TinyReg::A));
+        assert_eq!(200, vm.read_reg(TinyReg::B));
+        assert_eq!(200, vm.read_reg(TinyReg::C));
+        assert_eq!(8, vm.icnt);
+        assert_eq!(4, vm.pc);
+    }
+}
+
\ No newline at end of file -- cgit v1.2.3