pull/21/head
Ondřej Hruška 4 years ago
parent def2fc8db0
commit 5f4fd0e806
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 170
      Cargo.lock
  2. 5
      Cargo.toml
  3. 2
      asm/Cargo.toml
  4. 57
      asm/src/data/literal.rs
  5. 4
      asm/src/data/mask.rs
  6. 188
      asm/src/data/mod.rs
  7. 0
      asm/src/data/reg.rs
  8. 0
      asm/src/error.rs
  9. 16
      asm/src/instr/cond.rs
  10. 4
      asm/src/instr/flatten.rs
  11. 0
      asm/src/instr/mod.rs
  12. 5
      asm/src/instr/op.rs
  13. 18
      asm/src/lib.rs
  14. 0
      asm/src/parse/mod.rs
  15. 4
      asm/src/parse/parse_cond.rs
  16. 4
      asm/src/parse/parse_data.rs
  17. 0
      asm/src/parse/parse_instr.rs
  18. 11
      asm/src/parse/parse_op.rs
  19. 0
      asm/src/parse/parse_routines.rs
  20. 0
      asm/src/parse/sexp_expect.rs
  21. 0
      asm/src/patches/mod.rs
  22. 0
      asm/src/patches/sexp_is_a.rs
  23. 0
      asm/src/patches/try_remove.rs
  24. 15
      crsn/Cargo.toml
  25. 36
      crsn/src/main.rs
  26. 97
      csn_asm/src/data/mod.rs
  27. 13
      runtime/Cargo.toml
  28. 98
      runtime/src/exec/mod.rs
  29. 59
      runtime/src/fault.rs
  30. 150
      runtime/src/frame.rs
  31. 19
      runtime/src/lib.rs
  32. 91
      runtime/src/mlock.rs
  33. 24
      runtime/src/program.rs
  34. 65
      runtime/src/run_thread.rs
  35. 47
      runtime/src/span.rs
  36. 317
      runtime/src/sparse.rs

170
Cargo.lock generated

@ -7,7 +7,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
[[package]]
name = "csn_asm"
name = "asm"
version = "0.1.0"
dependencies = [
"anyhow",
@ -15,6 +15,112 @@ dependencies = [
"thiserror",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "chrono"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "942f72db697d8767c22d46a598e01f2d3b475501ea43d0db4f16d90259182d0b"
dependencies = [
"num-integer",
"num-traits",
"time",
]
[[package]]
name = "colored"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59"
dependencies = [
"atty",
"lazy_static",
"winapi",
]
[[package]]
name = "crsn"
version = "0.1.0"
dependencies = [
"anyhow",
"asm",
"log",
"runtime",
"simple_logger",
"thiserror",
]
[[package]]
name = "hermit-abi"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151"
dependencies = [
"libc",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235"
[[package]]
name = "log"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
dependencies = [
"cfg-if",
]
[[package]]
name = "num-integer"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
dependencies = [
"autocfg",
]
[[package]]
name = "proc-macro2"
version = "1.0.21"
@ -33,12 +139,35 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "runtime"
version = "0.1.0"
dependencies = [
"anyhow",
"asm",
"log",
"thiserror",
]
[[package]]
name = "sexp"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8fa7ac9df84000b0238cf497cb2d3056bac2ff2a7d8cf179d2803b4b58571f"
[[package]]
name = "simple_logger"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13a53ed2efd04911c8280f2da7bf9abd350c931b86bc7f9f2386fbafbf525ff9"
dependencies = [
"atty",
"chrono",
"colored",
"log",
"winapi",
]
[[package]]
name = "syn"
version = "1.0.41"
@ -70,8 +199,47 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

@ -1,5 +1,6 @@
[workspace]
members = [
"csn_asm",
"asm",
"runtime",
"crsn"
]

@ -1,5 +1,5 @@
[package]
name = "csn_asm"
name = "asm"
version = "0.1.0"
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
edition = "2018"

@ -6,46 +6,41 @@ use std::borrow::Cow;
pub type DebugMsg = Cow<'static, str>;
/// Immediate value
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct Value(pub i64);
pub type Value = u64;
impl From<i64> for Value {
fn from(n: i64) -> Self {
Self(n)
}
pub fn is_positive(val : Value) -> bool {
0 == (val & 0x8000_0000_0000_0000)
}
impl Display for Value {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "{:#010x}", self.0)
} else {
write!(f, "{}", self.0)
}
}
pub fn is_negative(val : Value) -> bool {
0 != (val & 0x8000_0000_0000_0000)
}
impl Value {
pub fn as_u64(self) -> u64 {
u64::from_ne_bytes(self.0.to_ne_bytes())
}
pub fn as_u32(self) -> Option<u32> {
u32::try_from(self.as_u64()).ok()
}
pub fn as_i32(self) -> Option<i32> {
i32::try_from(self.0).ok()
}
pub fn as_signed(val : Value) -> i64 {
i64::from_ne_bytes(val.to_ne_bytes())
}
/// Immediate address
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
pub struct Addr(pub u64);
impl Addr {
pub fn advance(&mut self, add: i64) {
if add < 0 {
self.0 = self.0.wrapping_sub(-add as u64);
} else {
self.0 = self.0.wrapping_add(add as u64);
}
}
}
impl Display for Addr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "@{:#010x}", self.0)
if self.0 > 0x7fff_ffff_ffff_ffff {
write!(f, "{}", i64::from_ne_bytes(self.0.to_ne_bytes()))
} else {
write!(f, "{:#010x}", self.0)
}
}
}
@ -55,6 +50,12 @@ impl From<u64> for Addr {
}
}
impl From<Addr> for u64 {
fn from(addr: Addr) -> Self {
addr.0
}
}
/// Label name
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Label {

@ -58,4 +58,8 @@ impl Mask {
pub fn as_bitmask(self) -> u64 {
((1 << self.len) - 1) << self.offset
}
pub fn is_default(self) -> bool {
self == Self::default()
}
}

@ -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, ")")
}
}

@ -7,8 +7,8 @@ pub enum Cond {
NotEqual,
Zero,
NotZero,
Less,
LessOrEqual,
Lower,
LowerOrEqual,
Greater,
GreaterOrEqual,
Positive,
@ -28,8 +28,8 @@ impl Display for Cond {
Cond::NotEqual => "ne",
Cond::Zero => "z",
Cond::NotZero => "nz",
Cond::Less => "lt",
Cond::LessOrEqual => "le",
Cond::Lower => "lt",
Cond::LowerOrEqual => "le",
Cond::Greater => "gt",
Cond::GreaterOrEqual => "ge",
Cond::Positive => "pos",
@ -64,10 +64,10 @@ impl Not for Cond {
Cond::NotOverflow => Cond::Overflow,
Cond::NotCarry => Cond::Carry,
Cond::Less => Cond::GreaterOrEqual,
Cond::Greater => Cond::LessOrEqual,
Cond::LessOrEqual => Cond::Greater,
Cond::GreaterOrEqual => Cond::Less,
Cond::Lower => Cond::GreaterOrEqual,
Cond::Greater => Cond::LowerOrEqual,
Cond::LowerOrEqual => Cond::Greater,
Cond::GreaterOrEqual => Cond::Lower,
}
}
}

@ -80,7 +80,7 @@ pub fn lower(ops: Vec<HLOp>) -> Result<Vec<Op>, Error> {
HLOp::Jump(target) => {
if let Some(dest) = label_positions.get(&target) {
let skip = *dest as isize - n as isize + skipped;
cleaned.push(Op::Skip(Rd::new(SrcDisp::Immediate(Value(skip as i64)))));
cleaned.push(Op::Skip(Rd::new(SrcDisp::Immediate(skip as Value))));
} else {
return Err(Error::Asm(AsmError::LabelNotDefined(target)));
}
@ -88,7 +88,7 @@ pub fn lower(ops: Vec<HLOp>) -> Result<Vec<Op>, Error> {
HLOp::JumpIf(cond, target) => {
if let Some(dest) = label_positions.get(&target) {
let skip = *dest as isize - n as isize + skipped;
cleaned.push(Op::SkipIf(cond, Rd::new(SrcDisp::Immediate(Value(skip as i64)))));
cleaned.push(Op::SkipIf(cond, Rd::new(SrcDisp::Immediate(skip as Value))));
} else {
return Err(Error::Asm(AsmError::LabelNotDefined(target)));
}

@ -22,6 +22,8 @@ pub enum HLOp {
/// A low level instruction
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Op {
/// Do nothing
Nop,
/// Mark a far jump target (can be jumped to from another routine).
/// This label is preserved in optimized code.
FarLabel(Label),
@ -48,7 +50,8 @@ pub enum Op {
/// Copy a value
Mov(Wr, Rd),
/// Compare two values and set conditional flags
/// Compare two values and set conditional flags.
/// If the values are identical, evaluate if they are zero, positive, or negative (the "tst" op)
Cmp(Rd, Rd),
// Increment a value
Inc(Wr),

@ -17,7 +17,7 @@ mod tests {
use crate::parse::{parse, parse_instructions};
use crate::instr::{HLOp, Op, Flatten, Instr, lower};
use crate::data::{Wr, DstDisp, Register, SrcDisp, Rd};
use crate::data::literal::{Value, Addr, Label};
use crate::data::literal::{Addr, Label};
use std::sync::atomic::AtomicU32;
use crate::instr::Cond;
@ -86,17 +86,17 @@ mod tests {
// (mov r15 7)
HLOp::L(Op::Mov(
Wr::new(DstDisp::Register(Register::Gen(15))),
Rd::new(SrcDisp::Immediate(Value(7))),
Rd::new(SrcDisp::Immediate(7)),
)),
// (mov r15 0xabcd)
HLOp::L(Op::Mov(
Wr::new(DstDisp::Register(Register::Gen(15))),
Rd::new(SrcDisp::Immediate(Value(0xabcd))),
Rd::new(SrcDisp::Immediate(0xabcd)),
)),
// (mov r7 0b11110000)
HLOp::L(Op::Mov(
Wr::new(DstDisp::Register(Register::Gen(7))),
Rd::new(SrcDisp::Immediate(Value(0b11110000))),
Rd::new(SrcDisp::Immediate(0b11110000)),
)),
// (mov r7 arg1)
HLOp::L(Op::Mov(
@ -250,7 +250,7 @@ mod tests {
)),
HLOp::Jump(Label::Numbered(0)),
HLOp::Label(Label::Numbered(1)),
HLOp::JumpIf(Cond::LessOrEqual, Label::Numbered(0)),
HLOp::JumpIf(Cond::LowerOrEqual, Label::Numbered(0)),
HLOp::L(Op::Mov(
Wr::new(DstDisp::Register(Register::Gen(0))),
Rd::new(SrcDisp::Register(Register::Gen(0))),
@ -317,8 +317,8 @@ mod tests {
Wr::new(DstDisp::Register(Register::Gen(0))),
Rd::new(SrcDisp::Register(Register::Gen(0))),
),
Op::Skip(Rd::new(SrcDisp::Immediate(Value(-1)))),
Op::Skip(Rd::new(SrcDisp::Immediate(Value(-2)))),
Op::Skip(Rd::new(SrcDisp::Immediate(-1))),
Op::Skip(Rd::new(SrcDisp::Immediate(-2))),
Op::Mov(
Wr::new(DstDisp::Register(Register::Gen(0))),
Rd::new(SrcDisp::Register(Register::Gen(0))),
@ -327,8 +327,8 @@ mod tests {
Wr::new(DstDisp::Register(Register::Gen(0))),
Rd::new(SrcDisp::Register(Register::Gen(0))),
),
Op::Skip(Rd::new(SrcDisp::Immediate(Value(-5)))),
Op::SkipIf(Cond::Equal, Rd::new(SrcDisp::Immediate(Value(-6)))),
Op::Skip(Rd::new(SrcDisp::Immediate(-5))),
Op::SkipIf(Cond::Equal, Rd::new(SrcDisp::Immediate(-6))),
Op::Barrier(Some("Routine \"foo\" overrun".into())),
], cleaned);
}

@ -22,8 +22,8 @@ pub fn parse_cond(text: &str) -> Result<Cond, Error> {
"ne" | "<>" | "!=" | "≠" => Cond::NotEqual,
"z" | "0" => Cond::Zero,
"nz" | "<>0" | "!0" => Cond::NotZero,
"lt" | "<" => Cond::Less,
"le" | "<=" | "≤" => Cond::LessOrEqual,
"lt" | "<" => Cond::Lower,
"le" | "<=" | "≤" => Cond::LowerOrEqual,
"gt" | ">" => Cond::Greater,
"ge" | ">=" | "≥" => Cond::GreaterOrEqual,
"pos" | "+" | ">0" => Cond::Positive,

@ -23,7 +23,7 @@ pub fn parse_data_disp(tok: Option<Sexp>) -> Result<DataDisp, Error> {
match &tok {
Sexp::Atom(Atom::I(val)) => {
Ok(DataDisp::Immediate(Value(*val)))
Ok(DataDisp::Immediate(unsafe { std::mem::transmute(*val) }))
},
Sexp::Atom(Atom::S(s)) => {
if let Some(reference) = s.strip_prefix('@') {
@ -34,7 +34,7 @@ pub fn parse_data_disp(tok: Option<Sexp>) -> Result<DataDisp, Error> {
Ok(DataDisp::RegisterPtr(reg::parse_reg(reference)?))
}
} else if s.starts_with(|c : char| c.is_ascii_digit()) {
Ok(DataDisp::Immediate(Value(parse_i64(s)?)))
Ok(DataDisp::Immediate(unsafe { std::mem::transmute(parse_i64(s)?) }))
} else {
Ok(DataDisp::Register(reg::parse_reg(s)?))
}

@ -40,17 +40,17 @@ pub fn parse_op(keyword: &str, far : bool, mut arg_tokens: impl Iterator<Item=Se
HLOp::L(Op::Routine(dest))
}
"sk" => {
"s" => {
HLOp::L(Op::Skip(parse_rd(arg_tokens.next())?))
}
"csk" => {
"sif" => {
let cond = parse_cond(&expect_string_atom(arg_tokens.next())?)?;
let offs = parse_rd(arg_tokens.next())?;
HLOp::L(Op::SkipIf(cond, offs))
}
"cj" => {
"jif" => {
let cond = parse_cond(&expect_string_atom(arg_tokens.next())?)?;
let dest = parse_label(arg_tokens.next())?;
HLOp::JumpIf(cond, dest)
@ -84,6 +84,11 @@ pub fn parse_op(keyword: &str, far : bool, mut arg_tokens: impl Iterator<Item=Se
))
}
"tst" => {
let arg = parse_rd(arg_tokens.next())?;
HLOp::L(Op::Cmp(arg, arg))
}
"inc" => {
HLOp::L(Op::Inc(
parse_wr(arg_tokens.next())?

@ -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…
Cancel
Save