From 8ededb86127393397905bb67817ae4d42061f10b Mon Sep 17 00:00:00 2001
From: Jakub Doka <jakub.doka2@gmail.com>
Date: Sun, 22 Dec 2024 21:33:15 +0100
Subject: [PATCH] adding standard instruction logging utility

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
---
 bytecode/build.rs        | 21 +++++++++++++++
 lang/src/backend/hbvm.rs | 55 +++++-----------------------------------
 vm/src/mem/mod.rs        | 44 +++++++++++++++++++++++++++++++-
 vm/src/vmrun.rs          |  1 +
 4 files changed, 72 insertions(+), 49 deletions(-)

diff --git a/bytecode/build.rs b/bytecode/build.rs
index e7aaad4e..2a10399b 100644
--- a/bytecode/build.rs
+++ b/bytecode/build.rs
@@ -98,6 +98,27 @@ fn gen_instrs(generated: &mut String) -> Result<(), Box<dyn std::error::Error>>
             writeln!(generated, "    {name} = {id},")?;
         }
         writeln!(generated, "}}")?;
+
+        writeln!(generated, "impl {instr} {{")?;
+        writeln!(generated, "    pub fn size(self) -> usize {{")?;
+        writeln!(generated, "        match self {{")?;
+        let mut instrs = instructions().collect::<Vec<_>>();
+        instrs.sort_unstable_by_key(|&[.., ty, _]| iter_args(ty).map(arg_to_width).sum::<usize>());
+        for group in instrs.chunk_by(|[.., a, _], [.., b, _]| {
+            iter_args(a).map(arg_to_width).sum::<usize>()
+                == iter_args(b).map(arg_to_width).sum::<usize>()
+        }) {
+            let ty = group[0][2];
+            for &[_, name, ..] in group {
+                writeln!(generated, "            | {instr}::{name}")?;
+            }
+            generated.pop();
+            let size = iter_args(ty).map(arg_to_width).sum::<usize>() + 1;
+            writeln!(generated, " => {size},")?;
+        }
+        writeln!(generated, "        }}")?;
+        writeln!(generated, "    }}")?;
+        writeln!(generated, "}}")?;
     }
 
     '_arg_kind: {
diff --git a/lang/src/backend/hbvm.rs b/lang/src/backend/hbvm.rs
index c3dc80c1..af18293a 100644
--- a/lang/src/backend/hbvm.rs
+++ b/lang/src/backend/hbvm.rs
@@ -697,42 +697,7 @@ fn binary_prelude(to: &mut Vec<u8>) {
 #[derive(Default)]
 pub struct LoggedMem {
     pub mem: hbvm::mem::HostMemory,
-    op_buf: Vec<hbbytecode::Oper>,
-    disp_buf: String,
-    prev_instr: Option<hbbytecode::Instr>,
-}
-
-impl LoggedMem {
-    unsafe fn display_instr<T>(&mut self, instr: hbbytecode::Instr, addr: hbvm::mem::Address) {
-        let novm: *const hbvm::Vm<Self, 0> = core::ptr::null();
-        let offset = core::ptr::addr_of!((*novm).memory) as usize;
-        let regs = unsafe {
-            &*core::ptr::addr_of!(
-                (*(((self as *mut _ as *mut u8).sub(offset)) as *const hbvm::Vm<Self, 0>))
-                    .registers
-            )
-        };
-
-        let mut bytes = core::slice::from_raw_parts(
-            (addr.get() - 1) as *const u8,
-            core::mem::size_of::<T>() + 1,
-        );
-        use core::fmt::Write;
-        hbbytecode::parse_args(&mut bytes, instr, &mut self.op_buf).unwrap();
-        debug_assert!(bytes.is_empty());
-        self.disp_buf.clear();
-        write!(self.disp_buf, "{:<10}", format!("{instr:?}")).unwrap();
-        for (i, op) in self.op_buf.drain(..).enumerate() {
-            if i != 0 {
-                write!(self.disp_buf, ", ").unwrap();
-            }
-            write!(self.disp_buf, "{op:?}").unwrap();
-            if let hbbytecode::Oper::R(r) = op {
-                write!(self.disp_buf, "({})", regs[r as usize].0).unwrap()
-            }
-        }
-        log::trace!("read-typed: {:x}: {}", addr.get(), self.disp_buf);
-    }
+    logger: hbvm::mem::InstrLogger,
 }
 
 impl hbvm::mem::Memory for LoggedMem {
@@ -765,20 +730,14 @@ impl hbvm::mem::Memory for LoggedMem {
     }
 
     unsafe fn prog_read<T: Copy + 'static>(&mut self, addr: hbvm::mem::Address) -> T {
-        if log::log_enabled!(log::Level::Trace) {
-            if core::any::TypeId::of::<u8>() == core::any::TypeId::of::<T>() {
-                if let Some(instr) = self.prev_instr {
-                    self.display_instr::<()>(instr, addr);
-                }
-                self.prev_instr = hbbytecode::Instr::try_from(*(addr.get() as *const u8)).ok();
-            } else {
-                let instr = self.prev_instr.take().unwrap();
-                self.display_instr::<T>(instr, addr);
-            }
-        }
-
         self.mem.prog_read(addr)
     }
+
+    fn log_instr(&mut self, at: hbvm::mem::Address, regs: &[hbvm::value::Value]) {
+        log::trace!("read-typed: {:x}: {}", at.get(), unsafe {
+            self.logger.display_instr(at, regs)
+        });
+    }
 }
 
 struct AsHex<'a>(&'a [u8]);
diff --git a/vm/src/mem/mod.rs b/vm/src/mem/mod.rs
index 535a30b6..c82db5cb 100644
--- a/vm/src/mem/mod.rs
+++ b/vm/src/mem/mod.rs
@@ -4,7 +4,7 @@ pub mod softpaging;
 
 pub(crate) mod addr;
 
-use crate::utils::impl_display;
+use crate::{utils::impl_display, value::Value};
 pub use addr::Address;
 
 /// Load-store memory access
@@ -36,6 +36,48 @@ pub trait Memory {
     /// # Safety
     /// - Data read have to be valid
     unsafe fn prog_read<T: Copy + 'static>(&mut self, addr: Address) -> T;
+
+    /// Log instruction to be executed
+    fn log_instr(&mut self, _at: Address, _regs: &[Value]) {}
+}
+
+#[derive(Default)]
+pub struct InstrLogger {
+    #[cfg(debug_assertions)]
+    op_buf: alloc::vec::Vec<hbbytecode::Oper>,
+    #[cfg(debug_assertions)]
+    disp_buf: alloc::string::String,
+}
+
+impl InstrLogger {
+    /// # Safety
+    /// - `addr` needs to point to a valid instruction
+    #[cfg(debug_assertions)]
+    pub unsafe fn display_instr(&mut self, addr: Address, regs: &[Value]) -> &str {
+        let instr = hbbytecode::Instr::try_from(unsafe { *(addr.get() as *const u8) }).unwrap();
+        let mut bytes =
+            unsafe { core::slice::from_raw_parts(addr.get() as *const u8, instr.size()) };
+        use core::fmt::Write;
+        hbbytecode::parse_args(&mut bytes, instr, &mut self.op_buf).unwrap();
+        debug_assert!(bytes.is_empty());
+        self.disp_buf.clear();
+        write!(self.disp_buf, "{:<10}", alloc::format!("{instr:?}")).unwrap();
+        for (i, op) in self.op_buf.drain(..).enumerate() {
+            if i != 0 {
+                write!(self.disp_buf, ", ").unwrap();
+            }
+            write!(self.disp_buf, "{op:?}").unwrap();
+            if let hbbytecode::Oper::R(r) = op {
+                write!(self.disp_buf, "({})", regs[r as usize].0).unwrap()
+            }
+        }
+        &self.disp_buf
+    }
+
+    #[cfg(not(debug_assertions))]
+    pub unsafe fn display_instr(&mut self, addr: Address, regs: &[Value]) -> &str {
+        ""
+    }
 }
 
 /// Unhandled load access trap
diff --git a/vm/src/vmrun.rs b/vm/src/vmrun.rs
index 2d84218f..ea43974c 100644
--- a/vm/src/vmrun.rs
+++ b/vm/src/vmrun.rs
@@ -55,6 +55,7 @@ where
             // - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU
             //   sorry 8 bit fans, HBVM won't run on your Speccy :(
             unsafe {
+                self.memory.log_instr(self.pc, &self.registers);
                 match self
                     .memory
                     .prog_read::<u8>(self.pc as _)