From 2be8445a9f65ca8028b147d051454e8d36f3be76 Mon Sep 17 00:00:00 2001 From: Stephen D <webmaster@scd31.com> Date: Sun, 26 Nov 2023 13:25:38 -0400 Subject: [PATCH] remove lots of expensive stack allocations --- src/buffer.rs | 68 +++++++++++++++++++++++++++++++++++------ src/interleaver.rs | 29 +++++++++++------- src/ldpc.rs | 61 ++++++++++++++++--------------------- src/packet.rs | 75 ++++++++++++++++++++++++++-------------------- src/whitener.rs | 6 ++-- 5 files changed, 148 insertions(+), 91 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index a822d66..13db9ee 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,24 +1,33 @@ -use core::{borrow::Borrow, ops::Deref}; +use core::ops::{Deref, DerefMut}; -pub(crate) struct BufferOverflow; +#[derive(Debug)] +pub struct BufferOverflow; -pub(crate) struct Buffer<'a, const N: usize> { +#[derive(Debug)] +pub struct Buffer<'a, const N: usize> { data: &'a mut [u8; N], i: usize, } impl<'a, const N: usize> Buffer<'a, N> { + /// Constructs a new `Buffer`. + /// `data` is the backing array. + /// `i` is the number of elements in `data` that contain data (and should thus be exposed by `Buffer`) pub fn new(data: &'a mut [u8; N], i: usize) -> Self { + assert!(i <= data.len()); + Self { data, i } } - /*pub fn as_slice(&self) -> &'a [u8] { - &self.data[..self.i] - } + pub fn new_full(data: &'a mut [u8; N]) -> Self { + let i = data.len(); + + Self::new(data, i) + } - pub fn as_slice_mut(&mut self) -> &'a [u8] { - &mut self.data[..self.i] - }*/ + pub fn new_empty(data: &'a mut [u8; N]) -> Self { + Self::new(data, 0) + } pub const fn remaining_capacity(&self) -> usize { N - self.i @@ -35,6 +44,10 @@ impl<'a, const N: usize> Buffer<'a, N> { Ok(()) } + pub fn push(&mut self, v: u8) { + self.try_push(v).unwrap(); + } + pub fn try_extend_from_slice(&mut self, other: &[u8]) -> Result<(), BufferOverflow> { if self.remaining_capacity() < other.len() { return Err(BufferOverflow); @@ -46,6 +59,10 @@ impl<'a, const N: usize> Buffer<'a, N> { Ok(()) } + pub fn extend(&mut self, other: &[u8]) { + self.try_extend_from_slice(other).unwrap(); + } + pub fn pop(&mut self) -> Option<u8> { if self.i == 0 { return None; @@ -55,6 +72,33 @@ impl<'a, const N: usize> Buffer<'a, N> { Some(self.data[self.i]) } + + pub fn truncate(&mut self, new_len: usize) { + assert!(self.i >= new_len); + + self.i = new_len; + } + + pub fn drain(&mut self, start: usize, end: usize) { + assert!(end >= start); + assert!(end <= self.i); + let delta = end - start; + let surplus = self.len() - end; + + for i in start..(start + surplus) { + self[i] = self[i + delta]; + } + + self.i -= delta; + } + + pub fn clone_backing<'b>(&self, buf: &'b mut [u8; N]) -> Buffer<'b, N> { + let mut out = Buffer::new_empty(buf); + + out.extend(self); + + out + } } impl<'a, const N: usize> From<&'a mut [u8; N]> for Buffer<'a, N> { @@ -70,3 +114,9 @@ impl<'a, const N: usize> Deref for Buffer<'a, N> { &self.data[..self.i] } } + +impl<'a, const N: usize> DerefMut for Buffer<'a, N> { + fn deref_mut(&mut self) -> &mut [u8] { + &mut self.data[..self.i] + } +} diff --git a/src/interleaver.rs b/src/interleaver.rs index 5661bb4..5efeaaf 100644 --- a/src/interleaver.rs +++ b/src/interleaver.rs @@ -1,11 +1,13 @@ -use arrayvec::{ArrayVec, CapacityError}; use bitvec::prelude::*; -use crate::error::EncodeError; +use crate::{ + buffer::{Buffer, BufferOverflow}, + error::EncodeError, +}; pub(crate) fn interleave<const N: usize>( data: &[u8], - out: &mut ArrayVec<u8, N>, + out: &mut Buffer<N>, ) -> Result<(), EncodeError> { let bv = data.view_bits::<Msb0>(); @@ -33,10 +35,10 @@ pub(crate) fn interleave<const N: usize>( pub(crate) fn uninterleave<const N: usize>( data: &[u8], -) -> Result<ArrayVec<u8, N>, CapacityError<u8>> { + out: &mut Buffer<N>, +) -> Result<(), BufferOverflow> { let bv = data.view_bits::<Msb0>(); - let mut out: ArrayVec<u8, N> = ArrayVec::new(); for _ in 0..data.len() { out.try_push(0)?; } @@ -55,7 +57,7 @@ pub(crate) fn uninterleave<const N: usize>( } } - Ok(out) + Ok(()) } #[cfg(test)] @@ -64,17 +66,22 @@ mod tests { #[test] fn interleaver_works() { - let orig = - ArrayVec::try_from([0x84, 0x73, 0x12, 0xA3, 0xFF, 0x00, 0xC2, 0x1B, 0x77]).unwrap(); - let mut interleaved: ArrayVec<u8, 10> = ArrayVec::new(); + let mut data = [0x84, 0x73, 0x12, 0xA3, 0xFF, 0x00, 0xC2, 0x1B, 0x77]; + let orig = Buffer::new_full(&mut data); + + let mut interleaved = [0; 10]; + let mut interleaved = Buffer::new(&mut interleaved, 0); interleaved.push(b'H'); + interleave(&orig, &mut interleaved).unwrap(); let expected = [b'H', 0xCD, 0xB5, 0xDB, 0x2A, 0x0A, 0x52, 0x0C, 0x89, 0x4F]; assert_eq!(expected, interleaved[..]); - let uninterleaved = uninterleave(&interleaved[1..]).unwrap(); + let mut uninterleaved = [0; 10]; + let mut uninterleaved = Buffer::new(&mut uninterleaved, 0); + uninterleave(&interleaved[1..], &mut uninterleaved).unwrap(); - assert_eq!(orig, uninterleaved); + assert_eq!(*orig, *uninterleaved); } } diff --git a/src/ldpc.rs b/src/ldpc.rs index 0c5c07e..a3cda75 100644 --- a/src/ldpc.rs +++ b/src/ldpc.rs @@ -1,4 +1,3 @@ -use arrayvec::ArrayVec; use labrador_ldpc::LDPCCode; use crate::{buffer::Buffer, error::EncodeError}; @@ -84,7 +83,7 @@ pub(crate) fn encode<const N: usize>(data: &mut Buffer<N>) -> Result<(), EncodeE Ok(()) } -pub(crate) fn decode<const N: usize>(data_av: &mut ArrayVec<u8, N>) -> Option<()> { +pub(crate) fn decode<const N: usize>(data_av: &mut Buffer<N>) -> Option<()> { if data_av.len() < 2 { return None; } @@ -157,25 +156,18 @@ pub(crate) fn decode<const N: usize>(data_av: &mut ArrayVec<u8, N>) -> Option<() #[cfg(test)] mod tests { use super::*; - use arrayvec::ArrayVec; #[test] fn len_test() { // from the example in the docs - let mut data: ArrayVec<u8, 8191> = ArrayVec::new(); + let mut buf = [0; 8191]; + let mut data = Buffer::new_empty(&mut buf); for _ in 0..41 { - data.extend( - b"Example packet data wueirpqwerwrywqoeiruy29346129384761" - .iter() - .cloned(), - ); + data.extend(b"Example packet data wueirpqwerwrywqoeiruy29346129384761"); } - data.extend( - b"Example packet data wueirpqwerwrywqoeiru346129384761" - .iter() - .cloned(), - ); + data.extend(b"Example packet data wueirpqwerwrywqoeiru346129384761"); + assert_eq!(2349, data.len()); encode(&mut data).unwrap(); @@ -185,51 +177,50 @@ mod tests { #[test] fn basic_encode_decode_short() { - let mut data: ArrayVec<u8, 32> = ArrayVec::new(); + let mut buf = [0; 32]; + let mut buf2 = [0; 32]; + let mut data = Buffer::new_empty(&mut buf); data.try_extend_from_slice(b"Hello world!").unwrap(); - let orig = data.clone(); + let orig = data.clone_backing(&mut buf2); encode(&mut data).unwrap(); decode(&mut data).unwrap(); - assert_eq!(orig, data); + assert_eq!(*orig, *data); } #[test] fn basic_encode_decode() { - let mut data: ArrayVec<u8, 8191> = ArrayVec::new(); + let mut buf = [0; 8191]; + let mut buf2 = [0; 8191]; + let mut data = Buffer::new_empty(&mut buf); for _ in 0..50 { - data.extend( - b"This is a test packet. jsalksjd093809324JASLD:LKD*#$)(*#@)" - .iter() - .cloned(), - ); + data.extend(b"This is a test packet. jsalksjd093809324JASLD:LKD*#$)(*#@)"); } - let orig = data.clone(); + let orig = data.clone_backing(&mut buf2); encode(&mut data).unwrap(); - assert_ne!(orig, data); + assert_ne!(*orig, *data); decode(&mut data).unwrap(); - assert_eq!(orig, data); + assert_eq!(*orig, *data); } #[test] fn encode_decode_with_bit_flips() { - let mut data: ArrayVec<u8, 8191> = ArrayVec::new(); + let mut buf = [0; 8191]; + let mut buf2 = [0; 8191]; + let mut data = Buffer::new_empty(&mut buf); + for _ in 0..50 { - data.extend( - b"jsalksjd093809324JASLD:LKD*#$)(*#@) Another test packet" - .iter() - .cloned(), - ); + data.extend(b"jsalksjd093809324JASLD:LKD*#$)(*#@) Another test packet"); } - let orig = data.clone(); + let orig = data.clone_backing(&mut buf2); encode(&mut data).unwrap(); - assert_ne!(orig, data); + assert_ne!(*orig, *data); data[234] ^= 0x55; data[0] ^= 0xAA; @@ -237,6 +228,6 @@ mod tests { decode(&mut data).unwrap(); - assert_eq!(orig, data); + assert_eq!(*orig, *data); } } diff --git a/src/packet.rs b/src/packet.rs index f669b1b..7565205 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -1,6 +1,5 @@ use core::fmt::Debug; -use arrayvec::{ArrayVec, CapacityError}; use crc::{Crc, CRC_16_IBM_SDLC}; use crate::{ @@ -31,7 +30,7 @@ macro_rules! uniq_whisker { return Err(EncodeError::DuplicateData); } - try_lock(&mut self.data, |data| { + try_lock(&mut self.buf, |data| { let mut buf = [0; 256]; // safe to unwrap since we know we have enough space let out = w.encode(&mut buf).unwrap(); @@ -67,7 +66,7 @@ macro_rules! poly_whisker { } pub fn [<add_ $t:lower>](&mut self, w: $t) -> Result<(), EncodeError> { - try_lock(self.buf.as_slice_mut(), |data| { + try_lock(&mut self.buf, |data| { let mut buf = [0; 256]; // safe to unwrap since we know we have enough space let out = w.encode(&mut buf).unwrap(); @@ -148,7 +147,7 @@ impl<'a, const N: usize> Packet<'a, N> { /// Directly after the CRC block in The Pipeline /// Expects bytes in the `buf` /// `buf` is used as the backing buffer for the Packet - pub fn semi_decode(buf: Buffer<'a, N>) -> Result<Self, DecodeError> { + pub fn semi_decode(mut buf: Buffer<'a, N>) -> Result<Self, DecodeError> { let crc1 = buf.pop().ok_or(DecodeError::UnexpectedEndOfInput)?; let crc0 = buf.pop().ok_or(DecodeError::UnexpectedEndOfInput)?; let crc_expected = u16::from_le_bytes([crc0, crc1]); @@ -163,34 +162,34 @@ impl<'a, const N: usize> Packet<'a, N> { /// Encodes packet for transmission on the air. /// Includes the data length L, but does not include the preamble or sync word. - // TODO buffer-ify this - pub fn fully_encode(self) -> Result<ArrayVec<u8, N>, EncodeError> { + pub fn fully_encode(self, out: &mut Buffer<N>) -> Result<(), EncodeError> { let mut data = self.semi_encode().map_err(|(err, _)| err)?; whitener::whiten(&mut data); ldpc::encode(&mut data)?; - let mut out: ArrayVec<u8, N> = ArrayVec::new(); + // safe to unwrap - length must be below 8191 out.try_extend_from_slice(&u16::try_from(data.len()).unwrap().to_le_bytes()) .map_err(|_| EncodeError::CatsOverflow)?; - interleaver::interleave(&data, &mut out)?; + interleaver::interleave(&data, out)?; - Ok(out) + Ok(()) } /// Decodes packet that was received over the air. /// Packet shouldn't have preamble, sync word, or data langth L. /// Expects bytes in the `buf` /// `buf` is used as the backing buffer for the Packet - pub fn fully_decode(buf: Buffer<'a, N>) -> Result<Self, DecodeError> { - let mut data = interleaver::uninterleave(buf).map_err(|_| DecodeError::Overflow)?; - ldpc::decode(&mut data).ok_or(DecodeError::LdpcError)?; - whitener::whiten(&mut data); + pub fn fully_decode(data: &[u8], buf: &'a mut [u8; N]) -> Result<Self, DecodeError> { + let mut buf = Buffer::new_empty(buf); + interleaver::uninterleave(data, &mut buf).map_err(|_| DecodeError::Overflow)?; + ldpc::decode(&mut buf).ok_or(DecodeError::LdpcError)?; + whitener::whiten(&mut buf); - Self::semi_decode(data) + Self::semi_decode(buf) } pub fn iter(&self) -> ValidatedWhiskerIter { - ValidatedWhiskerIter::new(&self.data) + ValidatedWhiskerIter::new(&self.buf) } uniq_whisker!(Identification); @@ -200,7 +199,7 @@ impl<'a, const N: usize> Packet<'a, N> { poly_whisker!(Destination); poly_whisker!(Arbitrary); - pub fn comment<'b>(&self, buf: &'b mut [u8]) -> Result<&'a str, CommentError> { + pub fn comment<'b>(&self, buf: &'b mut [u8]) -> Result<&'b str, CommentError> { let iter = self.iter().filter_map(|w| match w { Whisker::Comment(c) => Some(c), _ => None, @@ -232,7 +231,7 @@ impl<'a, const N: usize> Packet<'a, N> { return Err(EncodeError::DuplicateData); } - try_lock(&mut self.data, |data| { + try_lock(&mut self.buf, |data| { let mut comment = comment.as_bytes(); while comment.len() > 255 { @@ -269,12 +268,12 @@ impl<'a, const N: usize> Packet<'a, N> { fn clear_by_type(&mut self, whisker_type: u8, all: bool) { let mut i = 0; - while i < self.data.len() { - let t = self.data[i]; - let step = usize::from(self.data[i + 1]) + 2; + while i < self.buf.len() { + let t = self.buf[i]; + let step = usize::from(self.buf[i + 1]) + 2; if t == whisker_type { - self.data.drain(i..(i + step)); + self.buf.drain(i, i + step); if !all { return; @@ -289,7 +288,7 @@ impl<'a, const N: usize> Packet<'a, N> { impl<'a, const N: usize> Debug for Packet<'a, N> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_list() - .entries(ValidatedWhiskerIter::new(self.buf.as_slice())) + .entries(ValidatedWhiskerIter::new(&self.buf)) .finish() } } @@ -314,7 +313,7 @@ fn try_lock<'a, const N: usize, T, E, F: Fn(&mut Buffer<'a, N>) -> Result<T, E>> #[cfg(test)] mod tests { - use arrayvec::ArrayString; + use arrayvec::{ArrayString, ArrayVec}; use super::*; @@ -323,7 +322,8 @@ mod tests { let d1 = Destination::new(false, 7, "CALL1", 23).unwrap(); let d2 = Destination::new(true, 23, "CALL2", 2).unwrap(); - let mut packet: Packet<1024> = Packet::default(); + let mut buf = [0; 1024]; + let mut packet: Packet<1024> = Packet::new(&mut buf); packet.add_destination(d1.clone()).unwrap(); packet.add_destination(d2.clone()).unwrap(); @@ -335,7 +335,8 @@ mod tests { #[test] fn route_clear() { - let mut p: Packet<1024> = Packet::default(); + let mut buf = [0; 1024]; + let mut p: Packet<1024> = Packet::new(&mut buf); p.add_identification(Identification::new(123, "call", 43).unwrap()) .unwrap(); let mut r = Route::new(8); @@ -358,7 +359,8 @@ mod tests { fn semi_e2e() { let comment = "Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker."; - let mut packet = Packet::<2048>::default(); + let mut buf = [0; 2048]; + let mut packet = Packet::new(&mut buf); packet .add_identification(Identification { icon: 123, @@ -396,7 +398,8 @@ mod tests { fn full_e2e() { let comment = "Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker."; - let mut packet = Packet::<4096>::default(); + let mut buf = [0; 4096]; + let mut packet = Packet::new(&mut buf); packet .add_identification(Identification { icon: 123, @@ -414,13 +417,16 @@ mod tests { }); assert!(matches!(res, Err(EncodeError::DuplicateData))); - let mut fully = packet.fully_encode().unwrap(); + let mut buf2 = [0; 4096]; + let mut fully = Buffer::new_empty(&mut buf2); + packet.fully_encode(&mut fully).unwrap(); fully[40] ^= 0x55; fully[844] ^= 0x7B; // exclude length - let packet2: Packet<8191> = Packet::fully_decode(&fully[2..]).unwrap(); + let mut buf3 = [0; 8191]; + let packet2: Packet<8191> = Packet::fully_decode(&fully[2..], &mut buf3).unwrap(); assert_eq!( Identification { icon: 123, @@ -487,7 +493,8 @@ mod tests { 118, 118, 118, 118, 118, 118, 118, 0, 0, 0, 0, 96, 96, ]; - let _ = Packet::<1024>::fully_decode(&data); + let mut buf = [0; 1024]; + let _ = Packet::<1024>::fully_decode(&data, &mut buf); } #[test] @@ -535,13 +542,15 @@ mod tests { ) .unwrap(); - let _ = Packet::<1024>::semi_decode(data); + let mut buf = [0; 1024]; + let _ = Packet::<1024>::fully_decode(&data, &mut buf); } #[test] fn decode_fuzz_tests() { - let data = ArrayVec::try_from(&[4, 0, 0, 0][..]).unwrap(); + let data = [4, 0, 0, 0]; - let _ = Packet::<1024>::decode(data); + let mut buf = [0; 1024]; + let _ = Packet::<1024>::fully_decode(&data, &mut buf); } } diff --git a/src/whitener.rs b/src/whitener.rs index 53d846d..79d21d9 100644 --- a/src/whitener.rs +++ b/src/whitener.rs @@ -14,9 +14,9 @@ mod tests { #[test] fn basic() { - let mut data: ArrayVec<u8, 512> = - ArrayVec::try_from(&b"Hello world! The quick brown fox jumped over the lazy dog"[..]) - .unwrap(); + let mut data = [0; 64]; + data[0..57] + .clone_from_slice(&b"Hello world! The quick brown fox jumped over the lazy dog"[..]); let orig = data.clone(); whiten(&mut data); -- GitLab