performance improvements

pull/21/head
Ondřej Hruška 3 years ago
parent a3eae0a8a9
commit e3fe3c6d72
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 10
      Cargo.lock
  2. 9
      README.md
  3. 1
      crsn/Cargo.toml
  4. 10
      crsn/src/asm/data/literal.rs
  5. 12
      crsn/src/asm/data/rd.rs
  6. 13
      crsn/src/asm/mod.rs
  7. 5
      crsn/src/asm/parse/parse_op.rs
  8. 28
      crsn/src/module/mod.rs
  9. 2
      crsn/src/runtime/exec.rs
  10. 1
      crsn/src/runtime/frame.rs
  11. 4
      crsn/src/runtime/frame/status.rs
  12. 12
      crsn/src/runtime/program.rs
  13. 19
      crsn/src/runtime/run_thread.rs
  14. 14
      crsn/src/runtime/run_thread/info.rs
  15. 65
      crsn/src/runtime/run_thread/state.rs
  16. 8
      crsn_arith/src/exec.rs
  17. 2
      crsn_stacks/src/exec.rs
  18. 14
      launcher/src/main.rs

10
Cargo.lock generated

@ -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"

@ -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

@ -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"

@ -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);

@ -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 {

@ -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<Vec<Box<dyn CrsnExtension>>>) -> Result<Arc<Program>, error::CrsnError> {
pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec<Box<dyn CrsnExtension>>) -> Result<Arc<Program>, 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<Vec<Box<dyn CrsnExtension>>>) -> 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))?)
}

@ -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<Option<Op>, 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,

@ -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<Self> {
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;

@ -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);

@ -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];

@ -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 => {

@ -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())
});

@ -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<AtomicU64> {
Arc::new(AtomicU64::new(info::UNIQ_BASE))
}
pub struct ThreadParams<'a> {
pub id: ThreadToken,
pub uniq: Option<Arc<AtomicU64>>,
pub uniq: Arc<CrsnUniq>,
pub program: Arc<Program>,
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!

@ -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<AtomicU64>,
/// Initializer, used to generate unique IDs
pub(crate) uniq: Arc<CrsnUniq>,
/// Program to run
pub program: Arc<Program>,
/// Program to run
@ -20,10 +20,8 @@ pub struct ThreadInfo {
pub extensions: Arc<Vec<Box<dyn CrsnExtension>>>,
}
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()
}
}

@ -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<ThreadInfo>,
@ -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<Value, Fault> {
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)

@ -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 } => {

@ -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());

@ -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,

Loading…
Cancel
Save