diff --git a/dsa/assembler/src/assembler/codegen.rs b/dsa/assembler/src/assembler/codegen.rs index f08dcb5..c481db4 100644 --- a/dsa/assembler/src/assembler/codegen.rs +++ b/dsa/assembler/src/assembler/codegen.rs @@ -1,22 +1,18 @@ use common::prelude::*; - +use common::process::ProcessAgent; use crate::assembler::Token; use crate::assembler::model::{Node, Opcode}; use crate::{assembler::AssembleError, expect_token}; -fn log(message: &str) { - println!("\x1b[32mINFO:\x1b[0m {message}"); -} - -pub fn codegen(nodes: Vec) -> Result, AssembleError> { +pub fn codegen(nodes: Vec, ctx: &ProcessAgent) -> Result, AssembleError> { let mut instructions = vec![]; for node in nodes { - println!("{:?}", node); + ctx.log(format!("{}", node)); instructions.push(build_instruction(&node)?); } - log("Assembly Successful ✅"); + ctx.log("Assembly Successful ✅"); Ok(instructions) } diff --git a/dsa/assembler/src/assembler/lexer.rs b/dsa/assembler/src/assembler/lexer.rs index 1a1947b..c24f656 100644 --- a/dsa/assembler/src/assembler/lexer.rs +++ b/dsa/assembler/src/assembler/lexer.rs @@ -65,8 +65,6 @@ pub fn lexer(mut program: String, module: u64) -> Result, AssembleErr } } - // println!("{:#?}", tokens); - Ok(tokens) } pub fn parse_register(token: &str) -> Result, AssembleError> { diff --git a/dsa/assembler/src/assembler/mod.rs b/dsa/assembler/src/assembler/mod.rs index 36c04f6..b1709a7 100644 --- a/dsa/assembler/src/assembler/mod.rs +++ b/dsa/assembler/src/assembler/mod.rs @@ -50,15 +50,6 @@ pub struct Assembler { is_running: bool, } -impl TraceableProcess for Assembler { - type Output = DseExecutable; - type Error = AssembleError; - - fn run(mut self: Box, ctx: ProcessAgent) -> Result { - assemble(&self.src_path) - } -} - impl Assembler { #[must_use] pub fn new(src_path: impl Into) -> Self { @@ -70,137 +61,37 @@ impl Assembler { is_running: false, } } +} - /// Start the compilation process in a separate thread - pub fn start(&mut self) { - if self.is_running { - return; - } +impl TraceableProcess for Assembler { + type Output = DseExecutable; + type Error = AssembleError; - let src = self.src_path.clone(); - let tx = self.result_tx.clone(); - - thread::spawn(move || match assemble(&src) { - Ok(exe) => { - let mut buffer = Cursor::new(Vec::new()); - match exe.write(&mut buffer) { - Ok(()) => { - tx.send(Ok(buffer.into_inner())) - .expect("Failed to send compilation result from worker thread"); - } - Err(e) => { - tx.send(Err(AssembleError::Generic)) - .expect("..."); - } - } - } - Err(err) => { /* unchanged */ } - }); - - self.is_running = true; - } - - /// Polls the result channel to check if a compilation result is ready. - /// - /// This method attempts a non-blocking read (`try_recv`) from the result receiver. - /// It returns `Some(Ok(Vec))` if the compilation successfully finished and - /// the binary output is available, or `None` if no result was received yet. - /// - /// If it receives an error via `mpsc::TryRecvError::Disconnected`, it indicates that - /// the worker thread terminated unexpectedly, returning a specific error string. - pub fn poll(&mut self) -> Option, String>> { - if !self.is_running { - return None; - } - - match self - .result_rx - .as_ref() - .expect("result_rx should be Some while compilation is running") - .try_recv() - { - Ok(result) => { - self.is_running = false; - Some(result.map_err(|e| e.to_string())) - } - Err(mpsc::TryRecvError::Empty) => None, - Err(mpsc::TryRecvError::Disconnected) => { - self.is_running = false; - Some(Err(String::from( - "Compilation terminated before a result was returned", - ))) - } - } - } - - /// Block until compilation is complete and return the result - pub fn output(&mut self) -> Result, String> { - if let Ok(result) = self - .result_rx - .take() - .expect("result_rx should be Some while waiting for compilation result") - .recv() - { - self.is_running = false; - result.map_err(|e| e.to_string()) - } else { - self.is_running = false; - Err(String::from( - "Compilation terminated before a result was returned", - )) - } + fn run(mut self: Box, ctx: ProcessAgent) -> Result { + assemble(&self.src_path, &ctx) } } -// fn assemble(src: &Path) -> Result, AssembleError> { -// let mut modules = HashSet::new(); -// let mut program = Program::new(); -// -// let hash = quick_hash(src); -// -// if modules.contains(&hash) { -// return Ok(vec![]); -// } -// -// prepare_dependency(src, &mut modules, &mut program)?; -// -// let mut nodes = program.nodes.clone(); -// -// println!("PRE LINK"); -// for node in &nodes { -// println!("{:?}", node); -// } -// -// create_sections(&mut nodes)?; -// resolve_symbols(&mut nodes)?; -// -// println!("Generating assembly output..."); -// -// let instructions = codegen(nodes)?; -// -// println!("Compilation Successful"); -// Ok(instructions) -// } - -fn assemble(src: &Path) -> Result { +fn assemble(src: &Path, ctx: &ProcessAgent) -> Result { let mut modules = HashSet::new(); let mut program = Program::new(); - prepare_dependency(src, &mut modules, &mut program)?; + prepare_dependency(src, &mut modules, &mut program, &ctx)?; let nodes = program.nodes.clone(); - println!("PRE LINK"); - for node in &nodes { - println!("{:?}", node); - } + // for node in &nodes { + // ctx.log(format!("{:?}", node)); + // } let (data_nodes, mut text_nodes) = split_sections(nodes); let symbol_table = resolve_symbols(&data_nodes, &mut text_nodes)?; let text_len = text_nodes.len() as u32; - println!("Generating assembly output..."); - let instructions = codegen(text_nodes)?; + ctx.log("Generating assembly output..."); + + // println!("Generating assembly output..."); + let instructions = codegen(text_nodes, ctx)?; let text: Vec = instructions .iter() .map(|i| i.0).collect(); @@ -237,7 +128,7 @@ fn assemble(src: &Path) -> Result { module: Module::Resolved(0), }))?; - println!("Compilation Successful"); + ctx.log("Compilation Successful"); Ok(DseExecutable::new( symbols.as_slice(), @@ -264,6 +155,7 @@ fn prepare_dependency( path: &Path, modules: &mut HashSet, program: &mut Program, + ctx: &ProcessAgent, ) -> Result<(), AssembleError> { let filename = path .file_name() @@ -271,21 +163,21 @@ fn prepare_dependency( .expect("Failed to get file name from path"); if let Ok(path) = path.canonicalize() { - println!("{:20} {:20} [{}]", "Building", filename, path.display()); + ctx.log(format!("{:20} {:20} [{}]", "Building", filename, path.display())); } let src = fs::read_to_string(path).map_err(|_| AssembleError::InvalidFile(path.to_path_buf()))?; let file_hash = quick_hash(path); - println!("{:20} {:20}", "Tokenising", filename); + ctx.log(format!("{:20} {:20}", "Tokenising", filename)); let tokens = lexer::lexer(src, file_hash)?; // let tokens = Lexer::new(src, file_hash).run()?; - println!("{:20} {:20}", "Parsing", filename); + ctx.log(format!("{:20} {:20}", "Parsing", filename)); let parsed = Parser::parse_nodes(tokens)?; - println!("{:20} {:20}", "Resolving Deps", filename); + ctx.log(format!("{:20} {:20}", "Resolving Deps", filename)); // Get the parent directory of the source file to use as the base directory let base_dir = path .parent() @@ -295,32 +187,28 @@ fn prepare_dependency( let deps = Parser::get_dependencies(&nodes, path)?; - println!("{:20} {:20}", "Expanding Pseudo-ops", filename); + ctx.log(format!("{:20} {:20}", "Expanding Pseudo-ops", filename)); // add a section instruction nodes.insert( 0, node!(None, Opcode::Segment, Token::Immediate(file_hash as u32)), ); - - // for n in &nodes { - // println!("{n}"); - // } - + program.add_module(nodes); for dep in deps { - println!( + ctx.log(format!( "{:20} {:20}", "Including", dep.file_name() .and_then(|f| f.to_str()) .expect("Dependency path has no file name or is not valid UTF-8") - ); + )); let dep_hash = quick_hash(&dep); if modules.insert(dep_hash) { - prepare_dependency(dep.as_path(), modules, program)?; + prepare_dependency(dep.as_path(), modules, program, &ctx)?; } } diff --git a/dsa/assembler/src/assembler/resolver.rs b/dsa/assembler/src/assembler/resolver.rs deleted file mode 100644 index 2487d1a..0000000 --- a/dsa/assembler/src/assembler/resolver.rs +++ /dev/null @@ -1,157 +0,0 @@ -use std::{ - collections::HashMap, - fs::canonicalize, - path::{Path, PathBuf}, -}; - -use common::prelude::Register; - -use crate::assembler::model::{Module, Node, Opcode, Symbol, Token}; -use crate::assembler::quick_hash; -use crate::{assembler::AssembleError, node}; - -pub fn resolve_symbols(nodes: &mut [Node]) -> Result<(), AssembleError> { - let symbol_table = generate_symbol_table(nodes); - - for node in nodes.iter_mut() { - match node.opcode() { - Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc | Opcode::Lli | Opcode::Lui => { - if let Token::Symbol(symbol) = node - .arg(0) - .expect("Expected argument 0 for jump-like opcode") - { - if let Some(address) = symbol_table.get(&symbol) { - node.tokens[0] = Token::Immediate(*address); - } else { - return Err(AssembleError::UndefinedSymbol(symbol)); - } - } - } - Opcode::Jez | Opcode::Jnz => { - if let Token::Symbol(symbol) = node - .arg(1) - .expect("Expected argument 0 for jump-like opcode") - { - if let Some(address) = symbol_table.get(&symbol) { - node.tokens[1] = Token::Immediate(*address); - } else { - return Err(AssembleError::UndefinedSymbol(symbol)); - } - } - } - _ => (), - } - } - - Ok(()) -} - -fn generate_symbol_table(nodes: &[Node]) -> HashMap { - let mut table = HashMap::new(); - - for (i, node) in nodes.iter().enumerate() { - if let Some(symbol) = node.label() { - table.insert(symbol, 4 * i as u32); - } - } - - table -} - -pub fn resolve_dependencies( - mut nodes: Vec, - base_dir: &Path, -) -> Result, AssembleError> { - // First we get a list of imports. - let mut dependencies = Vec::new(); - for node in &nodes { - if node.opcode() == Opcode::Include { - // we want the path, and the name - let name = if let Token::Symbol(name) = node - .arg(0) - .expect("Expected argument #0 for Include directive.") - { - name.name.clone() - } else { - unreachable!() - }; //node.2.get(0).unwrap() - - let Ok(Token::StringLit(path)) = node.arg(1) else { - unreachable!() - }; - - let full_path = base_dir.join(path); - let canonical_path = full_path - .canonicalize() - .map_err(|_| AssembleError::InvalidFile(full_path.clone()))?; - - let hash = quick_hash(&canonical_path); - - dependencies.push((name, hash)); - } - } - - let mut changes = Vec::<(u32, u32, Symbol)>::new(); - // now we resolve the symbols on all the nodes - // we need to check all operands for unresolved signals - for (i, node) in nodes.clone().iter().enumerate() { - let Node { - tokens: operands, .. - } = node; - for (j, token) in operands.iter().enumerate() { - if let Token::Symbol(symbol) = token { - for d in &dependencies { - if let Module::Unresolved(name) = symbol.module.clone() { - if name != d.0 { - continue; - } - - let symbol = Symbol { - name: symbol.name.clone(), - module: Module::Resolved(d.1), - }; - changes.push((i as u32, j as u32, symbol)); - } - } - } - } - } - - for (i, j, symbol) in changes { - nodes[i as usize].tokens[j as usize] = Token::Symbol(symbol); - } - - Ok(nodes) -} - -pub fn create_sections(nodes: &mut Vec) -> Result<(), AssembleError> { - let mut res = Vec::::with_capacity(nodes.len()); - - res.push(node!(None, Opcode::Segment, Token::Immediate(0))); - - for n in nodes.iter() { - if n.opcode() == Opcode::Data { - res.push(n.clone()); - } - } - - let start = res.len() + 2; - res.insert( - 0, - node!( - None, - Opcode::Jmp, - Token::Immediate(start as u32 * 4), - Token::Register(Register::Zero) - ), - ); - for n in nodes.iter() { - if !matches!(n.opcode(), Opcode::Data | Opcode::Include) { - res.push(n.clone()); - } - } - - *nodes = res; - - Ok(()) -} diff --git a/dsa/assembler/src/main.rs b/dsa/assembler/src/main.rs index f48095e..ce3c589 100644 --- a/dsa/assembler/src/main.rs +++ b/dsa/assembler/src/main.rs @@ -1,6 +1,9 @@ use assembler::prelude::*; use clap::{Parser, arg}; use std::{fs, path::PathBuf}; +use std::io::Cursor; +use assembler::assembler::AssembleError; +use common::process::TraceableProcess; #[derive(Parser, Debug, Clone)] #[command(version, about, long_about = None)] @@ -15,11 +18,12 @@ fn main() { // Parse command line arguments let args = Args::parse(); - let mut engine = Assembler::new(PathBuf::from(args.input_path)); - engine.start(); - let result = engine.output().expect("assembler failed."); + let assembler = Assembler::new(PathBuf::from(args.input_path)); + let result = assembler.run_sync().expect("assembler failed."); + let mut buffer = Cursor::new(Vec::new()); + result.write(&mut buffer).unwrap(); - if let Err(e) = fs::write(args.output_path, result) { + if let Err(e) = fs::write(args.output_path, buffer.into_inner()) { eprintln!("Failed to write to output file: {e}"); std::process::exit(1); } diff --git a/dsa/common/src/process.rs b/dsa/common/src/process.rs index 7dee177..24af575 100644 --- a/dsa/common/src/process.rs +++ b/dsa/common/src/process.rs @@ -1,23 +1,53 @@ use std::fmt::Debug; -use std::sync::mpsc; +use std::sync::{mpsc, Arc}; +use crate::process::AgentSink::{Callback, Channel}; + -#[derive(Debug, Clone)] pub struct ProcessAgent { - tx: mpsc::Sender, + sink: Arc, } impl ProcessAgent { pub fn new(tx: mpsc::Sender) -> Self { - Self { tx } + Self { sink: Arc::new(Channel(tx)) } + } + + fn new_sync(f: impl Fn(&str) + Send + Sync + 'static) -> Self { + Self { sink: Arc::new(Callback(Box::new(f))) } } pub fn log(&self, msg: impl Into) { - let _ = self.tx.send(msg.into()); + let msg = msg.into(); + match &*self.sink { + Channel(tx) => { let _ = tx.send(msg); } + Callback(f) => f(&msg), + } } } +enum AgentSink { + Channel(mpsc::Sender), + Callback(Box) +} + pub trait TraceableProcess: Debug + Send + 'static { type Output: Send + 'static; type Error: std::error::Error + Send + 'static; + + fn label(&self) -> String { + format!("{self:?}") + } + fn run(self: Box, ctx: ProcessAgent) -> Result; + + /// + fn run_sync(self) -> Result + where + Self: Sized + { + Self::run( + Box::new(self), + ProcessAgent::new_sync(|line| println!("{line}") ) + ) + } } \ No newline at end of file diff --git a/dsa/emulator/emulator_core/src/lib.rs b/dsa/emulator/emulator_core/src/lib.rs index afcf8a4..9e4e098 100644 --- a/dsa/emulator/emulator_core/src/lib.rs +++ b/dsa/emulator/emulator_core/src/lib.rs @@ -30,6 +30,8 @@ pub trait DsaImplementation: Send { /// integration testing. fn boot(&mut self) -> Result<(), DsaError>; + fn reset(&mut self); + /// main run method. the main-loop of the program execution fn run(&mut self) -> Result<(), DsaError>; @@ -99,9 +101,10 @@ impl EmulatorHandle { } pub fn reset(&mut self) { - self.state.reseting.store(true, Ordering::Relaxed); + self.state.resetting.store(true, Ordering::Relaxed); self.memory.clear(); - self.state.reseting.store(false, Ordering::Relaxed); + self.state.proc.store(Arc::new(Default::default())); + self.state.resetting.store(false, Ordering::Relaxed); } pub fn get_register(&self, rg: Register) -> u32 { diff --git a/dsa/emulator/emulator_core/src/processor/dsa_core.rs b/dsa/emulator/emulator_core/src/processor/dsa_core.rs index e3e783f..cddf2c8 100644 --- a/dsa/emulator/emulator_core/src/processor/dsa_core.rs +++ b/dsa/emulator/emulator_core/src/processor/dsa_core.rs @@ -28,6 +28,18 @@ pub struct DsaProcessor { } impl DsaProcessor { + + pub fn reset(&mut self) { + self.internal_state = ProcessorSnapshot::default(); + self.mmu = MMU::new(); + + // deallocates the old memory + self.mainstore.clear(); + self.mmio_region = None; + self.memory_map = None; + self.__mmap_configured = false; + } + pub fn new() -> Self { Self { internal_state: ProcessorSnapshot::default(), diff --git a/dsa/emulator/emulator_core/src/processor/dsa_optimised.rs b/dsa/emulator/emulator_core/src/processor/dsa_optimised.rs index 3db53c7..869530b 100644 --- a/dsa/emulator/emulator_core/src/processor/dsa_optimised.rs +++ b/dsa/emulator/emulator_core/src/processor/dsa_optimised.rs @@ -32,6 +32,10 @@ impl DsaImplementation for Emulator { self.core.boot() } + fn reset(&mut self) { + self.core.reset(); + } + fn run(&mut self) -> Result<(), DsaError> { // wait for UI to initialise. if self.shared_state.paused.load(Ordering::Relaxed) { @@ -88,9 +92,8 @@ impl DsaImplementation for Emulator { } // Check if the previous instruction caused a fault. - if let Some(fault) = self.pending_fault { + if let Some(fault) = self.pending_fault.take() { println!("WARN fault: {:?}", fault); - self.pending_fault = None; self.core.interrupt(fault); } @@ -216,11 +219,10 @@ impl Emulator { } // Responsibility: reset when requested by the UI - if self.shared_state.reseting.load(Ordering::Relaxed) == true { - self.core.internal_state.registers = [0; Register::COUNT as usize]; - self.core.internal_state.clock = 0; + if self.shared_state.resetting.load(Ordering::Relaxed) == true { + self.reset(); - while self.shared_state.reseting.load(Ordering::Relaxed) == true { + while self.shared_state.resetting.load(Ordering::Relaxed) == true { thread::sleep(Duration::from_millis(100)); } } diff --git a/dsa/emulator/emulator_core/src/processor/state.rs b/dsa/emulator/emulator_core/src/processor/state.rs index 4acce97..ed0df70 100644 --- a/dsa/emulator/emulator_core/src/processor/state.rs +++ b/dsa/emulator/emulator_core/src/processor/state.rs @@ -8,7 +8,7 @@ use super::interrupts::Interrupt; pub struct SharedState { pub proc: ArcSwap, - pub reseting: AtomicBool, + pub resetting: AtomicBool, pub paused: AtomicBool, pub update_req: AtomicBool, pub interrupt_queue: mpsc::Sender, @@ -17,7 +17,7 @@ pub struct SharedState { impl SharedState { pub fn new(sender: mpsc::Sender) -> Self { Self { - reseting: AtomicBool::new(false), + resetting: AtomicBool::new(false), proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())), paused: AtomicBool::new(true), diff --git a/dsa/emulator/emulator_ui/src/ui/controller.rs b/dsa/emulator/emulator_ui/src/ui/controller.rs index a6784c9..dedd9f3 100644 --- a/dsa/emulator/emulator_ui/src/ui/controller.rs +++ b/dsa/emulator/emulator_ui/src/ui/controller.rs @@ -32,13 +32,13 @@ impl Controller { let padding = Vec2::new(14.0, 8.0); match status { - RunningState::Paused | RunningState::Halted | RunningState::Breakpoint(_) => { + RunningState::Paused | RunningState::Breakpoint(_) => { if PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui).clicked() { self.emulator.resume() } }, - RunningState::Running => { + RunningState::Running | RunningState::Halted => { if PaddedWidget::button("⏸ Pause").color(Color32::YELLOW).ui(ui).clicked() { self.emulator.pause() } @@ -51,6 +51,8 @@ impl Controller { } } + status.clone().ui(ui, padding); + if PaddedWidget::button("⟳ Reset All").color(Color32::YELLOW).ui(ui).clicked() { self.emulator.reset(); } @@ -79,9 +81,6 @@ impl Controller { // } // } - - status.clone().ui(ui, padding); - ui.separator(); let pcx = self.emulator.get_register(Register::Pcx); diff --git a/dsa/emulator/emulator_ui/src/ui/process_manager.rs b/dsa/emulator/emulator_ui/src/ui/process_manager.rs index 4719062..1e9f194 100644 --- a/dsa/emulator/emulator_ui/src/ui/process_manager.rs +++ b/dsa/emulator/emulator_ui/src/ui/process_manager.rs @@ -44,6 +44,7 @@ pub struct ProcessManager { handles: Vec, terminated_actions: VecDeque, + active_tab: Option, } impl ProcessManager { @@ -66,26 +67,91 @@ impl ProcessManager { self.handles.push(handle); } - pub fn ui(&mut self, ui: &mut Ui) { + fn drain_logs(&mut self) { for handle in &mut self.handles { - let is_finished = handle.join_handle - .as_ref() - .is_some_and(|jh| jh.is_finished()); + while let Ok(line) = handle.rx.try_recv() { + handle.logs.push(line); + } + } + } + /// Checks for finished processes and queues their resulting Actions. + fn poll_finished(&mut self) { + for handle in &mut self.handles { + let is_finished = handle.join_handle.as_ref().is_some_and(|jh| jh.is_finished()); if is_finished { if let Some(jh) = handle.join_handle.take() { - println!("finished! {}", handle.name); if let Ok(action) = jh.join() { self.terminated_actions.push_back(action); } } } } + } + pub fn ui(&mut self, ui: &mut egui::Ui) { + self.drain_logs(); + self.poll_finished(); + + if self.handles.is_empty() { + ui.centered_and_justified(|ui| { + ui.weak("No processes running."); + }); + return; + } + + // keep active_tab pointing at something valid + if !self.active_tab.map_or(false, |id| self.handles.iter().any(|h| h.id == id)) { + self.active_tab = self.handles.first().map(|h| h.id); + } + + // --- tab bar --- ui.horizontal(|ui| { - for h in &self.handles { - ui.label(&h.name); - }; + for handle in &self.handles { + let selected = self.active_tab == Some(handle.id); + let running = handle.join_handle.is_some(); + + let label = if running { + format!("● {}", handle.name) + } else { + format!("○ {}", handle.name) + }; + + if ui.selectable_label(selected, label).clicked() { + self.active_tab = Some(handle.id); + } + } }); + + ui.separator(); + + // --- close button for the active tab --- + if let Some(active_id) = self.active_tab { + ui.horizontal(|ui| { + if let Some(handle) = self.handles.iter().find(|h| h.id == active_id) { + ui.label(egui::RichText::new(&handle.name).strong()); + } + + if ui.small_button("✕ Close").clicked() { + self.handles.retain(|h| h.id != active_id); + self.active_tab = self.handles.first().map(|h| h.id); + } + }); + + ui.separator(); + + // --- log view for the active tab --- + if let Some(handle) = self.handles.iter().find(|h| h.id == active_id) { + egui::ScrollArea::vertical() + .id_salt(("process_log", active_id)) + .stick_to_bottom(true) + .auto_shrink([false, false]) + .show(ui, |ui| { + for line in &handle.logs { + ui.monospace(line); + } + }); + } + } } } \ No newline at end of file