forked from MightyPork/crsn
parent
def2fc8db0
commit
5f4fd0e806
@ -1,5 +1,5 @@ |
|||||||
[package] |
[package] |
||||||
name = "csn_asm" |
name = "asm" |
||||||
version = "0.1.0" |
version = "0.1.0" |
||||||
authors = ["Ondřej Hruška <ondra@ondrovo.com>"] |
authors = ["Ondřej Hruška <ondra@ondrovo.com>"] |
||||||
edition = "2018" |
edition = "2018" |
@ -0,0 +1,188 @@ |
|||||||
|
use super::error::AsmError; |
||||||
|
|
||||||
|
|
||||||
|
pub mod literal; |
||||||
|
pub mod reg; |
||||||
|
pub mod mask; |
||||||
|
|
||||||
|
pub use reg::Register; |
||||||
|
pub use mask::Mask; |
||||||
|
use literal::Addr; |
||||||
|
|
||||||
|
use std::convert::TryFrom; |
||||||
|
|
||||||
|
use crate::data::literal::{Value, is_negative, as_signed}; |
||||||
|
use std::fmt::{Debug, Formatter, Display}; |
||||||
|
use std::fmt; |
||||||
|
|
||||||
|
/// Data source disposition
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)] |
||||||
|
pub enum DataDisp { |
||||||
|
/// Constant value
|
||||||
|
Immediate(Value), |
||||||
|
/// Constant memory address
|
||||||
|
ImmediatePtr(Addr), |
||||||
|
/// Register
|
||||||
|
Register(Register), |
||||||
|
/// Pointer into memory, stored in a numbered register
|
||||||
|
RegisterPtr(Register), |
||||||
|
} |
||||||
|
|
||||||
|
impl Display for DataDisp { |
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
||||||
|
match self { |
||||||
|
DataDisp::Immediate(v) => { |
||||||
|
write!(f, "{}", as_signed(*v)) |
||||||
|
} |
||||||
|
DataDisp::ImmediatePtr(a) => { |
||||||
|
write!(f, "@0x{:#18x}", a.0) |
||||||
|
} |
||||||
|
DataDisp::Register(r) => { |
||||||
|
write!(f, "{}", r) |
||||||
|
} |
||||||
|
DataDisp::RegisterPtr(r) => { |
||||||
|
write!(f, "@{}", r) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<SrcDisp> for DataDisp { |
||||||
|
fn from(s: SrcDisp) -> Self { |
||||||
|
match s { |
||||||
|
SrcDisp::Immediate(x) => DataDisp::Immediate(x), |
||||||
|
SrcDisp::ImmediatePtr(x) => DataDisp::ImmediatePtr(x), |
||||||
|
SrcDisp::Register(x) => DataDisp::Register(x), |
||||||
|
SrcDisp::RegisterPtr(x) => DataDisp::RegisterPtr(x), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<DstDisp> for DataDisp { |
||||||
|
fn from(s: DstDisp) -> Self { |
||||||
|
match s { |
||||||
|
DstDisp::ImmediatePtr(x) => DataDisp::ImmediatePtr(x), |
||||||
|
DstDisp::Register(x) => DataDisp::Register(x), |
||||||
|
DstDisp::RegisterPtr(x) => DataDisp::RegisterPtr(x), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Data source disposition
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)] |
||||||
|
pub enum SrcDisp { |
||||||
|
/// Constant value
|
||||||
|
Immediate(Value), |
||||||
|
/// Constant memory address
|
||||||
|
ImmediatePtr(Addr), |
||||||
|
/// Register
|
||||||
|
Register(Register), |
||||||
|
/// Pointer into memory, stored in a numbered register
|
||||||
|
RegisterPtr(Register), |
||||||
|
} |
||||||
|
|
||||||
|
impl TryFrom<DataDisp> for SrcDisp { |
||||||
|
type Error = AsmError; |
||||||
|
|
||||||
|
fn try_from(value: DataDisp) -> Result<Self, Self::Error> { |
||||||
|
match value { |
||||||
|
DataDisp::Immediate(x) => Ok(SrcDisp::Immediate(x)), |
||||||
|
DataDisp::ImmediatePtr(x) => Ok(SrcDisp::ImmediatePtr(x)), |
||||||
|
DataDisp::Register(x) => Ok(SrcDisp::Register(x)), |
||||||
|
DataDisp::RegisterPtr(x) => Ok(SrcDisp::RegisterPtr(x)), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Data destination disposition
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)] |
||||||
|
pub enum DstDisp { |
||||||
|
/// Constant memory address
|
||||||
|
ImmediatePtr(Addr), |
||||||
|
/// Register
|
||||||
|
Register(Register), |
||||||
|
/// Pointer into memory, stored in a numbered register
|
||||||
|
RegisterPtr(Register), |
||||||
|
} |
||||||
|
|
||||||
|
impl From<DstDisp> for SrcDisp { |
||||||
|
fn from(s: DstDisp) -> Self { |
||||||
|
match s { |
||||||
|
DstDisp::ImmediatePtr(x) => SrcDisp::ImmediatePtr(x), |
||||||
|
DstDisp::Register(x) => SrcDisp::Register(x), |
||||||
|
DstDisp::RegisterPtr(x) => SrcDisp::RegisterPtr(x), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl TryFrom<DataDisp> for DstDisp { |
||||||
|
type Error = AsmError; |
||||||
|
|
||||||
|
fn try_from(value: DataDisp) -> Result<Self, Self::Error> { |
||||||
|
match value { |
||||||
|
DataDisp::Immediate(_x) => Err(AsmError::ValueAsOutput), |
||||||
|
DataDisp::ImmediatePtr(x) => Ok(DstDisp::ImmediatePtr(x)), |
||||||
|
DataDisp::Register(x) => Ok(DstDisp::Register(x)), |
||||||
|
DataDisp::RegisterPtr(x) => Ok(DstDisp::RegisterPtr(x)), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Data source argument (read-only)
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq)] |
||||||
|
pub struct Rd(SrcDisp, Mask); |
||||||
|
|
||||||
|
impl Rd { |
||||||
|
pub fn new(src: SrcDisp) -> Self { |
||||||
|
Rd(src, Mask::default()) |
||||||
|
} |
||||||
|
pub fn d(self) -> SrcDisp { |
||||||
|
self.0 |
||||||
|
} |
||||||
|
pub fn mask(self) -> Mask { |
||||||
|
self.1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Data destination argument (read-write)
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq)] |
||||||
|
pub struct Wr(DstDisp, Mask); |
||||||
|
|
||||||
|
impl Wr { |
||||||
|
pub fn new(dst: DstDisp) -> Self { |
||||||
|
Wr(dst, Mask::default()) |
||||||
|
} |
||||||
|
pub fn d(self) -> DstDisp { |
||||||
|
self.0 |
||||||
|
} |
||||||
|
pub fn mask(self) -> Mask { |
||||||
|
self.1 |
||||||
|
} |
||||||
|
pub fn as_rd(&self) -> Rd { |
||||||
|
Rd(self.0.into(), self.1) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Debug for Rd { |
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
||||||
|
write!(f, "Rd(")?; |
||||||
|
let disp : DataDisp = self.0.into(); |
||||||
|
write!(f, "{}", disp); |
||||||
|
if !self.mask().is_default() { |
||||||
|
write!(f, ",{:?}", self.mask()); |
||||||
|
} |
||||||
|
write!(f, ")") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Debug for Wr { |
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
||||||
|
write!(f, "Wr(")?; |
||||||
|
let disp : DataDisp = self.0.into(); |
||||||
|
write!(f, "{}", disp); |
||||||
|
if !self.mask().is_default() { |
||||||
|
write!(f, ",{:?}", self.mask()); |
||||||
|
} |
||||||
|
write!(f, ")") |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
[package] |
||||||
|
name = "crsn" |
||||||
|
version = "0.1.0" |
||||||
|
authors = ["Ondřej Hruška <ondra@ondrovo.com>"] |
||||||
|
edition = "2018" |
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
asm = { path = "../asm" } |
||||||
|
runtime = { path = "../runtime" } |
||||||
|
simple_logger = "1.9.0" |
||||||
|
log = "0.4.11" |
||||||
|
thiserror = "1.0.20" |
||||||
|
anyhow = "1.0.32" |
@ -0,0 +1,36 @@ |
|||||||
|
#[macro_use] |
||||||
|
extern crate log; |
||||||
|
|
||||||
|
use simple_logger::SimpleLogger; |
||||||
|
use runtime::run_thread::{RunThread, ThreadToken}; |
||||||
|
use runtime::program::Program; |
||||||
|
use asm::data::literal::Addr; |
||||||
|
|
||||||
|
fn main() { |
||||||
|
SimpleLogger::new().init().unwrap(); |
||||||
|
|
||||||
|
// ;(dec r0 (z? (ret)))
|
||||||
|
let program = " |
||||||
|
( |
||||||
|
(main |
||||||
|
(ld r0 2) |
||||||
|
(:again) |
||||||
|
(dec r0) |
||||||
|
(jif nz :again) |
||||||
|
(fault \"that's it\") |
||||||
|
) |
||||||
|
) |
||||||
|
"; |
||||||
|
|
||||||
|
let parsed = asm::assemble(program).unwrap(); |
||||||
|
|
||||||
|
debug!("---"); |
||||||
|
for op in &parsed { |
||||||
|
debug!("{:?}", op); |
||||||
|
} |
||||||
|
debug!("---"); |
||||||
|
|
||||||
|
let thread = RunThread::new(ThreadToken(0), Program::new(parsed), Addr(0), &[]); |
||||||
|
|
||||||
|
thread.start().join().unwrap(); |
||||||
|
} |
@ -1,97 +0,0 @@ |
|||||||
use super::error::AsmError; |
|
||||||
|
|
||||||
|
|
||||||
pub mod literal; |
|
||||||
pub mod reg; |
|
||||||
pub mod mask; |
|
||||||
|
|
||||||
pub use reg::Register; |
|
||||||
pub use mask::Mask; |
|
||||||
use literal::Addr; |
|
||||||
|
|
||||||
use std::convert::TryFrom; |
|
||||||
|
|
||||||
use crate::data::literal::Value; |
|
||||||
|
|
||||||
/// Data source disposition
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)] |
|
||||||
pub enum DataDisp { |
|
||||||
/// Constant value
|
|
||||||
Immediate(Value), |
|
||||||
/// Constant memory address
|
|
||||||
ImmediatePtr(Addr), |
|
||||||
/// Register
|
|
||||||
Register(Register), |
|
||||||
/// Pointer into memory, stored in a numbered register
|
|
||||||
RegisterPtr(Register), |
|
||||||
} |
|
||||||
|
|
||||||
/// Data source disposition
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)] |
|
||||||
pub enum SrcDisp { |
|
||||||
/// Constant value
|
|
||||||
Immediate(Value), |
|
||||||
/// Constant memory address
|
|
||||||
ImmediatePtr(Addr), |
|
||||||
/// Register
|
|
||||||
Register(Register), |
|
||||||
/// Pointer into memory, stored in a numbered register
|
|
||||||
RegisterPtr(Register), |
|
||||||
} |
|
||||||
|
|
||||||
impl TryFrom<DataDisp> for SrcDisp { |
|
||||||
type Error = AsmError; |
|
||||||
|
|
||||||
fn try_from(value: DataDisp) -> Result<Self, Self::Error> { |
|
||||||
match value { |
|
||||||
DataDisp::Immediate(x) => Ok(SrcDisp::Immediate(x)), |
|
||||||
DataDisp::ImmediatePtr(x) => Ok(SrcDisp::ImmediatePtr(x)), |
|
||||||
DataDisp::Register(x) => Ok(SrcDisp::Register(x)), |
|
||||||
DataDisp::RegisterPtr(x) => Ok(SrcDisp::RegisterPtr(x)), |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// Data destination disposition
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)] |
|
||||||
pub enum DstDisp { |
|
||||||
/// Constant memory address
|
|
||||||
ImmediatePtr(Addr), |
|
||||||
/// Register
|
|
||||||
Register(Register), |
|
||||||
/// Pointer into memory, stored in a numbered register
|
|
||||||
RegisterPtr(Register), |
|
||||||
} |
|
||||||
|
|
||||||
impl TryFrom<DataDisp> for DstDisp { |
|
||||||
type Error = AsmError; |
|
||||||
|
|
||||||
fn try_from(value: DataDisp) -> Result<Self, Self::Error> { |
|
||||||
match value { |
|
||||||
DataDisp::Immediate(_x) => Err(AsmError::ValueAsOutput), |
|
||||||
DataDisp::ImmediatePtr(x) => Ok(DstDisp::ImmediatePtr(x)), |
|
||||||
DataDisp::Register(x) => Ok(DstDisp::Register(x)), |
|
||||||
DataDisp::RegisterPtr(x) => Ok(DstDisp::RegisterPtr(x)), |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// Data source argument (read-only)
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)] |
|
||||||
pub struct Rd(SrcDisp, Mask); |
|
||||||
|
|
||||||
impl Rd { |
|
||||||
pub fn new(src : SrcDisp) -> Self { |
|
||||||
Rd(src, Mask::default()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// Data destination argument (read-write)
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)] |
|
||||||
pub struct Wr(DstDisp, Mask); |
|
||||||
|
|
||||||
impl Wr { |
|
||||||
pub fn new(dst : DstDisp) -> Self { |
|
||||||
Wr(dst, Mask::default()) |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,13 @@ |
|||||||
|
[package] |
||||||
|
name = "runtime" |
||||||
|
version = "0.1.0" |
||||||
|
authors = ["Ondřej Hruška <ondra@ondrovo.com>"] |
||||||
|
edition = "2018" |
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
asm = { path = "../asm" } |
||||||
|
thiserror = "1.0.20" |
||||||
|
anyhow = "1.0.32" |
||||||
|
log = "0.4.11" |
@ -0,0 +1,98 @@ |
|||||||
|
use crate::run_thread::{ThreadToken, RunThread}; |
||||||
|
use asm::instr::{Op, Cond}; |
||||||
|
use crate::fault::Fault; |
||||||
|
use crate::frame::StackFrame; |
||||||
|
use asm::data::literal::{Value, is_positive, is_negative}; |
||||||
|
|
||||||
|
pub type CyclesSpent = usize; |
||||||
|
|
||||||
|
pub struct EvalRes { |
||||||
|
pub cycles: u8, |
||||||
|
pub advance: i64, |
||||||
|
} |
||||||
|
|
||||||
|
impl RunThread { |
||||||
|
pub fn eval_op(&mut self) -> Result<EvalRes, Fault> { |
||||||
|
let mut cycles = 1; |
||||||
|
let mut advance = 1; |
||||||
|
|
||||||
|
let mut frame = &mut self.frame; |
||||||
|
|
||||||
|
let op = self.program.read(frame.pc); |
||||||
|
debug!("{} | {:?}", frame.pc, op); |
||||||
|
|
||||||
|
/* Operations can be given different execution times when run in slow mode. */ |
||||||
|
/* Presently, all that do anything use 1 cycle. */ |
||||||
|
|
||||||
|
match op { |
||||||
|
Op::Nop => {} |
||||||
|
Op::FarLabel(_) | Op::Routine(_) => { |
||||||
|
/* this is nop, but without any cost - just markers */ |
||||||
|
cycles = 0; |
||||||
|
} |
||||||
|
Op::Barrier(msg) => { |
||||||
|
return Err(Fault::Barrier { |
||||||
|
msg: msg.clone().unwrap_or_else(|| "No msg".into()) |
||||||
|
}) |
||||||
|
} |
||||||
|
Op::Fault(msg) => { |
||||||
|
return Err(Fault::FaultInstr { |
||||||
|
msg: msg.clone().unwrap_or_else(|| "No msg".into()) |
||||||
|
}) |
||||||
|
} |
||||||
|
Op::FarJump(_) => unimplemented!(), |
||||||
|
Op::Call(_, _) => unimplemented!(), |
||||||
|
Op::Ret(_) => unimplemented!(), |
||||||
|
Op::Skip(val) => { |
||||||
|
let steps = frame.read(*val)?; |
||||||
|
advance = i64::from_ne_bytes(steps.to_ne_bytes()); |
||||||
|
} |
||||||
|
Op::SkipIf(cond, val) => { |
||||||
|
if frame.status.test(*cond) { |
||||||
|
let steps = frame.read(*val)?; |
||||||
|
advance = i64::from_ne_bytes(steps.to_ne_bytes()); |
||||||
|
} |
||||||
|
} |
||||||
|
Op::Mov(dst, src) => { |
||||||
|
let val = frame.read(*src)?; |
||||||
|
frame.write(*dst, val)?; |
||||||
|
} |
||||||
|
Op::Cmp(a, b) => { |
||||||
|
frame.status.clear(); |
||||||
|
let a = frame.read(*a)?; |
||||||
|
let b = frame.read(*b)?; |
||||||
|
frame.status.equal = a == b; |
||||||
|
frame.status.zero = a == 0 && b == 0; |
||||||
|
frame.status.lower = a < b; |
||||||
|
frame.status.greater = a > b; |
||||||
|
frame.status.positive = is_positive(a) && is_positive(b); |
||||||
|
frame.status.negative = is_negative(a) && is_negative(b); |
||||||
|
} |
||||||
|
Op::Inc(reg) => { |
||||||
|
frame.status.clear(); |
||||||
|
let mut val = frame.read(reg.as_rd())?; |
||||||
|
val = val.wrapping_add(1); |
||||||
|
frame.status.overflow = (val == 0); |
||||||
|
frame.status.zero = (val == 0); |
||||||
|
frame.status.positive = is_positive(val); |
||||||
|
frame.status.negative = is_negative(val); |
||||||
|
frame.write(*reg, val)?; |
||||||
|
} |
||||||
|
Op::Dec(reg) => { |
||||||
|
frame.status.clear(); |
||||||
|
let mut val = frame.read(reg.as_rd())?; |
||||||
|
frame.status.overflow = (val == 0); // will overflow
|
||||||
|
val = val.wrapping_sub(1); |
||||||
|
frame.status.zero = (val == 0); |
||||||
|
frame.status.positive = is_positive(val); |
||||||
|
frame.status.negative = is_negative(val); |
||||||
|
frame.write(*reg, val)?; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Ok(EvalRes { |
||||||
|
cycles, |
||||||
|
advance, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,59 @@ |
|||||||
|
use thiserror::Error; |
||||||
|
use super::span::MemorySpan; |
||||||
|
use crate::run_thread::ThreadToken; |
||||||
|
use crate::mlock::ClaimId; |
||||||
|
use asm::data::literal::DebugMsg; |
||||||
|
use asm::data::Register; |
||||||
|
|
||||||
|
#[derive(Error,Debug)] |
||||||
|
pub enum Fault { |
||||||
|
#[error("Bad instruction at addr {addr:#10x}: {cause}")] |
||||||
|
BadInstruction { |
||||||
|
addr : u32, |
||||||
|
cause: InstrError, |
||||||
|
}, |
||||||
|
|
||||||
|
#[error("Runtime hit a barrier instruction: {msg}")] |
||||||
|
Barrier { |
||||||
|
msg: DebugMsg, |
||||||
|
}, |
||||||
|
|
||||||
|
#[error("User fault: {msg}")] |
||||||
|
FaultInstr { |
||||||
|
msg: DebugMsg, |
||||||
|
}, |
||||||
|
|
||||||
|
#[error("Memory region {area:?} is locked by thread {owner:?}")] |
||||||
|
MemoryLocked { |
||||||
|
area: MemorySpan, |
||||||
|
owner: ThreadToken |
||||||
|
}, |
||||||
|
|
||||||
|
#[error("Memory claim {claim:?} owned by thread {owner:?} does not exist")] |
||||||
|
ClaimNotExist { |
||||||
|
claim: ClaimId, |
||||||
|
owner: ThreadToken |
||||||
|
}, |
||||||
|
|
||||||
|
#[error("Register does not exist: {reg:?}")] |
||||||
|
RegisterNotExist { |
||||||
|
reg: Register, |
||||||
|
}, |
||||||
|
|
||||||
|
#[error("Register is read-only: {reg:?}")] |
||||||
|
RegisterNotWritable { |
||||||
|
reg: Register, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Error,Debug)] |
||||||
|
pub enum InstrError { |
||||||
|
#[error("Instruction not recognized")] |
||||||
|
UnknownInstruction, |
||||||
|
|
||||||
|
#[error("Invalid bit span")] |
||||||
|
BadBitSpan, |
||||||
|
|
||||||
|
#[error("Operands data size differs")] |
||||||
|
UnevenOperandSize, |
||||||
|
} |
@ -0,0 +1,150 @@ |
|||||||
|
use asm::data::literal::{Addr, Value}; |
||||||
|
use asm::data::{Rd, SrcDisp, Register, Wr, DstDisp}; |
||||||
|
use crate::fault::Fault; |
||||||
|
use asm::instr::Cond; |
||||||
|
|
||||||
|
pub const REG_COUNT: usize = 8; |
||||||
|
|
||||||
|
#[derive(Default, Clone, Debug)] |
||||||
|
pub struct StatusFlags { |
||||||
|
/// Arguments are equal
|
||||||
|
pub equal: bool, |
||||||
|
/// Register is zero
|
||||||
|
pub zero: bool, |
||||||
|
/// A < B
|
||||||
|
pub lower: bool, |
||||||
|
/// A > B
|
||||||
|
pub greater: bool, |
||||||
|
/// Register is positive
|
||||||
|
pub positive: bool, |
||||||
|
/// Register is negative
|
||||||
|
pub negative: bool, |
||||||
|
/// Overflow (multiplication etc.)
|
||||||
|
pub overflow: bool, |
||||||
|
/// Arithmetic carry
|
||||||
|
pub carry: bool, |
||||||
|
} |
||||||
|
|
||||||
|
impl StatusFlags { |
||||||
|
pub fn clear(&mut self) { |
||||||
|
*self = Self::default(); |
||||||
|
} |
||||||
|
|
||||||
|
pub fn test(&self, cond: Cond) -> bool { |
||||||
|
match cond { |
||||||
|
Cond::Equal => self.equal, |
||||||
|
Cond::NotEqual => !self.equal, |
||||||
|
Cond::Zero => self.zero, |
||||||
|
Cond::NotZero => !self.zero, |
||||||
|
Cond::Lower => self.lower, |
||||||
|
Cond::LowerOrEqual => self.lower || self.equal, |
||||||
|
Cond::Greater => self.greater, |
||||||
|
Cond::GreaterOrEqual => self.greater || self.equal, |
||||||
|
Cond::Positive => self.positive, |
||||||
|
Cond::NonPositive => !self.positive, |
||||||
|
Cond::Negative => self.negative, |
||||||
|
Cond::NonNegative => !self.negative, |
||||||
|
Cond::Overflow => self.overflow, |
||||||
|
Cond::NotOverflow => !self.overflow, |
||||||
|
Cond::Carry => self.carry, |
||||||
|
Cond::NotCarry => !self.carry |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Default, Clone, Debug)] |
||||||
|
pub struct StackFrame { |
||||||
|
/// Program counter, address of the executed instruction
|
||||||
|
pub pc: Addr, |
||||||
|
/// Status flags
|
||||||
|
pub status: StatusFlags, |
||||||
|
/// Argument registers
|
||||||
|
pub arg: [Value; REG_COUNT], |
||||||
|
/// Result registers
|
||||||
|
pub res: [Value; REG_COUNT], |
||||||
|
/// General purpose registers
|
||||||
|
pub gen: [Value; REG_COUNT], |
||||||
|
} |
||||||
|
|
||||||
|
impl StackFrame { |
||||||
|
/// Create a new stack frame at a given address
|
||||||
|
pub fn new(addr: Addr, args: &[Value]) -> Self { |
||||||
|
let mut sf = StackFrame::default(); |
||||||
|
sf.pc = addr; |
||||||
|
for n in 0..(args.len().min(REG_COUNT)) { |
||||||
|
sf.arg[n] = args[n]; |
||||||
|
} |
||||||
|
sf |
||||||
|
} |
||||||
|
|
||||||
|
pub fn read(&mut self, rd: Rd) -> Result<u64, Fault> { |
||||||
|
match rd.d() { |
||||||
|
SrcDisp::Immediate(v) => Ok(v), |
||||||
|
SrcDisp::ImmediatePtr(_) => { |
||||||
|
unimplemented!("Immediate ptr") |
||||||
|
} |
||||||
|
SrcDisp::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
|
||||||
|
} else { |
||||||
|
debug!("Rd {:?} = {}", rd, self.res[rn as usize]); |
||||||
|
Ok(self.res[rn as usize]) |
||||||
|
} |
||||||
|
} |
||||||
|
SrcDisp::Register(Register::Arg(rn)) => { |
||||||
|
if rn >= REG_COUNT as u8 { |
||||||
|
Err(Fault::RegisterNotExist { reg: Register::Arg(rn) }) |
||||||
|
} else { |
||||||
|
debug!("Rd {:?} = {}", rd, self.arg[rn as usize]); |
||||||
|
Ok(self.arg[rn as usize]) |
||||||
|
} |
||||||
|
} |
||||||
|
SrcDisp::Register(Register::Gen(rn)) => { |
||||||
|
if rn >= REG_COUNT as u8 { |
||||||
|
Err(Fault::RegisterNotExist { reg: Register::Gen(rn) }) |
||||||
|
} else { |
||||||
|
debug!("Rd {:?} = {}", rd, self.gen[rn as usize]); |
||||||
|
Ok(self.gen[rn as usize]) |
||||||
|
} |
||||||
|
} |
||||||
|
SrcDisp::RegisterPtr(_) => { |
||||||
|
unimplemented!("Register ptr") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn write(&mut self, wr: Wr, val: Value) -> Result<(), Fault> { |
||||||
|
debug!("WR {:?} := {}", wr, val); |
||||||
|
|
||||||
|
match wr.d() { |
||||||
|
DstDisp::ImmediatePtr(_) => { |
||||||
|
unimplemented!("Immediate ptr") |
||||||
|
} |
||||||
|
DstDisp::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
|
||||||
|
} else { |
||||||
|
Err(Fault::RegisterNotWritable { reg: Register::Res(rn) }) |
||||||
|
} |
||||||
|
} |
||||||
|
DstDisp::Register(Register::Arg(rn)) => { |
||||||
|
if rn >= REG_COUNT as u8 { |
||||||
|
Err(Fault::RegisterNotExist { reg: Register::Arg(rn) }) |
||||||
|
} else { |
||||||
|
Err(Fault::RegisterNotWritable { reg: Register::Res(rn) }) |
||||||
|
} |
||||||
|
} |
||||||
|
DstDisp::Register(Register::Gen(rn)) => { |
||||||
|
if rn >= REG_COUNT as u8 { |
||||||
|
Err(Fault::RegisterNotExist { reg: Register::Gen(rn) }) |
||||||
|
} else { |
||||||
|
self.gen[rn as usize] = val; |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
||||||
|
DstDisp::RegisterPtr(_) => { |
||||||
|
unimplemented!("Register ptr") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
#[macro_use] extern crate log; |
||||||
|
|
||||||
|
pub mod run_thread; |
||||||
|
pub mod mlock; |
||||||
|
pub mod sparse; |
||||||
|
pub mod fault; |
||||||
|
pub mod span; |
||||||
|
pub mod exec; |
||||||
|
pub mod frame; |
||||||
|
pub mod program; |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
#[test] |
||||||
|
fn it_works() { |
||||||
|
assert_eq!(2 + 2, 4); |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,91 @@ |
|||||||
|
use std::sync::atomic::{AtomicU32, Ordering}; |
||||||
|
use std::fmt; |
||||||
|
use std::fmt::Formatter; |
||||||
|
use crate::run_thread::ThreadToken; |
||||||
|
use crate::fault::Fault; |
||||||
|
use crate::span::MemorySpan; |
||||||
|
|
||||||
|
/// Records memory claims and protects from illegal access
|
||||||
|
#[derive(Debug, Default)] |
||||||
|
struct MemoryGuard { |
||||||
|
claims: Vec<Claim>, |
||||||
|
counter: AtomicU32, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq, Debug, Ord, PartialOrd)] |
||||||
|
pub struct ClaimId(pub u32); |
||||||
|
|
||||||
|
impl fmt::Display for ClaimId { |
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
||||||
|
write!(f, "{}", self.0) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
struct Claim { |
||||||
|
owner: ThreadToken, |
||||||
|
span: MemorySpan, |
||||||
|
id: ClaimId, |
||||||
|
} |
||||||
|
|
||||||
|
impl MemoryGuard { |
||||||
|
pub fn new() -> Self { |
||||||
|
Default::default() |
||||||
|
} |
||||||
|
|
||||||
|
/// Claim a memory area
|
||||||
|
pub fn claim(&mut self, owner: ThreadToken, span: MemorySpan) -> Result<ClaimId, Fault> { |
||||||
|
// naive
|
||||||
|
for claim in &self.claims { |
||||||
|
if claim.span.intersects(span) && claim.owner != owner { |
||||||
|
return Err(Fault::MemoryLocked { |
||||||
|
area: claim.span, |
||||||
|
owner: claim.owner, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let id = self.next_id(); |
||||||
|
|
||||||
|
self.claims.push(Claim { |
||||||
|
id, |
||||||
|
owner, |
||||||
|
span, |
||||||
|
}); |
||||||
|
|
||||||
|
Ok(id) |
||||||
|
} |
||||||
|
|
||||||
|
/// Get a unique claim ID and increment the counter
|
||||||
|
pub fn next_id(&self) -> ClaimId { |
||||||
|
ClaimId(self.counter.fetch_and(1, Ordering::Relaxed)) |
||||||
|
} |
||||||
|
|
||||||
|
/// Get the next claim ID (ID is incremented after calling "next").
|
||||||
|
/// May be used for release_owned_after()
|
||||||
|
pub fn epoch(&self) -> ClaimId { |
||||||
|
ClaimId(self.counter.load(Ordering::Relaxed)) |
||||||
|
} |
||||||
|
|
||||||
|
/// Release a claim by claim ID
|
||||||
|
pub fn release(&mut self, owner: ThreadToken, claim: ClaimId) -> Result<(), Fault> { |
||||||
|
match self.claims.iter().position(|c| c.id == claim && c.owner == owner) { |
||||||
|
Some(pos) => { |
||||||
|
self.claims.swap_remove(pos); |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
None => Err(Fault::ClaimNotExist { claim, owner }), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Release all owned by a thread (thread ends)
|
||||||
|
pub fn release_owned(&mut self, owner: ThreadToken) { |
||||||
|
self.claims.retain(|c| c.owner != owner); |
||||||
|
} |
||||||
|
|
||||||
|
/// Release all owned by a thread, with claim ID >= a given value
|
||||||
|
/// (return from a subroutine)
|
||||||
|
pub fn release_owned_after(&mut self, owner: ThreadToken, epoch : ClaimId) { |
||||||
|
self.claims.retain(|c| c.owner != owner || c.id >= epoch); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
use asm::instr::Op; |
||||||
|
use asm::data::literal::Addr; |
||||||
|
|
||||||
|
#[derive(Clone, Debug)] |
||||||
|
pub struct Program { |
||||||
|
ops: Vec<Op>, |
||||||
|
} |
||||||
|
|
||||||
|
impl Program { |
||||||
|
pub fn new(ops : Vec<Op>) -> Self { |
||||||
|
Self { |
||||||
|
ops, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn read(&self, addr: Addr) -> &Op { |
||||||
|
if addr.0 >= self.ops.len() as u64 { |
||||||
|
&Op::Nop |
||||||
|
} else { |
||||||
|
&self.ops[addr.0 as usize] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,65 @@ |
|||||||
|
use std::time::Duration; |
||||||
|
use std::thread::JoinHandle; |
||||||
|
|
||||||
|
use asm::data::literal::{Addr, Value}; |
||||||
|
use asm::instr::Op; |
||||||
|
use crate::exec; |
||||||
|
use asm::data::{Rd, SrcDisp, reg::Register, Wr, DstDisp}; |
||||||
|
use crate::fault::Fault; |
||||||
|
use crate::frame::StackFrame; |
||||||
|
use crate::program::Program; |
||||||
|
use crate::exec::EvalRes; |
||||||
|
|
||||||
|
const CYCLE_TIME : Duration = Duration::from_millis(100); |
||||||
|
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq, Debug, Ord, PartialOrd)] |
||||||
|
pub struct ThreadToken(pub u32); |
||||||
|
|
||||||
|
pub struct RunThread { |
||||||
|
/// Thread ID
|
||||||
|
pub id: ThreadToken, |
||||||
|
/// Active stack frame
|
||||||
|
pub frame: StackFrame, |
||||||
|
/// Call stack
|
||||||
|
pub call_stack: Vec<StackFrame>, |
||||||
|
/// Program to run
|
||||||
|
pub program: Program, |
||||||
|
} |
||||||
|
|
||||||
|
impl RunThread { |
||||||
|
pub fn new(id: ThreadToken, program: Program, pc: Addr, args: &[u64]) -> Self { |
||||||
|
let sf = StackFrame::new(pc, args); |
||||||
|
|
||||||
|
Self { |
||||||
|
id, |
||||||
|
frame: sf, |
||||||
|
call_stack: vec![], |
||||||
|
program, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn start(self) -> JoinHandle<()> { |
||||||
|
std::thread::spawn(move || { |
||||||
|
self.run(); |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
fn run(mut self) { |
||||||
|
'run: loop { |
||||||
|
match self.eval_op() { |
||||||
|
Ok(EvalRes { |
||||||
|
cycles, advance |
||||||
|
}) => { |
||||||
|
std::thread::sleep(CYCLE_TIME * (cycles as u32)); |
||||||
|
debug!("PC += {}", advance); |
||||||
|
self.frame.pc.advance(advance); |
||||||
|
} |
||||||
|
Err(e) => { |
||||||
|
error!("Fault: {:?}", e); |
||||||
|
break 'run; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,47 @@ |
|||||||
|
|
||||||
|
|
||||||
|
use asm::data::literal::Addr; |
||||||
|
|
||||||
|
#[derive(Debug,Clone,Copy)] |
||||||
|
pub struct MemorySpan { |
||||||
|
addr: usize, |
||||||
|
len: usize, |
||||||
|
} |
||||||
|
|
||||||
|
impl MemorySpan { |
||||||
|
pub fn new(addr: Addr, len: usize) -> MemorySpan { |
||||||
|
if len == 0 { |
||||||
|
panic!("Cannot create empty span!"); |
||||||
|
} |
||||||
|
|
||||||
|
Self { |
||||||
|
addr: addr.0 as usize, |
||||||
|
len, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Get start address
|
||||||
|
pub fn start(&self) -> Addr { |
||||||
|
Addr(self.addr as u64) |
||||||
|
} |
||||||
|
|
||||||
|
/// Get end address (last included byte)
|
||||||
|
pub fn last(&self) -> Addr { |
||||||
|
Addr((self.addr + self.len - 1) as u64) |
||||||
|
} |
||||||
|
|
||||||
|
/// Check if this intersects another span
|
||||||
|
pub fn intersects(&self, other: MemorySpan) -> bool { |
||||||
|
!( |
||||||
|
self.last() < other.start() |
||||||
|
|| self.start() > other.last() |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
/// Check if this is a strict subset of another span
|
||||||
|
pub fn inside(&self, other: MemorySpan) -> bool { |
||||||
|
self.start() >= other.start() |
||||||
|
&& self.last() <= other.last() |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,317 @@ |
|||||||
|
use std::io::Write; |
||||||
|
|
||||||
|
#[derive(Default)] |
||||||
|
pub struct SparseBuffer { |
||||||
|
chunks: Vec<(usize, Vec<u8>)>, |
||||||
|
} |
||||||
|
|
||||||
|
impl SparseBuffer { |
||||||
|
pub fn new() -> Self { |
||||||
|
Default::default() |
||||||
|
} |
||||||
|
|
||||||
|
pub fn write(&mut self, addr: usize, bytes: &[u8]) { |
||||||
|
if self.chunks.is_empty() { |
||||||
|
self.chunks.push((addr, Vec::from(bytes))); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
enum InsertLoc { |
||||||
|
Head, |
||||||
|
Append(usize), |
||||||
|
Tail, |
||||||
|
} |
||||||
|
|
||||||
|
let mut loc = None; |
||||||
|
for (i, (at, _ch)) in self.chunks.iter().enumerate() { |
||||||
|
if *at > addr { |
||||||
|
if i == 0 { |
||||||
|
loc = Some(InsertLoc::Head); |
||||||
|
} else { |
||||||
|
loc = Some(InsertLoc::Append(i - 1)); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let loc = if let Some(l) = loc { |
||||||
|
l |
||||||
|
} else { |
||||||
|
InsertLoc::Tail |
||||||
|
}; |
||||||
|
|
||||||
|
match loc { |
||||||
|
InsertLoc::Head => self.write_head(addr, bytes), |
||||||
|
InsertLoc::Append(i) => self.write_after(i, addr, bytes), |
||||||
|
InsertLoc::Tail => self.write_tail(addr, bytes), |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
fn write_head(&mut self, addr: usize, bytes: &[u8]) { |
||||||
|
self.chunks.insert(0, (addr, vec![])); |
||||||
|
self.write_after(0, addr, bytes); |
||||||
|
} |
||||||
|
|
||||||
|
fn write_after(&mut self, index: usize, addr: usize, bytes: &[u8]) { |
||||||
|
if index == self.chunks.len() - 1 { |
||||||
|
self.write_tail(addr, bytes); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
let end_addr = addr + bytes.len(); |
||||||
|
|
||||||
|
// This means we have at least two chunks.
|
||||||
|
// The written area can:
|
||||||
|
// - fit within the chunk
|
||||||
|
// - extend the chunk, but still end before the second's start address
|
||||||
|
// - extend the chunk, overflowing into one or more following chunks
|
||||||
|
|
||||||
|
let (a, slice) = self.chunks.iter().nth(index + 1).unwrap(); |
||||||
|
let second_start = *a; |
||||||
|
let _second_len = slice.len(); |
||||||
|
|
||||||
|
let (a, slice) = self.chunks.get_mut(index).unwrap(); |
||||||
|
let first_addr = *a; |
||||||
|
let first_len = slice.len(); |
||||||
|
|
||||||
|
if end_addr <= first_addr + first_len { |
||||||
|
(&mut slice[(addr - first_addr) as usize..]).write(bytes); |
||||||
|
} else if end_addr <= second_start { |
||||||
|
slice.truncate((addr - first_addr) as usize); |
||||||
|
slice.extend_from_slice(bytes); |
||||||
|
} else { |
||||||
|
// overflows into one or more chunks
|
||||||
|
slice.truncate((addr - first_addr) as usize); |
||||||
|
slice.extend_from_slice(&bytes[..(second_start - addr) as usize]); |
||||||
|
|
||||||
|
// recurse
|
||||||
|
self.write_after(index + 1, second_start, &bytes[(second_start - addr) as usize..]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn write_tail(&mut self, addr: usize, bytes: &[u8]) { |
||||||
|
let (a, slice) = self.chunks.last_mut().unwrap(); |
||||||
|
|
||||||
|
let last_addr = *a; |
||||||
|
let last_len = slice.len(); |
||||||
|
let end_addr = addr + bytes.len(); |
||||||
|
|
||||||
|
assert!(addr >= last_addr); |
||||||
|
|
||||||
|
if end_addr <= last_addr + last_len { |
||||||
|
// Entirely contained within the last chunk
|
||||||
|
(&mut slice[((addr - last_addr) as usize)..]).write(bytes); |
||||||
|
} else if addr > last_addr + last_len { |
||||||
|
self.chunks.push((addr, Vec::from(bytes))); |
||||||
|
} else { |
||||||
|
// The write slice starts within the last chunk, but extends past its end.
|
||||||
|
slice.truncate((addr - last_addr) as usize); |
||||||
|
slice.extend_from_slice(bytes); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn coalesce(&mut self) { |
||||||
|
let mut prev = Option::<(usize, Vec<u8>)>::None; |
||||||
|
let mut merged = vec![]; |
||||||
|
for chunk in self.chunks.drain(..) { |
||||||
|
if let Some(prevchunk) = &mut prev { |
||||||
|
if chunk.0 == prevchunk.0 + prevchunk.1.len() { |
||||||
|
prevchunk.1.extend_from_slice(&chunk.1[..]); |
||||||
|
continue; |
||||||
|
} else { |
||||||
|
merged.push(prev.take().unwrap()); |
||||||
|
} |
||||||
|
} |
||||||
|
prev = Some(chunk); |
||||||
|
} |
||||||
|
if let Some(prevchunk) = prev { |
||||||
|
merged.push(prevchunk); |
||||||
|
} |
||||||
|
self.chunks = merged; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
use super::SparseBuffer; |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_empty() { |
||||||
|
let mut sparse = SparseBuffer::new(); |
||||||
|
|
||||||
|
sparse.write(100, &[0, 1, 2, 3, 4]); |
||||||
|
assert_eq!(vec![(100, vec![0, 1, 2, 3, 4])], sparse.chunks); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_append_sparse() { |
||||||
|
let mut sparse = SparseBuffer::new(); |
||||||
|
|
||||||
|
sparse.write(0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); |
||||||
|
sparse.write(100, &[7, 8, 9, 10]); |
||||||
|
assert_eq!(vec![ |
||||||
|
(0, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), |
||||||
|
(100, vec![7, 8, 9, 10]) |
||||||
|
], sparse.chunks); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_append_inside_last() { |
||||||
|
let mut sparse = SparseBuffer::new(); |
||||||
|
|
||||||
|
sparse.write(0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); |
||||||
|
sparse.write(7, &[70, 80, 90]); |
||||||
|
assert_eq!(vec![ |
||||||
|
(0, vec![0, 1, 2, 3, 4, 5, 6, 70, 80, 90, 10]), |
||||||
|
], sparse.chunks); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_append_extend_last() { |
||||||
|
let mut sparse = SparseBuffer::new(); |
||||||
|
|
||||||
|
sparse.write(0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); |
||||||
|
sparse.write(5, &[50, 60, 70, 80, 90, 100, 110, 120, 130, 140]); |
||||||
|
assert_eq!(vec![(0, vec![0, 1, 2, 3, 4, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140])], sparse.chunks); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_prepend_sparse() { |
||||||
|
let mut sparse = SparseBuffer::new(); |
||||||
|
|
||||||
|
sparse.write(100, &[0, 1, 2, 3, 4]); |
||||||
|
sparse.write(10, &[70, 80, 90, 100]); |
||||||
|
|
||||||
|
assert_eq!(vec![ |
||||||
|
(10, vec![70, 80, 90, 100]), |
||||||
|
(100, vec![0, 1, 2, 3, 4]) |
||||||
|
], sparse.chunks); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_within_first() { |
||||||
|
let mut sparse = SparseBuffer::new(); |
||||||
|
|
||||||
|
sparse.write(0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); |
||||||
|
sparse.write(4, &[40, 50, 60]); |
||||||
|
|
||||||
|
assert_eq!(vec![ |
||||||
|
(0, vec![0, 1, 2, 3, 40, 50, 60, 7, 8, 9]), |
||||||
|
], sparse.chunks); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_grows_first() { |
||||||
|
let mut sparse = SparseBuffer::new(); |
||||||
|
|
||||||
|
sparse.write(0, &[0, 1, 2, 3, 4, 5]); |
||||||
|
sparse.write(10, &[10, 11, 12]); |
||||||
|
sparse.write(4, &[40, 50, 60]); |
||||||
|
|
||||||
|
assert_eq!(vec![ |
||||||
|
(0, vec![0, 1, 2, 3, 40, 50, 60]), |
||||||
|
(10, vec![10, 11, 12]), |
||||||
|
], sparse.chunks); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_grows_first2() { |
||||||
|
let mut sparse = SparseBuffer::new(); |
||||||
|
|
||||||
|
sparse.write(0, &[0, 1, 2, 3, 4, 5]); |
||||||
|
sparse.write(10, &[10, 11, 12]); |
||||||
|
sparse.write(4, &[40, 50, 60, 70, 80, 90]); |
||||||
|
|
||||||
|
assert_eq!(vec![ |
||||||
|
(0, vec![0, 1, 2, 3, 40, 50, 60, 70, 80, 90]), |
||||||
|
(10, vec![10, 11, 12]), |
||||||
|
], sparse.chunks); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_overflow_first() { |
||||||
|
let mut sparse = SparseBuffer::new(); |
||||||
|
|
||||||
|
sparse.write(0, &[0, 1, 2, 3, 4, 5]); |
||||||
|
sparse.write(10, &[10, 11, 12, 13, 14, 15]); |
||||||
|
sparse.write(4, &[40, 50, 60, 70, 80, 90, 100, 110, 120]); |
||||||
|
|
||||||
|
assert_eq!(vec![ |
||||||
|
(0, vec![0, 1, 2, 3, 40, 50, 60, 70, 80, 90]), |
||||||
|
(10, vec![100, 110, 120, 13, 14, 15]), |
||||||
|
], sparse.chunks); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_join_tail() { |
||||||
|
let mut sparse = SparseBuffer::new(); |
||||||
|
|
||||||
|
sparse.write(0, &[0, 1, 2]); |
||||||
|
sparse.write(3, &[3, 4, 5]); |
||||||
|
sparse.write(6, &[6, 7, 8]); |
||||||
|
|
||||||
|
assert_eq!(vec![ |
||||||
|
(0, vec![0, 1, 2, 3, 4, 5, 6, 7, 8]), |
||||||
|
], sparse.chunks); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_overflow_multiple() { |
||||||
|
let mut sparse = SparseBuffer::new(); |
||||||
|
|
||||||
|
sparse.write(0, &[0, 1, 2]); |
||||||
|
sparse.write(4, &[4, 5, 6]); |
||||||
|
sparse.write(8, &[8, 9, 10]); |
||||||
|
|
||||||
|
assert_eq!(vec![ |
||||||
|
(0, vec![0, 1, 2]), |
||||||
|
(4, vec![4, 5, 6]), |
||||||
|
(8, vec![8, 9, 10]), |
||||||
|
], sparse.chunks); |
||||||
|
|
||||||
|
sparse.write(2, &[20, 30, 40, 50, 60, 70, 80, 90]); |
||||||
|
|
||||||
|
assert_eq!(vec![ |
||||||
|
(0, vec![0, 1, 20, 30]), |
||||||
|
(4, vec![40, 50, 60, 70]), |
||||||
|
(8, vec![80, 90, 10]), |
||||||
|
], sparse.chunks); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_overflow_multiple2() { |
||||||
|
let mut sparse = SparseBuffer::new(); |
||||||
|
|
||||||
|
sparse.write(0, &[0, 1, 2]); |
||||||
|
sparse.write(4, &[4, 5, 6]); |
||||||
|
sparse.write(8, &[8, 9, 10]); |
||||||
|
|
||||||
|
assert_eq!(vec![ |
||||||
|
(0, vec![0, 1, 2]), |
||||||
|
(4, vec![4, 5, 6]), |
||||||
|
(8, vec![8, 9, 10]), |
||||||
|
], sparse.chunks); |
||||||
|
sparse.coalesce(); |
||||||
|
|
||||||
|
// no change, as expected
|
||||||
|
assert_eq!(vec![ |
||||||
|
(0, vec![0, 1, 2]), |
||||||
|
(4, vec![4, 5, 6]), |
||||||
|
(8, vec![8, 9, 10]), |
||||||
|
], sparse.chunks); |
||||||
|
|
||||||
|
sparse.write(2, &[20, 30, 40, 50, 60, 70, 80, 90, 100, 110]); |
||||||
|
|
||||||
|
assert_eq!(vec![ |
||||||
|
(0, vec![0, 1, 20, 30]), |
||||||
|
(4, vec![40, 50, 60, 70]), |
||||||
|
(8, vec![80, 90, 100, 110]), |
||||||
|
], sparse.chunks); |
||||||
|
|
||||||
|
// join contiguous
|
||||||
|
sparse.coalesce(); |
||||||
|
assert_eq!(vec![ |
||||||
|
(0, vec![0, 1, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110]), |
||||||
|
], sparse.chunks); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue