#![feature(likely_unlikely, widening_mul, int_lowest_highest_one)] #![deny(clippy::all)] use std::env::args; use std::hint::unlikely; use std::process::exit; use std::sync::{ Arc, atomic::Ordering::{Acquire, Relaxed, Release}, }; use std::thread::scope; use std::time::{Duration, Instant}; use pixels::wgpu::{BlendState, Color}; use pixels::{Error, Pixels, PixelsBuilder, SurfaceTexture}; use winit::application::ApplicationHandler; use winit::dpi::LogicalSize; use winit::event::WindowEvent; use winit::event_loop::EventLoop; use winit::platform::scancode::PhysicalKeyExtScancode; use winit::window::Window; use crate::cpu::{Computer, MMIOInterupt, instr_to_text}; mod wait; use wait::WaitOnAtomic; mod cpu; use cpu::SHARED; fn wait_int() { let mut v = (&SHARED.external_interupts).load(Acquire); while unlikely(v != 0) { #[cfg(feature = "debug")] println!("wating for interupt clear {v}"); SHARED.external_interupts.wait(v); v = (&SHARED.external_interupts).load(Acquire); } } const WIDTH: u32 = 640; const HEIGHT: u32 = 480; struct App<'a> { w: Option>, pixels: Option>, } impl<'a> ApplicationHandler for App<'a> { fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { let window = { let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64); Arc::new( event_loop .create_window( Window::default_attributes() .with_title("bisare screen") .with_min_inner_size(size) .with_maximized(true), ) .unwrap(), ) }; self.w = Some(window.clone()); let size = window.inner_size(); let surface_texture = SurfaceTexture::new(size.width, size.height, window); let pix = PixelsBuilder::new(WIDTH, HEIGHT, surface_texture) .clear_color(Color::BLACK) .blend_state(BlendState::REPLACE) .build(); self.pixels = Some(pix.unwrap()); } fn window_event( &mut self, elwt: &winit::event_loop::ActiveEventLoop, _: winit::window::WindowId, event: WindowEvent, ) { // Draw the current frame match event { WindowEvent::KeyboardInput { event: key_event, .. } => { let enabled = (&SHARED.external_enabled_interupts).load(Relaxed) & Into::::into(MMIOInterupt::Keyboard) != 0; if enabled { wait_int(); } #[cfg(feature = "debug")] print!("Keyboard event: "); #[cfg(feature = "rich_keyboard")] { use winit::platform::modifier_supplement::KeyEventExtModifierSupplement; let kb0 = key_event.text_with_all_modifiers().map_or(u32::MAX, |s| { s.as_bytes() .into_iter() .fold(0, |a, e| a << 8 | (*e as u32)) }); SHARED.keyboard[0].store(kb0, Relaxed); let kb1 = key_event .key_without_modifiers() .to_text() .map_or(u32::MAX, |s| { s.as_bytes() .into_iter() .fold(0, |a, e| a << 8 | (*e as u32)) }); SHARED.keyboard[1].store(kb1, Relaxed); let kb2 = key_event.state.is_pressed() as u32 | ((key_event.repeat as u32) << 1); SHARED.keyboard[2].store(kb2, Relaxed); #[cfg(feature = "debug")] print!("{kb0} {kb1} {kb2}") } let kb3 = key_event.physical_key.to_scancode().unwrap_or(0); #[cfg(feature = "debug")] println!(" {kb3}"); SHARED.keyboard[3].store(kb3, Relaxed); if enabled { (&SHARED.external_interupts).store(MMIOInterupt::Keyboard.into(), Release); #[cfg(feature = "debug")] println!("wake due to keyboard event"); SHARED.external_interupts.signal(); } } WindowEvent::CursorMoved { position, .. } => { let enabled = (&SHARED.external_enabled_interupts).load(Relaxed) & Into::::into(MMIOInterupt::MouseMove) != 0; if enabled { wait_int(); } match self .pixels .as_ref() .unwrap() .window_pos_to_pixel((position.x as f32, position.y as f32)) { Ok((x, y)) => { (&cpu::SHARED.mouse[1]).store(x as u32, Relaxed); (&cpu::SHARED.mouse[2]).store(y as u32, Relaxed); } Err(_) => { (&SHARED.mouse[1]).store(u32::MAX, Relaxed); (&SHARED.mouse[2]).store(u32::MAX, Relaxed); } } if enabled { (&SHARED.external_interupts).store(MMIOInterupt::MouseMove.into(), Release); #[cfg(feature = "debug")] println!("wake due mouse move"); SHARED.external_interupts.signal(); } } // WindowEvent::MouseWheel { // delta, // .. // } => {} WindowEvent::MouseInput { state, button, .. } => { let enabled = (&SHARED.external_enabled_interupts).load(Relaxed) & Into::::into(MMIOInterupt::MouseClick) != 0; if enabled { wait_int(); } let but = 1 << match button { winit::event::MouseButton::Left => 0, winit::event::MouseButton::Right => 1, winit::event::MouseButton::Middle => 2, winit::event::MouseButton::Back => 3, winit::event::MouseButton::Forward => 4, winit::event::MouseButton::Other(i) => i & 31, }; match state { winit::event::ElementState::Pressed => { (&cpu::SHARED.mouse[0]).fetch_or(but, std::sync::atomic::Ordering::Relaxed) } winit::event::ElementState::Released => (&cpu::SHARED.mouse[0]) .fetch_and(!but, std::sync::atomic::Ordering::Relaxed), }; if enabled { (&SHARED.external_interupts).store(MMIOInterupt::MouseClick.into(), Release); #[cfg(feature = "debug")] println!("wake mouse click"); SHARED.external_interupts.signal(); } } WindowEvent::ScaleFactorChanged { .. } => { self.w.as_ref().unwrap().request_redraw(); } //handling redraws and other graphical events WindowEvent::RedrawRequested => { let enabled = (&SHARED.external_enabled_interupts).load(Relaxed) & Into::::into(MMIOInterupt::VSync) != 0; if enabled { (&SHARED.external_interupts).store(MMIOInterupt::VSync.into(), Relaxed); SHARED.external_interupts.signal(); wait_int(); } let pix = self.pixels.as_mut().unwrap(); let screen = pix.frame_mut(); for (addr, ubgr) in cpu::SHARED.screen_buf.iter().enumerate() { let raw = ubgr.load(std::sync::atomic::Ordering::Relaxed); #[cfg(not(feature = "rgba"))] let rgba: [u8; 4] = raw.to_le_bytes(); #[cfg(feature = "rgba")] let rgba = raw.to_be_bytes(); for i in 0..4 { screen[addr * 4 + i] = rgba[i]; } } if let Err(_) = pix.render() { elwt.exit(); return; } } WindowEvent::Resized(size) => { if self .pixels .as_mut() .unwrap() .resize_surface(size.width, size.height) .is_err() { println!("Error while resising pixels, exiting!"); elwt.exit(); return; } self.w.as_ref().unwrap().request_redraw(); } WindowEvent::CloseRequested => { elwt.exit(); return; } WindowEvent::Destroyed => { println!("Windows destroyed, exiting!"); elwt.exit(); return; } //do nothing for the other events _ => {} } } fn new_events( &mut self, event_loop: &winit::event_loop::ActiveEventLoop, cause: winit::event::StartCause, ) { match cause { winit::event::StartCause::ResumeTimeReached { requested_resume, .. } => { let mut next = requested_resume + Duration::from_secs_f64(1. / 60.); let now = Instant::now(); if next < now { next = now + Duration::from_secs_f64(1. / 30.); } event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(next)); if let Some(w) = self.w.as_ref() { w.request_redraw(); } } winit::event::StartCause::WaitCancelled { .. } => {} winit::event::StartCause::Poll => { let next = Instant::now() + Duration::from_secs_f64(1. / 60.); event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(next)); } winit::event::StartCause::Init => { let next = Instant::now() + Duration::from_secs_f64(1. / 60.); event_loop.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(next)); } } } } fn main() -> Result<(), Error> { let event_loop = EventLoop::new().unwrap(); // let mut input = WinitInputHelper::new(); let mut app = App { w: None, pixels: None, }; let program = args() .nth(1) .expect("you must supply the exec name as the first argument"); scope(|sc| { sc.spawn(|| { let mut simulation = Computer::new(program); #[cfg(not(feature = "debug"))] loop { simulation.step(64); } //ugly debug code, I should improve that using a real TUI crate #[cfg(feature = "debug")] { debug_loop(&mut simulation); } }); #[allow(deprecated)] let res = event_loop.run_app(&mut app); match res { Ok(_) => exit(0), Err(e) => { println!("{e}"); exit(1); } } }) } #[cfg(feature = "debug")] fn debug_loop(com: &mut Computer) { struct Wrap(DefaultCompleter, DefaultCompleter); use clap::Parser; use clap_repl::ClapEditor; use clap_repl::reedline::{ Completer, DefaultCompleter, DefaultPrompt, DefaultPromptSegment, FileBackedHistory, }; use crate::cpu::instr_to_text; #[derive(Debug, Parser)] #[command(name = "")] // This name will show up in clap's error messages, so it is important to set it to "". enum Commands { /// Step by single instrcution #[command(alias = "s")] Step { ///number of instruction to step (default one) num: Option, }, /// Run until program halt, or specified instruction is reached #[command(alias = "r")] Run { /// Can be either a label or a address (support hex format) desigantor: Option, }, /// run until the current function return. #[command(alias = "u")] Up, /// Print memory at address. Support hexa format #[command(alias = "p")] Print { address: String }, /// Print the address associated with a label #[command(alias = "l")] Label { label: String }, /// Print context #[command(alias = "c")] Context, } let prompt = DefaultPrompt { left_prompt: DefaultPromptSegment::Basic(">>".to_owned()), right_prompt: DefaultPromptSegment::Empty, }; let commands_comp = DefaultCompleter::new_with_wordlen( ["step", "run", "up", "print", "label", "help", "context"] .map(|s| s.to_string()) .to_vec(), 0, ); let mut labels_comp = DefaultCompleter::with_inclusions("_0123456789".chars().collect::>().as_slice()) .set_min_word_len(0); labels_comp.insert(com.book.0.values().cloned().collect()); let editor = ClapEditor::::builder() .with_prompt(Box::new(prompt)) .with_editor_hook(|reed| { reed.with_history(Box::new( FileBackedHistory::with_file(1000, "debug_cmd.hist".into()).unwrap(), )) .with_completer(Box::new(Wrap(commands_comp, labels_comp))) .with_quick_completions(true) .with_partial_completions(true) }) .build(); debug_context(com); editor.repl(|command| match command { Commands::Step { num } => { let steps = num.unwrap_or(1); com.debug_step(steps); debug_context(com); } Commands::Run { desigantor } => match desigantor { Some(s) => match parse_int::parse::(s.as_str()) { Ok(addr) => { while com.pc != (addr / 4) { com.debug_step(1); } debug_context(com); } Err(_) => match com.book.1.get(s.as_str()).cloned() { Some(addr) => { while com.pc != (addr as usize / 4) { com.debug_step(1); } debug_context(com); } None => { println!("Error, {s} cannot be interpreted as addr nor label") } }, }, None => { while !com.error { com.debug_step(64); } debug_context(com); } }, Commands::Up => { let curr_sp = com.sp; while (com.sp > curr_sp) || ((com.ram[com.pc] != (0b10001000 << 24)) && (com.ram[com.pc] != (0b10101000 << 24))) { com.debug_step(1); } debug_context(com); } Commands::Print { address } => match parse_int::parse::(address.as_str()) { Ok(addr) => match com.ram.get(addr / 4) { Some(i) => { println!( "RAM at {addr:8x}: {:8x} {}", i, instr_to_text(*i, u32::MAX, &com.book.0) ) } None => println!("Cannot index RAM at address {addr:8x}"), }, Err(_) => match com.book.1.get(address.as_str()).cloned() { Some(addr) => println!( "RAM at {addr:8x}: {:8x} {}", com.ram[addr as usize / 4], instr_to_text(com.ram[addr as usize / 4], addr, &com.book.0) ), None => { println!("Error, {address} cannot be interpreted as addr nor label") } }, }, Commands::Label { label } => match com.book.1.get(label.as_str()) { Some(addr) => println!("label is at addr {addr}"), None => println!("error: label not found"), }, Commands::Context => debug_context(com), }); exit(0); impl Completer for Wrap { fn complete(&mut self, line: &str, pos: usize) -> Vec { let trimmed = line.trim_start(); let line_parts = trimmed.splitn(2, ' ').collect::>(); if line_parts.len() <= 1 { self.0.complete(line, pos) } else { match line_parts[0] { "r" | "run" | "p" | "print" | "l" | "label" => { let trimmed_2 = line_parts[1].trim_start(); let offset = line.len() - trimmed_2.len(); let mut sub = self.1.complete(trimmed_2, pos - offset); for sug in sub.iter_mut() { use clap_repl::reedline::Span; sug.span = Span { start: sug.span.start + offset, end: sug.span.end + offset, } } sub } _ => Vec::new(), } } } } } fn debug_context(com: &Computer) { println!("Interupt state: {:?}", com.interupts); for i in 0..8 { println!( "r{i} = {:8x} r{:<2} = {:8x}", com.regs[i], i + 8, com.regs[i + 8] ); } println!("SP={:08x} PC={:08x}", com.sp, com.pc); println!("RAM at SP | Ram at PC:"); let mut pc_lines = Vec::new(); for i in 0..16 { match com.book.0.get(&((com.pc + i) as u32 * 4)) { Some(label) => pc_lines.push(format!(" {label}:")), None => {} }; pc_lines.push(format!( "{:08x} {}", com.ram[com.pc + i], instr_to_text(com.ram[com.pc + i], (com.pc + i) as u32 * 4, &com.book.0) )); } for (i, pc_l) in pc_lines.iter().enumerate() { if com.sp + i < com.ram.len() { println!("{:08x} | {pc_l}", com.ram[com.sp + i]) } else { println!(" -- | {pc_l}") } } }