exec
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user