diff --git a/Cargo.lock b/Cargo.lock index 9dcf80c..ffa4093 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,6 +175,7 @@ dependencies = [ "anyhow", "dyn-clonable", "log", + "nudge", "num-traits", "parking_lot", "sexp", @@ -443,6 +444,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "nudge" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f379c3eafab28638ffb5cd95f1091dd21f5c6e7699e40b2ee96e9764a87f779" +dependencies = [ + "cfg-if", +] + [[package]] name = "num" version = "0.1.42" diff --git a/README.md b/README.md index 378ebfe..ae62df1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CROISSANT VIRTUAL MACHINE -Croissant (or Crsn for short) is an extensible runtime emulating a weird microcomputer (or not so micro, that depends on what extensions you install). +Croissant (or *crsn* for short) is an extensible runtime emulating a weird microcomputer (or not so micro, that depends on what extensions you install). ## FAQ @@ -8,6 +8,13 @@ Croissant (or Crsn for short) is an extensible runtime emulating a weird microco F U N +### How is the performance? + +Silly fast, actually. 60fps animations are perfectly doable if that's your thing. +It's probably faster than you need for most things, actually. + +You can slow it down using the `-C` argument, or using sleep instructions. + #### What if I don't enjoy writing assembly that looks like weird Lisp? Maybe this is not for you diff --git a/crsn/Cargo.toml b/crsn/Cargo.toml index 15c3d4e..eba809f 100644 --- a/crsn/Cargo.toml +++ b/crsn/Cargo.toml @@ -13,3 +13,4 @@ dyn-clonable = "0.9.0" log = "0.4.11" num-traits = "0.2.12" parking_lot = "0.11.0" +nudge = "0.2.1" diff --git a/crsn/src/asm/data/literal.rs b/crsn/src/asm/data/literal.rs index 54bc73d..a202442 100644 --- a/crsn/src/asm/data/literal.rs +++ b/crsn/src/asm/data/literal.rs @@ -7,15 +7,18 @@ pub type DebugMsg = Cow<'static, str>; /// Immediate value pub type Value = u64; -pub fn is_positive(val: Value) -> bool { +#[inline(always)] +pub const fn is_positive(val: Value) -> bool { 0 == (val & 0x8000_0000_0000_0000) } -pub fn is_negative(val: Value) -> bool { +#[inline(always)] +pub const fn is_negative(val: Value) -> bool { 0 != (val & 0x8000_0000_0000_0000) } -pub fn as_signed(val: Value) -> i64 { +#[inline(always)] +pub const fn as_signed(val: Value) -> i64 { i64::from_ne_bytes(val.to_ne_bytes()) } @@ -24,6 +27,7 @@ pub fn as_signed(val: Value) -> i64 { pub struct Addr(pub u64); impl Addr { + #[inline(always)] pub fn advance(&mut self, add: i64) { if add < 0 { self.0 = self.0.wrapping_sub(-add as u64); diff --git a/crsn/src/asm/data/rd.rs b/crsn/src/asm/data/rd.rs index 589bb62..43ae689 100644 --- a/crsn/src/asm/data/rd.rs +++ b/crsn/src/asm/data/rd.rs @@ -12,15 +12,17 @@ impl Rd { pub const fn new(src: RdData) -> Self { Rd(src, Mask::FULL) } - pub fn data(self) -> RdData { + + pub const fn data(self) -> RdData { self.0 } - pub fn mask(self) -> Mask { + + pub const fn mask(self) -> Mask { self.1 } - pub fn immediate(val: Value) -> Rd { - Rd(RdData::Immediate(val), Mask::default()) + pub const fn immediate(val: Value) -> Rd { + Rd(RdData::Immediate(val), Mask::FULL) } } @@ -48,7 +50,7 @@ impl Debug for Rd { pub struct RdObj(Register); impl RdObj { - pub fn new(reg: Register) -> Self { + pub const fn new(reg: Register) -> Self { RdObj(reg) } pub const fn reg(self) -> Register { diff --git a/crsn/src/asm/mod.rs b/crsn/src/asm/mod.rs index 292713e..bbdf097 100644 --- a/crsn/src/asm/mod.rs +++ b/crsn/src/asm/mod.rs @@ -5,8 +5,9 @@ use sexp::SourcePosition; use crate::asm::instr::flatten::labels_to_skips; use crate::asm::parse::{ParserContext, ParserState}; -use crate::module::CrsnExtension; +use crate::module::{CrsnExtension, CrsnUniq}; use crate::runtime::program::Program; +use crate::builtin::BuiltinOps; pub mod data; pub mod error; @@ -15,7 +16,13 @@ pub mod parse; pub mod patches; /// Parse a program from string and assemble a low level instruction sequence from it. -pub fn assemble(source: &str, parsers: Arc>>) -> Result, error::CrsnError> { +pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec>) -> Result, error::CrsnError> { + parsers.insert(0, BuiltinOps::new()); + + for p in &mut parsers { + p.init(uniq); + } + let pcx = ParserContext { parsers: &parsers, state: RefCell::new(ParserState { @@ -28,5 +35,5 @@ pub fn assemble(source: &str, parsers: Arc>>) -> Resu let ops = parse::parse(source, &SourcePosition::default(), &pcx)?; let ops = labels_to_skips(ops)?; - Ok(Program::new(ops, parsers)?) + Ok(Program::new(ops, Arc::new(parsers))?) } diff --git a/crsn/src/asm/parse/parse_op.rs b/crsn/src/asm/parse/parse_op.rs index bf26826..9fb058d 100644 --- a/crsn/src/asm/parse/parse_op.rs +++ b/crsn/src/asm/parse/parse_op.rs @@ -4,12 +4,11 @@ use crate::asm::error::CrsnError; use crate::asm::instr::cond::parse_cond; use crate::asm::instr::Op; use crate::asm::parse::arg_parser::TokenParser; -use crate::builtin::BuiltinOps; + use crate::module::ParseRes; pub fn parse_op<'a>(mut keyword: &str, mut arg_tokens: TokenParser<'a>, spos: &SourcePosition) -> Result, CrsnError> { // Include built-in instructions - let builtins = [BuiltinOps::new()]; let mut cond = None; if let Some(pos) = keyword.find('.') { @@ -17,7 +16,7 @@ pub fn parse_op<'a>(mut keyword: &str, mut arg_tokens: TokenParser<'a>, spos: &S keyword = &keyword[..pos]; } - for p in builtins.iter().chain(arg_tokens.pcx.parsers) { + for p in arg_tokens.pcx.parsers { arg_tokens = match p.parse_op(spos, keyword, arg_tokens) { Ok(ParseRes::Parsed(kind)) => return Ok(Some(Op { cond, diff --git a/crsn/src/module/mod.rs b/crsn/src/module/mod.rs index dbb1e12..3d2e672 100644 --- a/crsn/src/module/mod.rs +++ b/crsn/src/module/mod.rs @@ -14,6 +14,8 @@ use crate::asm::parse::arg_parser::TokenParser; use crate::runtime::fault::Fault; use crate::runtime::run_thread::state::RunState; use crate::runtime::run_thread::ThreadInfo; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; mod eval_res; @@ -41,7 +43,33 @@ pub trait OpTrait: Debug + Send + Sync + 'static { fn to_sexp(&self) -> Sexp; } +/// CRSN initializer object. +/// Only one should be created for the lifespan of the parser and runtime. +#[derive(Default)] +pub struct CrsnUniq { + object_handle_counter : AtomicU64 +} + +pub const UNIQ_BASE: u64 = 0x6372_736e_0000_0000; + +impl CrsnUniq { + pub fn new() -> Arc { + Arc::new(Self { + object_handle_counter: AtomicU64::new(UNIQ_BASE), + }) + } + + pub fn unique_handle(&self) -> u64 { + self.object_handle_counter.fetch_add(1, Ordering::Relaxed) + } +} + + pub trait CrsnExtension: Debug + Send + Sync + 'static { + fn init(&mut self, uniq: &CrsnUniq) { + // + } + /// Get name of the module fn name(&self) -> &'static str; diff --git a/crsn/src/runtime/exec.rs b/crsn/src/runtime/exec.rs index 3f83924..4763823 100644 --- a/crsn/src/runtime/exec.rs +++ b/crsn/src/runtime/exec.rs @@ -8,7 +8,7 @@ impl RunThread { let state = &mut self.state; let info = &self.info; - let op = info.program.read(state.frame.pc); + let op = info.program.fetch_instr(state.frame.pc); trace!("### {:04} : {:?}", state.frame.pc.0, op); diff --git a/crsn/src/runtime/frame.rs b/crsn/src/runtime/frame.rs index 8cf518f..950b682 100644 --- a/crsn/src/runtime/frame.rs +++ b/crsn/src/runtime/frame.rs @@ -33,6 +33,7 @@ impl StackFrame { sf } + #[inline(always)] pub fn set_retvals(&mut self, vals: &[Value]) { for n in 0..(vals.len().min(REG_COUNT)) { self.res[n] = vals[n]; diff --git a/crsn/src/runtime/frame/status.rs b/crsn/src/runtime/frame/status.rs index 3518ff0..0ed5096 100644 --- a/crsn/src/runtime/frame/status.rs +++ b/crsn/src/runtime/frame/status.rs @@ -27,6 +27,7 @@ pub struct StatusFlags { } impl StatusFlags { + #[inline(always)] pub fn clear(&mut self) { *self = Self::default(); } @@ -57,12 +58,14 @@ impl StatusFlags { if val & 0x100 != 0 { self.carry = true; } } + #[inline(always)] pub fn update(&mut self, val: Value) { self.zero = val == 0; self.positive = is_positive(val); self.negative = is_negative(val); } + #[inline(always)] pub fn test(&self, cond: Cond) -> bool { match cond { Cond::Equal => self.equal, @@ -86,6 +89,7 @@ impl StatusFlags { } } + #[inline(always)] pub fn set(&mut self, cond: Cond) { match cond { Cond::Equal => { diff --git a/crsn/src/runtime/program.rs b/crsn/src/runtime/program.rs index 1ddebdc..c2c1d05 100644 --- a/crsn/src/runtime/program.rs +++ b/crsn/src/runtime/program.rs @@ -90,8 +90,10 @@ impl Program { } /// Read a program instruction at address - pub fn read(&self, addr: Addr) -> &Op { - if addr.0 >= self.ops.len() as u64 { + pub fn fetch_instr(&self, addr: Addr) -> &Op { + if (addr.0 as usize) < self.ops.len() { + unsafe { self.ops.get_unchecked(addr.0 as usize) } + } else { &Op { kind: OpKind::BuiltIn(BuiltinOp::Halt), pos: SourcePosition { @@ -101,8 +103,6 @@ impl Program { }, cond: None, } - } else { - &self.ops[addr.0 as usize] } } @@ -116,7 +116,7 @@ impl Program { if b0 != b1 { // block barrier that only partially intersects the jump if (*b0 >= from && *b0 <= to) != (*b1 >= from && *b1 <= to) { - if let OpKind::BuiltIn(BuiltinOp::Barrier { msg, .. }) = &self.read(*b0).kind { + if let OpKind::BuiltIn(BuiltinOp::Barrier { msg, .. }) = &self.fetch_instr(*b0).kind { return Err(Fault::JumpThroughBarrier { msg: msg.clone().unwrap_or("BLOCK BARRIER".into()) }); @@ -127,7 +127,7 @@ impl Program { } else { // point barrier if *b0 >= from && *b0 <= to { - if let OpKind::BuiltIn(BuiltinOp::Barrier { msg, .. }) = &self.read(*b0).kind { + if let OpKind::BuiltIn(BuiltinOp::Barrier { msg, .. }) = &self.fetch_instr(*b0).kind { return Err(Fault::JumpThroughBarrier { msg: msg.clone().unwrap_or("POINT BARRIER".into()) }); diff --git a/crsn/src/runtime/run_thread.rs b/crsn/src/runtime/run_thread.rs index 1ec04eb..9755cb9 100644 --- a/crsn/src/runtime/run_thread.rs +++ b/crsn/src/runtime/run_thread.rs @@ -1,5 +1,5 @@ use std::sync::Arc; -use std::sync::atomic::AtomicU64; + use std::thread::JoinHandle; use std::time::Duration; @@ -7,7 +7,7 @@ pub use info::ThreadInfo; pub use state::RunState; use crate::asm::data::literal::Addr; -use crate::module::EvalRes; +use crate::module::{EvalRes, CrsnUniq}; use crate::runtime::fault::Fault; use crate::runtime::frame::StackFrame; use crate::runtime::program::Program; @@ -23,13 +23,9 @@ pub struct RunThread { pub mod info; pub mod state; -pub fn new_uniq() -> Arc { - Arc::new(AtomicU64::new(info::UNIQ_BASE)) -} - pub struct ThreadParams<'a> { pub id: ThreadToken, - pub uniq: Option>, + pub uniq: Arc, pub program: Arc, pub pc: Addr, pub cycle_time: Duration, @@ -42,7 +38,7 @@ impl RunThread { let ti = Arc::new(ThreadInfo { id: params.id, - uniq: params.uniq.unwrap_or_else(new_uniq), + uniq: params.uniq, program: params.program, cycle_time: params.cycle_time, extensions, @@ -73,12 +69,11 @@ impl RunThread { 'run: loop { match self.eval_op() { Ok(EvalRes { cycles, advance }) => { - std::thread::sleep(self.info.cycle_time * (cycles as u32)); + if cycles > 0 { + std::thread::sleep(self.info.cycle_time * (cycles as u32)); + } trace!("Step {}; Status = {}", advance, self.state.frame.status); self.state.frame.pc.advance(advance); - if self.state.frame.status.invalid { - warn!("Operation failed with INVALID status!"); - } } Err(Fault::Halt) => { // TODO implement coordinated shutdown when more threads are running! diff --git a/crsn/src/runtime/run_thread/info.rs b/crsn/src/runtime/run_thread/info.rs index 83df3c4..7559559 100644 --- a/crsn/src/runtime/run_thread/info.rs +++ b/crsn/src/runtime/run_thread/info.rs @@ -1,17 +1,17 @@ use std::sync::Arc; -use std::sync::atomic::{AtomicU64, Ordering}; + use std::time::Duration; use crate::asm::data::literal::Value; use crate::runtime::program::Program; use crate::runtime::run_thread::ThreadToken; -use crate::module::CrsnExtension; +use crate::module::{CrsnExtension, CrsnUniq}; pub struct ThreadInfo { /// Thread ID pub id: ThreadToken, - /// Thread ID - pub(crate) uniq: Arc, + /// Initializer, used to generate unique IDs + pub(crate) uniq: Arc, /// Program to run pub program: Arc, /// Program to run @@ -20,10 +20,8 @@ pub struct ThreadInfo { pub extensions: Arc>>, } -pub const UNIQ_BASE: u64 = 0x6372_736e_0000_0000; - impl ThreadInfo { - pub fn unique_value(&self) -> Value { - self.uniq.fetch_add(1, Ordering::Relaxed) + pub fn unique_handle(&self) -> Value { + self.uniq.unique_handle() } } diff --git a/crsn/src/runtime/run_thread/state.rs b/crsn/src/runtime/run_thread/state.rs index c9f190c..0aa5e96 100644 --- a/crsn/src/runtime/run_thread/state.rs +++ b/crsn/src/runtime/run_thread/state.rs @@ -8,6 +8,7 @@ use crate::runtime::fault::Fault; use crate::runtime::frame::{CallStack, REG_COUNT, StackFrame}; use std::sync::Arc; use crate::runtime::run_thread::ThreadInfo; +use nudge::{likely}; pub struct RunState { pub thread_info: Arc, @@ -49,6 +50,7 @@ impl RunState { } /// Set a status flag. Only supports simple, positive conds (i.e. not GreaterOrEqual) + #[inline(always)] pub fn set_flag(&mut self, cond: Cond, set: bool) { if set { self.frame.status.set(cond); @@ -56,6 +58,7 @@ impl RunState { } /// Check status flags for a condition + #[inline(always)] pub fn test_cond(&self, cond: Cond) -> bool { self.frame.status.test(cond) } @@ -72,12 +75,14 @@ impl RunState { } /// Clear status flags + #[inline(always)] pub fn clear_status(&mut self) { self.frame.status.clear(); } /// Update status flags using a variable. /// The update is additive - call `clear_status()` first if desired! + #[inline(always)] pub fn update_status(&mut self, val: Value) { self.frame.status.update(val); } @@ -90,29 +95,29 @@ impl RunState { /// Read a `Rd` value pub fn read(&mut self, rd: Rd) -> Result { match rd.data() { - RdData::Immediate(v) => Ok(v), - RdData::Register(Register::Res(rn)) => { - if rn >= REG_COUNT as u8 { - Err(Fault::RegisterNotExist { reg: Register::Res(rn) }) // TODO use match after @ when stabilized https://github.com/rust-lang/rust/issues/65490 + RdData::Register(Register::Gen(rn)) => { + if likely(rn < REG_COUNT as u8) { + trace!("Rd {:?} = {}", rd, self.frame.gen[rn as usize]); + Ok(self.frame.gen[rn as usize]) } else { - trace!("Rd {:?} = {}", rd, self.frame.res[rn as usize]); - Ok(self.frame.res[rn as usize]) + Err(Fault::RegisterNotExist { reg: Register::Gen(rn) }) } } + RdData::Immediate(v) => Ok(v), RdData::Register(Register::Arg(rn)) => { - if rn >= REG_COUNT as u8 { - Err(Fault::RegisterNotExist { reg: Register::Arg(rn) }) - } else { + if likely(rn < REG_COUNT as u8) { trace!("Rd {:?} = {}", rd, self.frame.arg[rn as usize]); Ok(self.frame.arg[rn as usize]) + } else { + Err(Fault::RegisterNotExist { reg: Register::Arg(rn) }) } } - RdData::Register(Register::Gen(rn)) => { - if rn >= REG_COUNT as u8 { - Err(Fault::RegisterNotExist { reg: Register::Gen(rn) }) + RdData::Register(Register::Res(rn)) => { + if likely(rn < REG_COUNT as u8) { + trace!("Rd {:?} = {}", rd, self.frame.res[rn as usize]); + Ok(self.frame.res[rn as usize]) } else { - trace!("Rd {:?} = {}", rd, self.frame.gen[rn as usize]); - Ok(self.frame.gen[rn as usize]) + Err(Fault::RegisterNotExist { reg: Register::Res(rn) }) // TODO use match after @ when stabilized https://github.com/rust-lang/rust/issues/65490 } } RdData::RegObject(register) => { @@ -160,32 +165,32 @@ impl RunState { trace!("WR {:?} := {}", wr, val); match wr.d() { - WrData::Discard => { - /* Discard */ - Ok(()) - } - WrData::Register(Register::Res(rn)) => { - if rn >= REG_COUNT as u8 { - Err(Fault::RegisterNotExist { reg: Register::Res(rn) }) // TODO use match after @ when stabilized https://github.com/rust-lang/rust/issues/65490 + WrData::Register(Register::Gen(rn)) => { + if likely(rn < REG_COUNT as u8) { + self.frame.gen[rn as usize] = val; + Ok(()) } else { - Err(Fault::RegisterNotWritable { reg: Register::Res(rn) }) + Err(Fault::RegisterNotExist { reg: Register::Gen(rn) }) } } WrData::Register(Register::Arg(rn)) => { - if rn >= REG_COUNT as u8 { - Err(Fault::RegisterNotExist { reg: Register::Arg(rn) }) - } else { + if likely(rn < REG_COUNT as u8) { Err(Fault::RegisterNotWritable { reg: Register::Res(rn) }) + } else { + Err(Fault::RegisterNotExist { reg: Register::Arg(rn) }) } } - WrData::Register(Register::Gen(rn)) => { - if rn >= REG_COUNT as u8 { - Err(Fault::RegisterNotExist { reg: Register::Gen(rn) }) + WrData::Register(Register::Res(rn)) => { + if likely(rn < REG_COUNT as u8) { + Err(Fault::RegisterNotWritable { reg: Register::Res(rn) }) } else { - self.frame.gen[rn as usize] = val; - Ok(()) + Err(Fault::RegisterNotExist { reg: Register::Res(rn) }) // TODO use match after @ when stabilized https://github.com/rust-lang/rust/issues/65490 } } + WrData::Discard => { + /* Discard */ + Ok(()) + } WrData::RegObject(register) => { let reference = self.read(Rd::new(RdData::Register(register)))?; self.write_object(reference, wr.mask(), val) diff --git a/crsn_arith/src/exec.rs b/crsn_arith/src/exec.rs index 412473a..63278dd 100644 --- a/crsn_arith/src/exec.rs +++ b/crsn_arith/src/exec.rs @@ -64,13 +64,13 @@ impl OpTrait for ArithOp { state.clear_status(); let x = state.read(*a)?; let y = state.read(*b)?; - let (res, ov) = if let Some(v) = x.checked_mul(y) { - (v, false) + let res = if let Some(v) = x.checked_mul(y) { + v } else { - (x.wrapping_mul(y), true) + state.set_flag(Cond::Overflow, true); + x.wrapping_mul(y) }; state.update_status(res); - state.set_flag(Cond::Overflow, ov); state.write(*dst, res)?; } ArithOp::Div { dst, rem, a, div } => { diff --git a/crsn_stacks/src/exec.rs b/crsn_stacks/src/exec.rs index a39faa6..e7313f6 100644 --- a/crsn_stacks/src/exec.rs +++ b/crsn_stacks/src/exec.rs @@ -22,7 +22,7 @@ impl OpTrait for StackOp { let eres = EvalRes::default(); match self { StackOp::NewStack { dst } => { - let id = info.unique_value(); + let id = info.unique_handle(); let stacks: &mut Stacks = state.ext_mut(); stacks.store.insert(id, VecDeque::new()); diff --git a/launcher/src/main.rs b/launcher/src/main.rs index 336873c..e69e631 100644 --- a/launcher/src/main.rs +++ b/launcher/src/main.rs @@ -2,7 +2,7 @@ extern crate log; use std::collections::HashMap; -use std::sync::Arc; + use std::time::Duration; use clappconfig::{AppConfig, clap}; @@ -10,7 +10,7 @@ use clappconfig::clap::ArgMatches; use serde::{Deserialize, Serialize}; use crsn::asm::data::literal::Addr; -use crsn::module::OpTrait; +use crsn::module::{OpTrait, CrsnUniq}; use crsn::runtime::run_thread::{RunThread, ThreadToken, ThreadParams}; use crsn_arith::ArithOps; use crsn_screen::ScreenOps; @@ -121,13 +121,13 @@ fn main() -> anyhow::Result<()> { let source = read_file::read_file(&config.program_file)?; - let parsers = Arc::new(vec![ + let uniq = CrsnUniq::new(); + + let parsed = crsn::asm::assemble(&source, &uniq, vec![ ArithOps::new(), StackOps::new(), ScreenOps::new(), - ]); - - let parsed = crsn::asm::assemble(&source, parsers)?; + ])?; if config.asm_only { for (n, op) in parsed.ops.iter().enumerate() { @@ -147,7 +147,7 @@ fn main() -> anyhow::Result<()> { let args = &[]; let thread = RunThread::new(ThreadParams { id: ThreadToken(0), - uniq: None, + uniq, program: parsed, pc: Addr(0), cycle_time: config.cycle_time,