Skip to content

Commit

Permalink
Minimalist documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
haxelion committed Dec 20, 2020
1 parent 3d9c4c9 commit 8f55fd6
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 18 deletions.
33 changes: 31 additions & 2 deletions src/auto.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
//! This module contains an automatically managed bit vector type. Depending on the required
//! capacity, it might use a fixed capacity implementation to avoid unnecessary dynamic memory
//! allocations, or it might use a dynamic capacity implementation if the capacity of fixed
//! implementations is exceeded.
//!
//! While avoiding memory allocation might improve performance, there is a slight performance cost
//! due to the dynamic dispatch and extra capacity checks. The net effect will depend on the exact
//! application. It is designed for the case where most bit vector are expected to fit inside
//! fixed capacity implementations but some outliers might not.

use std::convert::{From, TryFrom};
use std::fmt::{Binary, Display, LowerHex, Octal, UpperHex};
use std::mem::size_of;
Expand All @@ -10,22 +20,37 @@ use crate::dynamic::BVN;
#[allow(unused_imports)]
use crate::fixed::{BV8, BV16, BV32, BV64, BV128};

// Choose a fixed BV type which will match the size of BVN inside the enum

// Choose a fixed BV type which should match the size of BVN inside the enum
#[cfg(target_pointer_width = "16")]
type BVF = BV32;
#[cfg(target_pointer_width = "32")]
type BVF = BV64;
#[cfg(target_pointer_width = "64")]
type BVF = BV128;

/// A bit vector with automatic capacity management.
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum BV {
Fixed(BVF),
Dynamic(BVN)
}

impl BV {
/// Create a bit vector of length 0 but with enough capacity to store `capacity` bits.
pub fn with_capacity(capacity: usize) -> Self {
if capacity <= BVF::CAPACITY {
BV::Fixed(BVF::zeros(0))
}
else {
BV::Dynamic(BVN::with_capacity(capacity))
}
}

/// Reserve will reserve room for at least `additional` bits in the bit vector. The actual
/// length of the bit vector will stay unchanged, see [`BitVector::resize`] to change the actual
/// length of the bit vector.
///
/// Calling this method might cause the storage to become dynamically allocated.
pub fn reserve(&mut self, additional: usize) {
match self {
&mut BV::Fixed(ref b) => {
Expand All @@ -39,6 +64,10 @@ impl BV {
}
}

/// Drop as much excess capacity as possible in the bit vector to fit the current length.
///
/// Calling this method might cause the implementation to drop unnecessary dynamically
/// allocated memory.
pub fn shrink_to_fit(&mut self) {
if let BV::Dynamic(b) = self {
if b.len() <= BVF::CAPACITY {
Expand Down
28 changes: 25 additions & 3 deletions src/dynamic/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
//! This module contains a dynamic capacity bit vector implementation using a dynamically allocated
//! integer array as storage.
//!
//! As the capacity is dynamic, performing operations exceeding the current capacity will result in
//! a reallocation of the internal array.

use std::cmp::Ordering;
use std::convert::{From, TryFrom};
use std::fmt::{Binary, Display, LowerHex, Octal, UpperHex};
Expand All @@ -14,6 +20,7 @@ mod adapter;

use adapter::USizeStream;

/// A bit vector with dynamic capacity.
#[derive(Clone, Debug)]
pub struct BVN {
data: Box<[usize]>,
Expand All @@ -38,18 +45,33 @@ impl BVN {
usize::MAX.checked_shr((Self::BIT_UNIT - length) as u32).unwrap_or(0)
}

/// Allocate a bit vector of length 0 but with enough capacity to store `capacity` bits.
pub fn with_capacity(capacity: usize) -> Self {
let data: Vec<usize> = repeat(0usize).take(Self::capacity_from_bit_len(capacity)).collect();
BVN {
data: data.into_boxed_slice(),
length: 0
}
}

/// Reserve will reserve room for at least `additional` bits in the bit vector. The actual
/// length of the bit vector will stay unchanged, see [`BitVector::resize`] to change the actual
/// length of the bit vector.
///
/// The underlying allocator might reserve additional capacity.
pub fn reserve(&mut self, additional: usize) {
let new_length = self.length + additional;
if Self::capacity_from_bit_len(new_length) > self.data.len() {
let new_capacity = self.length + additional;
if Self::capacity_from_bit_len(new_capacity) > self.data.len() {
// TODO: in place reallocation
let mut new_data: Vec<usize> = repeat(0usize).take(Self::capacity_from_bit_len(new_length)).collect();
let mut new_data: Vec<usize> = repeat(0usize).take(Self::capacity_from_bit_len(new_capacity)).collect();
for i in 0..self.data.len() {
new_data[i] = self.data[i];
}
self.data = new_data.into_boxed_slice();
}
}

/// Drop as much excess capacity as possible in the bit vector to fit the current length.
pub fn shrink_to_fit(&mut self) {
if Self::capacity_from_bit_len(self.length) < self.data.len() {
// TODO: in place reallocation
Expand Down
20 changes: 10 additions & 10 deletions src/fixed.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
//! This module contains fixed capacity bit vector implementations using a single integer as
//! storage. Each implementations is named `BV` followed by a number corresponding to its max bit
//! capacity.
//!
//! Performing operations resulting in exceeding the bit vector capacity will result in
//! a runtime panic. See [`crate::dynamic`] for dynamic capacity bit vector implementations.

use std::convert::{From, TryFrom};
use std::fmt::{Binary, Display, LowerHex, Octal, UpperHex};
use std::io::{Read, Write};
Expand All @@ -9,11 +16,6 @@ use std::ops::{Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor,

use crate::{Bit, BitVector, Endianness};

// Beware! Here be hardcore macro magic!

/// Implement the binary operation (`$trait`, `$method`) for `$t` backed by the underlying type `$st`
/// and the rhs `$rhs`.
/// Also implement the various borrowed versions
macro_rules! impl_op { ($t:ident, $st:ty, $rhs:ty, $trait:ident, $method:ident) => {
impl $trait<$rhs> for $t {
type Output = $t;
Expand Down Expand Up @@ -60,9 +62,6 @@ macro_rules! impl_op { ($t:ident, $st:ty, $rhs:ty, $trait:ident, $method:ident)
}
}}

/// Implement the assign variant of the binary operation (`$trait`, `$method`) for `$t`
/// backed by the underlying type `$st` and the rhs `$t2`.
/// Also implement the various borrowed versions.
macro_rules! impl_op_assign { ($t:ident, $st:ty, $rhs:ty, $trait:ident, $method:ident) => {
impl $trait<$rhs> for $t {
fn $method(&mut self, rhs: $rhs) {
Expand Down Expand Up @@ -207,15 +206,16 @@ macro_rules! impl_shr_assign {($t:ident, {$($rhs:ty),+}) => {
/// and accepting for its operations a list of valid rhs BA types `$rhs`.
/// In addition, a list of sub storage type is also specified.
macro_rules! decl_bv { ($name:ident, $st:ty, {$(($rhs:ident, $sst:ty)),*}) => {
/// Fixed capacity bit vector backed by a single unsigned integer.
/// Operation exceding the underlying capacity will panic.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct $name {
data: Wrapping<$st>,
length: u8
}

/// Fixed capacity bit vector backed by a single unsigned integer.
/// Operation exceding the underlying capacity will panic.
impl $name {
/// Bit vector capacity.
pub const CAPACITY: usize = size_of::<$st>() * 8;

fn mask(length: usize) -> Wrapping<$st> {
Expand Down
30 changes: 27 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
//! Crate for manipulating bit vectors and doing arithmetic on arbitrary sized bit vectors.
//!
//! This crate emphasizes optimizing storage by providing alternative storage options.
//! The module [`fixed`] contains implementations using unsigned integers as storage and thus
//! has a fixed capacity. The module [`dynamic`] contains an implementation using dynamically
//! allocated array of integers as storage and therefore has a dynamic capacity. The module
//! [`auto`] contains an implementation capable of switching at runtime between a fixed or
//! dynamic capacity implementation to try to minimize dynamic memory allocations.
//! All of those implementations implement the [`BitVector`] trait and can be freely mixed together
//! and converted into each other.
//!
//! The different bit vector types represent a vector of bits where the bit at index 0 is the least
//! significant bit and the bit at index `.len() - 1` is the most significant bit. There is no
//! notion of endianness for the bit vector themselves, endianness is only involved when reading or
//! writing a bit vector to or from memory or storage.
//!
//! Arithmetic operation can be applied to bit vectors of different length. The result will always
//! have the length of the longest operand and the smallest operand will be zero extended for the
//! operation. This should match the most intuitive behavior in most cases except when manipulating
//! bit vector supposed to have a sign bit.

use std::fmt::{Binary, Display, Debug, LowerHex, UpperHex};
use std::io::{Read, Write};
use std::ops::Range;
Expand All @@ -9,8 +30,11 @@ pub mod auto;

use bit::Bit;

/// A enumeration representing the endianness of an I/O or memory operation.
pub enum Endianness {
/// Little Endian ordering: bytes are stored from the least significant byte to the most significant byte.
LE,
/// big Endian ordering: bytes are stored from the most significant byte to the least significant byte.
BE
}

Expand Down Expand Up @@ -41,14 +65,14 @@ pub trait BitVector: Sized + Clone + Debug + PartialEq + Eq + Display + Binary +
/// If the length is not a multiple of 8 bits, he highest weight bits will be padded with `'0'`.
fn to_vec(&self, endianness: Endianness) -> Vec<u8>;

/// Construct a bit vector by reading `num_bytes` bytes from a type implementing `Read` and
/// Construct a bit vector by reading `length` bits from a type implementing `Read` and
/// arrange them according to the specified `endianness`. If `length` is not a multiple of 8,
/// the bits remaining in the highest weight byte will be dropped.
/// the bits remaining in the most signigicant byte will be dropped.
/// Will panic if there is not enough capacity and it is a fixed variant.
fn read<R: Read>(reader: &mut R, length: usize, endianness: Endianness) -> std::io::Result<Self>;

/// Write the bit vector to a type implementing `Write` and according to the specified
/// `endianness`. If the length is not a multiple of 8 bits, he highest weight bits will be
/// `endianness`. If the length is not a multiple of 8 bits, the most significant byte will be
/// padded with `'0'`.
fn write<W: Write>(&self, writer: &mut W, endianness: Endianness) -> std::io::Result<()>;

Expand Down

0 comments on commit 8f55fd6

Please sign in to comment.