1use juicebox_asm::insn::*;
44use juicebox_asm::Runtime;
45use juicebox_asm::{Asm, Imm16, Imm64, Mem16, Reg16, Reg64};
46
47pub struct PhysAddr(pub u16);
49
50impl Into<usize> for PhysAddr {
51 fn into(self) -> usize {
52 self.0 as usize
53 }
54}
55
56#[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#[derive(Debug, PartialEq, Clone, Copy)]
73pub enum TinyInsn {
74 Halt,
76 LoadImm(TinyReg, u16),
78 Load(TinyReg, u16),
80 Store(TinyReg, u16),
82 Add(TinyReg, TinyReg),
84 Addi(TinyReg, i16),
86 Branch(usize),
88 BranchZero(TinyReg, usize),
90}
91
92#[repr(C)]
94struct JitRet(u64, u64);
95
96type JitFn = extern "C" fn(*mut u16, *mut u8) -> JitRet;
111
112pub struct TinyVm {
114 dmem: [u8; 0x1_0000 + 1],
119 imem: Vec<TinyInsn>,
121 regs: [u16; 3],
123 pc: usize,
125 icnt: usize,
127
128 jit_cache: Vec<Option<JitFn>>,
132 rt: Runtime,
134}
135
136impl TinyVm {
137 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_cache,
150 rt: Runtime::new(),
151 }
154 }
155
156 #[inline]
158 pub fn read_reg(&self, reg: TinyReg) -> u16 {
159 self.regs[reg.idx()]
160 }
161
162 #[inline]
164 pub fn write_reg(&mut self, reg: TinyReg, val: u16) {
165 self.regs[reg.idx()] = val;
166 }
167
168 #[inline]
170 pub fn read_mem(&self, paddr: PhysAddr) -> u16 {
171 let bytes = self.dmem[paddr.into()..][..2].try_into().unwrap();
173 u16::from_le_bytes(bytes)
174 }
175
176 #[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 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 pub fn interp(&mut self) {
198 'outer: loop {
199 let insn = self.imem[self.pc];
200 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 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 bb_fn
251 };
252
253 match bb_fn(self.regs.as_mut_ptr(), self.dmem.as_mut_ptr()) {
254 JitRet(0, insn) => {
256 self.pc += insn as usize;
257 self.icnt += insn as usize;
258 break 'outer;
259 }
260 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 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 let reg_op = |r: TinyReg| {
292 Mem16::indirect_disp(Reg64::rdi, (r.idx() * 2).try_into().expect("only 3 regs"))
293 };
294
295 let mem_op = |paddr: u16| Mem16::indirect_disp(Reg64::rsi, paddr.into());
297
298 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 bb.mov(Reg64::rdx, Imm64::from(reenter_pc(pc)));
339
340 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
354pub struct Fixup {
356 pc: usize,
357}
358
359impl Fixup {
360 pub fn new(pc: usize) -> Self {
362 Fixup { pc }
363 }
364
365 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
384pub fn make_tinyvm_fib(start_n: u16) -> Vec<TinyInsn> {
386 let tmp = 0u16;
404 let prv = 2u16;
405 let sum = 4u16;
406 let n = TinyReg::C;
408
409 let mut prog = Vec::with_capacity(32);
410
411 prog.push(TinyInsn::LoadImm(n, start_n));
413
414 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 prog.push(TinyInsn::LoadImm(TinyReg::A, 1));
421 prog.push(TinyInsn::Store(TinyReg::A, prv));
422
423 let loop_start = prog.len();
425
426 let end_fixup = Fixup::new(prog.len());
428
429 prog.push(TinyInsn::BranchZero(n, 0xdead));
431
432 prog.push(TinyInsn::Load(TinyReg::A, sum));
434 prog.push(TinyInsn::Store(TinyReg::A, tmp));
435
436 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 prog.push(TinyInsn::Load(TinyReg::A, tmp));
443 prog.push(TinyInsn::Store(TinyReg::A, prv));
444
445 prog.push(TinyInsn::Addi(n, -1));
447
448 prog.push(TinyInsn::Branch(loop_start));
450
451 end_fixup.bind(&mut prog);
453
454 prog.push(TinyInsn::Load(TinyReg::A, sum));
456
457 prog.push(TinyInsn::Halt);
459
460 prog
461}
462
463pub 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
490pub 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}