diff --git a/.cargo/config.toml b/.cargo/config.toml index c5379ea..edd692e 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,3 +5,7 @@ rustc-wrapper = "sccache" [future-incompat-report] frequency = "always" + +[profile.profiling] +inherits = "release" +debug = true diff --git a/.vscode/settings.json b/.vscode/settings.json index 7ade51f..e6f31fd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,4 +8,8 @@ "files.trimTrailingWhitespace": true, "gitea.owner": "LowLevelDevs", "gitea.repo": "damn_simple_architecture", + "[markdown]": { + "editor.formatOnSave": true, + "editor.formatOnPaste": true + } } diff --git a/Cargo.toml b/Cargo.toml index c934ada..f5ab526 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,11 @@ authors = ["zxq5", "nullndvoid"] [profile.dev] codegen-backend = "cranelift" -panic = "abort" # Cranelift does not support stack unwinds. +panic = "abort" # Cranelift does not support stack unwinds. lto = false debug = true -incremental = false # sccache does not support caching incremental crates. +incremental = false # sccache does not support caching incremental crates. + +[profile.release] +debug = true +lto = "fat" diff --git a/assembler/src/assembler/macros.rs b/assembler/src/assembler/macros.rs index 592ef32..e84c85c 100644 --- a/assembler/src/assembler/macros.rs +++ b/assembler/src/assembler/macros.rs @@ -4,7 +4,8 @@ use crate::assembler::model::{Node, Opcode, Symbol, Token}; /// Parse DSA assembly code with optional formatting /// /// # Examples -/// ``` +/// ```rs +/// use assembler::macros::dsa; /// // With formatting: /// let nodes = dsa!(hash, "mov r1, {}", 42)?; /// diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..ad9bd11 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +disallowed-types = ["std::collections::HashMap", "std::collections::HashSet"] diff --git a/emulator/Cargo.toml b/emulator/Cargo.toml index 95df15a..911b66e 100644 --- a/emulator/Cargo.toml +++ b/emulator/Cargo.toml @@ -20,10 +20,11 @@ compiler = { path = "../compiler" } dsa_editor = { path = "../dsa_editor" } egui = "0.31.1" dirs = "6.0.0" -discord-presence = { version = "1.6.0", optional = true } +discord-presence = { version = "2.0.0", optional = true } toml = { version = "0.8.23", optional = true } serde = { version = "1.0.219", features = ["derive"], optional = true } egui_file = "0.22.1" +rustc-hash = "2.1.1" [features] default = ["config"] diff --git a/emulator/src/emulator/system/cache.rs b/emulator/src/emulator/system/cache.rs new file mode 100644 index 0000000..1213d78 --- /dev/null +++ b/emulator/src/emulator/system/cache.rs @@ -0,0 +1,53 @@ +use common::prelude::Instruction; +use rustc_hash::FxHashMap; + +#[derive(Debug)] +pub struct Cache { + addr: u32, + instruction_block: Option<[u8; 256]>, + instruction_lookup: FxHashMap, +} + +impl Cache { + #[must_use] + pub fn new() -> Self { + Self { + addr: 0, + instruction_block: None, + instruction_lookup: FxHashMap::default(), + } + } + + pub fn lookup_value(&mut self, addr: u32) -> Option { + if addr < self.addr || addr >= self.addr + 256 || self.instruction_block.is_none() + { + return None; + } + + Some(u32::from_be_bytes( + self.instruction_block.expect("this should not be none!") + [(addr - self.addr) as usize..(addr - self.addr + 4) as usize] + .try_into() + .expect("Failed to convert bytes to u32"), + )) + } + + pub const fn set(&mut self, addr: u32, block: &[u8; 256]) { + self.addr = addr - addr % 256; + self.instruction_block = Some(*block); + } + + pub fn lookup_instruction(&mut self, instruction: u32) -> Option { + self.instruction_lookup.get(&instruction).copied() + } + + pub fn insert(&mut self, value: u32, instruction: Instruction) { + self.instruction_lookup.insert(value, instruction); + } +} + +impl Default for Cache { + fn default() -> Self { + Self::new() + } +} diff --git a/emulator/src/emulator/system/emulator.rs b/emulator/src/emulator/system/emulator.rs index f6e5c0c..cb87d72 100644 --- a/emulator/src/emulator/system/emulator.rs +++ b/emulator/src/emulator/system/emulator.rs @@ -25,9 +25,11 @@ pub fn run_emulator( let mut running = Running::Paused; let mut step = 0; let mut addr; - let mut history = Vec::<(u32, Instruction)>::new(); + let mut history = Vec::<(u32, u32)>::with_capacity(32768); let size = 256; + let record_history = true; + state_tx .send(StateUpdate::Running(Running::Paused)) .expect("Failed to send initial state!"); @@ -36,7 +38,9 @@ pub fn run_emulator( let mut update = false; loop { - let cmd = if running == Running::Running || step > 0 { + let cmd = if step > 0 { + None + } else if running == Running::Running && step == 0 { match cmd_rx.try_recv() { Ok(cmd) => Some(cmd), Err(mpsc::TryRecvError::Empty) => { @@ -52,10 +56,15 @@ pub fn run_emulator( } }; + if running == Running::Running && step == 0 { + step = 32768; + } + if let Some(cmd) = cmd { match cmd { Command::Start => { running = Running::Running; + step = 32768; // Update RPC with current state. TODO: Make this only occur on state // changes. @@ -71,9 +80,11 @@ pub fn run_emulator( } Command::Stop => { running = Running::Paused; + step = 0; } Command::Reset(x) => { running = Running::Paused; + step = 0; match x { 0 => { @@ -95,20 +106,12 @@ pub fn run_emulator( } Command::Step(x) => { step = x; + running = Running::Paused; } Command::Write(offset, data) => { update = true; - processor - .memory - .write_range(offset, data) - .unwrap_or_else(|_| { - report_err( - state_tx, - "Failed to write memory range!", - &mut processor, - ); - }); + processor.memory.write_range(offset, data); } Command::Interrupt(_interrupt) => { update = true; @@ -118,14 +121,7 @@ pub fn run_emulator( Command::MemRequest(new, size) if update => { addr = new; let _ = state_tx.send(StateUpdate::MemoryView( - processor.memory.read_range(addr, size).unwrap_or_else(|_| { - report_err( - state_tx, - "Failed to read memory range!", - &mut processor, - ); - Vec::new() - }), + processor.memory.read_range(addr, size), )); } Command::DisplayRequest if update => { @@ -163,50 +159,19 @@ pub fn run_emulator( let _ = state_tx.send(StateUpdate::Instructions(instruction_count)); } Command::WriteBlock(addr, block) => { - processor - .memory - .write_range(addr, block.to_vec()) - .unwrap_or_else(|_| { - report_err( - state_tx, - "Failed to write memory block!", - &mut processor, - ); - }); + processor.memory.write_range(addr, block.to_vec()); } _ => {} } } - if step > 0 { - step -= 1; - update = true; - running = Running::Paused; - - // Execute one cycle. - match processor.cycle() { - Ok((addr, instruction)) => { - history.push((addr, instruction)); - } - Err(why) => { - let pcx = processor - .get(Register::Pcx) - .expect("SPR should never be invalid"); - report_err( - state_tx, - &format!( - "Could not decode instruction at {pcx:x}. Reason: {why}" - ), - &mut processor, - ); - } - } - instruction_count += 1; - continue; + if running == Running::Running { + step += 1; } - if running == Running::Running { + if step > 0 { + step -= 1; update = true; // Execute one cycle. @@ -227,9 +192,18 @@ pub fn run_emulator( } }; - history.push(instruction); - if matches!(instruction.1, Instruction::Halt) { + if record_history { + history.push(( + instruction.0, + processor + .get(Register::Cir) + .expect("CIR should never be invalid"), + )); + } + + if matches!(instruction, (_, Instruction::Halt)) { running = Running::Halted; + step = 0; } instruction_count += 1; diff --git a/emulator/src/emulator/system/memory.rs b/emulator/src/emulator/system/memory.rs index b6a1425..e195b6b 100644 --- a/emulator/src/emulator/system/memory.rs +++ b/emulator/src/emulator/system/memory.rs @@ -1,52 +1,42 @@ -use std::collections::HashMap; +use rustc_hash::FxHashMap; use crate::emulator::system::model::ProcessorError; pub trait MemoryUnit: Send + Sync { fn reset(&mut self); - fn read_byte(&mut self, addr: u32) -> Result; - fn write_byte(&mut self, addr: u32, value: u8) -> Result<(), ProcessorError>; + fn read_byte(&mut self, addr: u32) -> u8; + fn write_byte(&mut self, addr: u32, value: u8); fn read_word(&mut self, addr: u32) -> Result; fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError>; - fn read_range(&mut self, addr: u32, size: u32) -> Result, ProcessorError> { + fn read_range(&mut self, addr: u32, size: u32) -> Vec { let mut data = Vec::with_capacity(size as usize); for i in 0..size { - data.push(self.read_byte(addr + i)?); + data.push(self.read_byte(addr + i)); } - Ok(data) + data } - fn write_range(&mut self, addr: u32, value: Vec) -> Result<(), ProcessorError> { + fn write_range(&mut self, addr: u32, value: Vec) { for (i, byte) in value.into_iter().enumerate() { - self.write_byte(addr + i as u32, byte)?; + self.write_byte(addr + i as u32, byte); } - Ok(()) } - fn read_block(&mut self, addr: u32) -> Result<[u8; 256], ProcessorError> { - let mut data = [0; 256]; - for (i, byte) in data.iter_mut().enumerate() { - *byte = self.read_byte(addr + i as u32)?; - } - Ok(data) - } + fn read_block(&mut self, addr: u32) -> &[u8; 256]; - fn write_block(&mut self, addr: u32, data: [u8; 256]) -> Result<(), ProcessorError> { + fn write_block(&mut self, addr: u32, data: &[u8; 256]) { for (i, byte) in data.iter().enumerate() { - self.write_byte(addr + i as u32, *byte)?; + self.write_byte(addr + i as u32, *byte); } - Ok(()) } } pub struct MainStore { - pub data: HashMap, + pub data: FxHashMap, } -pub struct Block { - data: [u8; 256], -} +pub type Block = [u8; 256]; impl Default for MainStore { fn default() -> Self { @@ -58,79 +48,73 @@ impl MainStore { #[must_use] pub fn new() -> Self { Self { - data: HashMap::new(), + data: FxHashMap::default(), } } + #[inline] const fn segment_addr(addr: u32) -> (u32, u8) { (addr / 256, (addr % 256) as u8) } + #[inline] fn mut_block(&mut self, addr: u32) -> &mut Block { - self.data - .entry(addr) - .or_insert_with(|| Block { data: [0; 256] }); - - self.data.get_mut(&addr).map_or_else( - || panic!("Could not fetch block with address {addr:x?}"), - |block| block, - ) + self.data.entry(addr).or_insert([0; 256]) } + #[inline] fn block(&mut self, addr: u32) -> &Block { - self.data - .entry(addr) - .or_insert_with(|| Block { data: [0; 256] }); - - self.data.get(&addr).map_or_else( - || panic!("Could not fetch block with address {addr:x?}"), - |block| block, - ) + self.data.entry(addr).or_insert([0; 256]) } } impl MemoryUnit for MainStore { + #[inline] fn reset(&mut self) { self.data.clear(); } - fn read_byte(&mut self, addr: u32) -> Result { + #[inline] + fn read_byte(&mut self, addr: u32) -> u8 { let (block_addr, offset) = Self::segment_addr(addr); let block = self.block(block_addr); - Ok(block.data[offset as usize]) + block[offset as usize] } + #[inline] fn read_word(&mut self, addr: u32) -> Result { if addr % 4 != 0 { return Err(ProcessorError::BadMemoryAccess(addr)); } let (block_addr, offset) = Self::segment_addr(addr); - let block = self.mut_block(block_addr); - let mut bytes = [0; 4]; - bytes[0] = block.data[offset as usize]; - bytes[1] = block.data[(offset + 1) as usize]; - bytes[2] = block.data[(offset + 2) as usize]; - bytes[3] = block.data[(offset + 3) as usize]; - Ok(u32::from_be_bytes(bytes)) + let offset = offset as usize; + let block = self.block(block_addr); + Ok(u32::from_be_bytes( + block[offset..=offset + 3] + .try_into() + .expect("Failed to read word!"), + )) } - fn read_range(&mut self, addr: u32, size: u32) -> Result, ProcessorError> { + #[inline] + fn read_range(&mut self, addr: u32, size: u32) -> Vec { let mut data = Vec::with_capacity(size as usize); for i in 0..size { - data.push(self.read_byte(addr + i)?); + data.push(self.read_byte(addr + i)); } - Ok(data) + data } - fn write_byte(&mut self, addr: u32, value: u8) -> Result<(), ProcessorError> { + #[inline] + fn write_byte(&mut self, addr: u32, value: u8) { let (block_addr, offset) = Self::segment_addr(addr); let block = self.mut_block(block_addr); - block.data[offset as usize] = value; - Ok(()) + block[offset as usize] = value; } + #[inline] fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError> { if addr % 4 != 0 { return Err(ProcessorError::BadMemoryAccess(addr)); @@ -138,33 +122,36 @@ impl MemoryUnit for MainStore { let (block_addr, offset) = Self::segment_addr(addr); let block = self.mut_block(block_addr); - block.data[offset as usize] = (value >> 24) as u8; - block.data[(offset + 1) as usize] = (value >> 16) as u8; - block.data[(offset + 2) as usize] = (value >> 8) as u8; - block.data[(offset + 3) as usize] = value as u8; + block[offset as usize..=(offset + 3) as usize] + .copy_from_slice(&value.to_be_bytes()); Ok(()) } - fn write_range(&mut self, addr: u32, value: Vec) -> Result<(), ProcessorError> { - for (i, byte) in value.into_iter().enumerate() { - let (block_addr, offset) = Self::segment_addr(addr + i as u32); - let block = self.mut_block(block_addr); - block.data[offset as usize] = byte; + #[inline] + fn write_range(&mut self, addr: u32, value: Vec) { + let mut current_block_addr = addr / 256; + let mut current_block = self.mut_block(current_block_addr); + let mut offset = addr % 256; + for byte in value { + current_block[offset as usize] = byte; + offset += 1; + if offset >= 256 { + offset = 0; + current_block_addr += 1; + current_block = self.mut_block(current_block_addr); + } } - - Ok(()) } - fn read_block(&mut self, addr: u32) -> Result<[u8; 256], ProcessorError> { + #[inline] + fn read_block(&mut self, addr: u32) -> &[u8; 256] { let (block_addr, _) = Self::segment_addr(addr); - let block = self.block(block_addr); - Ok(block.data) + self.block(block_addr) } - fn write_block(&mut self, addr: u32, data: [u8; 256]) -> Result<(), ProcessorError> { + #[inline] + fn write_block(&mut self, addr: u32, data: &[u8; 256]) { let (block_addr, _) = Self::segment_addr(addr); - let block = self.mut_block(block_addr); - block.data = data; - Ok(()) + let _ = self.data.insert(block_addr, *data); } } diff --git a/emulator/src/emulator/system/mod.rs b/emulator/src/emulator/system/mod.rs index e483168..6356927 100644 --- a/emulator/src/emulator/system/mod.rs +++ b/emulator/src/emulator/system/mod.rs @@ -1,3 +1,4 @@ +pub mod cache; pub mod emulator; pub mod memory; pub mod model; diff --git a/emulator/src/emulator/system/model.rs b/emulator/src/emulator/system/model.rs index 5df1891..3338848 100644 --- a/emulator/src/emulator/system/model.rs +++ b/emulator/src/emulator/system/model.rs @@ -78,7 +78,7 @@ pub struct State { pub error_log: Vec, - pub instruction_history: Vec<(u32, Instruction)>, + pub instruction_history: Vec<(u32, u32)>, } impl State { @@ -154,7 +154,7 @@ pub enum StateUpdate { MemoryView(Vec), DisplayView(Vec), Error(String), - InstructionHistory(Vec<(u32, Instruction)>), + InstructionHistory(Vec<(u32, u32)>), } #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] diff --git a/emulator/src/emulator/system/processor/mod.rs b/emulator/src/emulator/system/processor/mod.rs index fb50926..d9f48f3 100644 --- a/emulator/src/emulator/system/processor/mod.rs +++ b/emulator/src/emulator/system/processor/mod.rs @@ -4,6 +4,7 @@ use std::{ }; use crate::emulator::system::{ + cache::Cache, memory::MemoryUnit, model::{IODevice, ProcessorError, RegFile}, }; @@ -17,6 +18,7 @@ pub struct Processor { pub io_devices: Vec>, pub void: u32, + pub cache: Cache, } fn log(message: &str) { @@ -32,6 +34,7 @@ impl Processor { halted: false, io_devices, void: 0, + cache: Cache::new(), } } @@ -51,21 +54,35 @@ impl Processor { // Get value from PCX. let addr = self.fetch()?; // Increment PCX. - self.advance(); + self.advance()?; // Set MAR to the previous value of PCX. *self.reg(Register::Mar)? = addr; - let val = self.memory.read_word(addr)?; + + let encoded = if let Some(val) = self.cache.lookup_value(addr) { + val + } else { + let block = self.memory.read_block(addr); + self.cache.set(addr, block); + self.cache + .lookup_value(addr) + .expect("Failed to lookup value!") + }; // Set CIR to the value of RAM[MAR]. - *self.reg(Register::Mar)? = val; + *self.reg(Register::Cir)? = encoded; - // Decode and execute the instruction. - let instruction = Instruction::decode(val) - .map_err(|_| ProcessorError::InvalidInstruction(val))?; + let decoded = if let Some(val) = self.cache.lookup_instruction(addr) { + val + } else { + let decoded = Instruction::decode(encoded) + .map_err(|_| ProcessorError::InvalidInstruction(encoded))?; + self.cache.insert(addr, decoded); + decoded + }; - instruction.execute(self)?; - Ok((addr, instruction)) + decoded.execute(self)?; + Ok((addr, decoded)) } const fn fetch(&self) -> Result { @@ -84,7 +101,7 @@ impl Processor { } pub fn display(&mut self) -> Result, ProcessorError> { - self.memory.read_range(0x20000, 2000) + Ok(self.memory.read_range(0x20000, 2000)) } pub fn cmp(&mut self, a: u32, b: u32) { @@ -163,10 +180,10 @@ impl Processor { let addr = self.get(Register::Spr)?; let size = n * 4; // returns the stack - self.memory.read_range( + Ok(self.memory.read_range( max(addr, 0), // ensures that we cannot read from a negative address min(size, addr), // ensures we don't read above the top of the stack - ) + )) } } @@ -209,7 +226,7 @@ impl Executable for Instruction { Self::LoadByte(a) => { *cpu.reg(a.r2)? = u32::from( cpu.memory - .read_byte(cpu.get(a.r1)? + u32::from(a.immediate))?, + .read_byte(cpu.get(a.r1)? + u32::from(a.immediate)), ); } @@ -218,7 +235,7 @@ impl Executable for Instruction { Self::LoadByteSigned(a) => { *cpu.reg(a.r2)? = sign_extend(u32::from( cpu.memory - .read_byte(cpu.get(a.r1)? + u32::from(a.immediate))?, + .read_byte(cpu.get(a.r1)? + u32::from(a.immediate)), )); } @@ -257,7 +274,7 @@ impl Executable for Instruction { cpu.memory.write_byte( cpu.get(a.r2)? + u32::from(a.immediate), cpu.get(a.r1)? as u8, - )?; + ); } // Stores a half-word from SrcReg in memory address (base + offset) The @@ -266,9 +283,9 @@ impl Executable for Instruction { // split the value into bytes and then write two bytes let bytes = (cpu.get(a.r1)? as u16).to_le_bytes(); cpu.memory - .write_byte(cpu.get(a.r2)? + u32::from(a.immediate), bytes[0])?; + .write_byte(cpu.get(a.r2)? + u32::from(a.immediate), bytes[0]); cpu.memory - .write_byte(cpu.get(a.r2)? + u32::from(a.immediate) + 1, bytes[1])?; + .write_byte(cpu.get(a.r2)? + u32::from(a.immediate) + 1, bytes[1]); } // Stores a word from SrcReg in memory address (base + offset) The effective diff --git a/emulator/src/emulator/system/processor/tests.rs b/emulator/src/emulator/system/processor/tests.rs index 6381e3a..c9ca424 100644 --- a/emulator/src/emulator/system/processor/tests.rs +++ b/emulator/src/emulator/system/processor/tests.rs @@ -81,9 +81,7 @@ fn test_mov_signed_instruction() { fn test_load_byte_instruction() { let mut cpu = create_test_processor(); let addr = 0x100; - cpu.memory - .write_byte(addr, 0xAB) - .expect("Failed to write byte to memory"); + cpu.memory.write_byte(addr, 0xAB); *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr - 4; let load_byte_instr = Instruction::LoadByte(ITypeArgs::new( @@ -105,9 +103,7 @@ fn test_load_byte_instruction() { fn test_load_byte_signed_instruction() { let mut cpu = create_test_processor(); let addr = 0x100; - cpu.memory - .write_byte(addr, 0xFF) - .expect("Failed to write byte to memory"); + cpu.memory.write_byte(addr, 0xFF); *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr; let load_byte_signed_instr = Instruction::LoadByteSigned(ITypeArgs::new( @@ -189,7 +185,7 @@ fn test_store_byte_instruction() { store_byte_instr.execute(&mut cpu).expect( "Emulator was slain by losing the game while attempting to execute instruction", ); - assert_eq!(cpu.memory.read_byte(addr).expect("Emulator was slain by losing the game while attempting to execute instruction"), 0xAB); + assert_eq!(cpu.memory.read_byte(addr), 0xAB); } #[test] diff --git a/emulator/src/emulator/ui/history.rs b/emulator/src/emulator/ui/history.rs index 64be793..c24a1e4 100644 --- a/emulator/src/emulator/ui/history.rs +++ b/emulator/src/emulator/ui/history.rs @@ -1,3 +1,4 @@ +use common::prelude::Instruction; use egui::{Context, Ui}; use crate::emulator::{ @@ -57,8 +58,11 @@ impl Component for History { .color(egui::Color32::from_rgb(255, 200, 200)), ); + let decoded = Instruction::decode(instruction.1) + .unwrap_or(Instruction::Nop); + ui.label( - egui::RichText::new(instruction.1.to_string()) + egui::RichText::new(decoded.to_string()) .font(egui::FontId::monospace(12.0)) .color(egui::Color32::from_rgb(200, 255, 200)), ); diff --git a/resources/dsa/main.dsa b/resources/dsa/main.dsa new file mode 100644 index 0000000..2499bf3 --- /dev/null +++ b/resources/dsa/main.dsa @@ -0,0 +1,12 @@ +// program to just test compute power + +dw large_num: 0x333333 // 333,333 instructions +start: + ldw large_num, rg0 + +// run approx 1m instructions +loop: + dec rg0 + cmp rg0, zero + jgt loop + hlt