Skip to content

Commit fe4e6fe

Browse files
committed
feat(utils): add RingBuffer type
Add generic fixed size ring buffer type. The main difference from a standard `VecDequeue` is the use of `u32` as indexes. This shrinks the size of the ring buffer from 32 bytes for `VecDequeue` to 24 bytes for `RingBuffer`. Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent 445a035 commit fe4e6fe

File tree

3 files changed

+175
-1
lines changed

3 files changed

+175
-1
lines changed

src/vmm/src/devices/virtio/iov_ring_buffer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ pub enum IovRingBufferError {
7070
// Like that, the elements stored in the buffer are always laid out in contiguous virtual memory,
7171
// so making a slice out of them does not require any copies.
7272
#[derive(Debug)]
73-
pub(crate) struct IovRingBuffer {
73+
pub struct IovRingBuffer {
7474
iov_ptr: *mut iovec,
7575
start: usize,
7676
len: usize,

src/vmm/src/utils/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
pub mod byte_order;
66
/// Module with network related helpers
77
pub mod net;
8+
/// Module with `RingBuffer` struct
9+
pub mod ring_buffer;
810
/// Module with external libc functions
911
pub mod signal;
1012
/// Module with state machine
@@ -22,6 +24,15 @@ pub fn get_page_size() -> Result<usize, vmm_sys_util::errno::Error> {
2224
}
2325
}
2426

27+
/// Safely converts a u32 value to a usize value.
28+
/// This bypasses the Clippy lint check because we only support 64-bit platforms.
29+
#[cfg(target_pointer_width = "64")]
30+
#[inline]
31+
#[allow(clippy::cast_possible_truncation)]
32+
pub const fn u32_to_usize(num: u32) -> usize {
33+
num as usize
34+
}
35+
2536
/// Safely converts a u64 value to a usize value.
2637
/// This bypasses the Clippy lint check because we only support 64-bit platforms.
2738
#[cfg(target_pointer_width = "64")]

src/vmm/src/utils/ring_buffer.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use super::u32_to_usize;
5+
6+
/// Simple ring buffer of fixed size. Because we probably will never
7+
/// need to have buffers with size bigger that u32::MAX,
8+
/// indexes in this struct are u32, so the maximum size is u32::MAX.
9+
/// This saves 8 bytes compared to `VecDequeue` in the standard library.
10+
/// (24 bytes vs 32 bytes)
11+
/// Making indexes smaller than u32 does not make sense, as the alignment
12+
/// of `Box` is 8 bytes.
13+
#[derive(Debug, Default, Clone)]
14+
pub struct RingBuffer<T: std::fmt::Debug + Default + Clone> {
15+
/// Fixed array of items.
16+
pub items: Box<[T]>,
17+
/// Start index.
18+
pub start: u32,
19+
/// Current length of the ring.
20+
pub len: u32,
21+
}
22+
23+
impl<T: std::fmt::Debug + Default + Clone> RingBuffer<T> {
24+
/// New with specified size.
25+
pub fn new_with_size(size: u32) -> Self {
26+
Self {
27+
items: vec![T::default(); u32_to_usize(size)].into_boxed_slice(),
28+
start: 0,
29+
len: 0,
30+
}
31+
}
32+
33+
/// Get number of items in the buffer
34+
pub fn len(&self) -> u32 {
35+
self.len
36+
}
37+
38+
/// Check if ring is empty
39+
pub fn is_empty(&self) -> bool {
40+
self.len == 0
41+
}
42+
43+
/// Check if ring is full
44+
pub fn is_full(&self) -> bool {
45+
u32_to_usize(self.len) == self.items.len()
46+
}
47+
48+
/// Returns a reference to the first element if ring is not empty.
49+
pub fn first(&self) -> Option<&T> {
50+
if self.is_empty() {
51+
None
52+
} else {
53+
Some(&self.items[u32_to_usize(self.start)])
54+
}
55+
}
56+
57+
/// Push new item to the end of the ring and increases
58+
/// the length.
59+
/// If there is no space for it, nothing will happen.
60+
pub fn push_back(&mut self, item: T) {
61+
if !self.is_full() {
62+
let index = u32_to_usize(self.start + self.len) % self.items.len();
63+
self.items[index] = item;
64+
self.len += 1;
65+
}
66+
}
67+
68+
/// Pop item from the from of the ring and return
69+
/// a reference to it.
70+
/// If ring is empty returns None.
71+
pub fn pop_front(&mut self) -> Option<&T> {
72+
if self.is_empty() {
73+
None
74+
} else {
75+
let index = u32_to_usize(self.start);
76+
self.start += 1;
77+
78+
// Need to allow this, because we cast `items.len()` to u32,
79+
// but this is safe as the max size of the buffer is u32::MAX.
80+
#[allow(clippy::cast_possible_truncation)]
81+
let items_len = self.items.len() as u32;
82+
83+
self.start %= items_len;
84+
self.len -= 1;
85+
Some(&self.items[index])
86+
}
87+
}
88+
}
89+
90+
#[cfg(test)]
91+
pub mod tests {
92+
use super::*;
93+
94+
#[test]
95+
fn test_new() {
96+
let a = RingBuffer::<u8>::new_with_size(69);
97+
assert_eq!(a.items.len(), 69);
98+
assert_eq!(a.start, 0);
99+
assert_eq!(a.len, 0);
100+
assert!(a.is_empty());
101+
assert!(!a.is_full());
102+
}
103+
104+
#[test]
105+
fn test_push() {
106+
let mut a = RingBuffer::<u8>::new_with_size(4);
107+
108+
a.push_back(0);
109+
assert!(!a.is_empty());
110+
assert!(!a.is_full());
111+
112+
a.push_back(1);
113+
assert!(!a.is_empty());
114+
assert!(!a.is_full());
115+
116+
a.push_back(2);
117+
assert!(!a.is_empty());
118+
assert!(!a.is_full());
119+
120+
a.push_back(3);
121+
assert!(!a.is_empty());
122+
assert!(a.is_full());
123+
124+
assert_eq!(a.items.as_ref(), &[0, 1, 2, 3]);
125+
126+
a.push_back(4);
127+
assert!(!a.is_empty());
128+
assert!(a.is_full());
129+
130+
assert_eq!(a.items.as_ref(), &[0, 1, 2, 3]);
131+
}
132+
133+
#[test]
134+
fn test_pop_front() {
135+
let mut a = RingBuffer::<u8>::new_with_size(4);
136+
a.push_back(0);
137+
a.push_back(1);
138+
a.push_back(2);
139+
a.push_back(3);
140+
assert!(!a.is_empty());
141+
assert!(a.is_full());
142+
143+
assert_eq!(*a.pop_front().unwrap(), 0);
144+
assert!(!a.is_empty());
145+
assert!(!a.is_full());
146+
147+
assert_eq!(*a.pop_front().unwrap(), 1);
148+
assert!(!a.is_empty());
149+
assert!(!a.is_full());
150+
151+
assert_eq!(*a.pop_front().unwrap(), 2);
152+
assert!(!a.is_empty());
153+
assert!(!a.is_full());
154+
155+
assert_eq!(*a.pop_front().unwrap(), 3);
156+
assert!(a.is_empty());
157+
assert!(!a.is_full());
158+
159+
assert!(a.pop_front().is_none());
160+
assert!(a.is_empty());
161+
assert!(!a.is_full());
162+
}
163+
}

0 commit comments

Comments
 (0)