Skip to content

Commit b1afb75

Browse files
committed
feat: allow the matcher to use user defined stack implementation and use by default a static stack
1 parent e0fd77a commit b1afb75

File tree

4 files changed

+208
-18
lines changed

4 files changed

+208
-18
lines changed

bmatcher-core/benches/matcher.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,37 @@ pub fn simple_binary_pattern(instance: &mut Criterion) {
2323
let pattern = compiler::parse_pattern("FF FF 00 00 FF DE AD BE EF").unwrap();
2424
let pattern = compiler::optimize_pattern(&pattern);
2525

26-
let mut group = instance.benchmark_group("binary match throughput");
26+
let mut group = instance.benchmark_group("binary");
27+
for target_size in [128usize, 1024, 1024 * 4, 1024 * 1024] {
28+
group.throughput(Throughput::Bytes(target_size as u64));
29+
group.bench_with_input(
30+
BenchmarkId::from_parameter(target_size),
31+
&target_size,
32+
|bencher, size| {
33+
let mut buffer = Vec::with_capacity(*size);
34+
35+
let mut rng = StdRng::from_seed([0xEEu8; 32]);
36+
buffer.resize_with(*size, || rng.next_u32() as u8);
37+
38+
let buffer = buffer.as_slice();
39+
bencher.iter(|| {
40+
let mut matcher =
41+
BinaryMatcher::new(black_box(&pattern), black_box(&buffer));
42+
while let Some(_match) = matcher.next_match() {
43+
panic!("pattern should not match");
44+
}
45+
});
46+
},
47+
);
48+
}
49+
group.finish();
50+
}
51+
52+
{
53+
let pattern = compiler::parse_pattern("FF FF 00 00 ? ? ? ? FF DE AD BE EF").unwrap();
54+
let pattern = compiler::optimize_pattern(&pattern);
55+
56+
let mut group = instance.benchmark_group("binary with wildcard");
2757
for target_size in [128usize, 1024, 1024 * 4, 1024 * 1024] {
2858
group.throughput(Throughput::Bytes(target_size as u64));
2959
group.bench_with_input(

bmatcher-core/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ pub use target::MatchTarget;
1616
mod matcher;
1717
pub use matcher::BinaryMatcher;
1818

19+
mod stack;
20+
pub use stack::{
21+
HeapStack,
22+
Stack,
23+
StaticStack,
24+
};
25+
1926
mod pattern;
2027
pub use pattern::{
2128
BinaryPattern,

bmatcher-core/src/matcher.rs

Lines changed: 74 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,78 @@
1-
use alloc::{
2-
vec,
3-
vec::Vec,
4-
};
5-
61
use crate::{
72
Atom,
83
BinaryPattern,
4+
HeapStack,
95
JumpType,
106
MatchTarget,
117
ReadWidth,
8+
Stack,
9+
StaticStack,
1210
};
1311

1412
/// The `BinaryMatcher` is responsible for searching a [BinaryPattern] within a [MatchTarget].
1513
///
1614
/// Use [`BinaryMatcher::next_match`] to iterate through matches of the specified pattern.
17-
pub struct BinaryMatcher<'a> {
15+
pub struct BinaryMatcher<
16+
'a,
17+
S: Stack<u32> = StaticStack<0x10, u32>,
18+
C: Stack<usize> = StaticStack<0x10, usize>,
19+
> {
1820
pattern_atoms: &'a [Atom],
1921
pattern_byte_sequence: &'a [u8],
2022

2123
target: &'a dyn MatchTarget,
2224

2325
match_offset: usize,
2426

25-
save_stack: Vec<u32>,
26-
cursor_stack: Vec<usize>,
27+
save_stack: S,
28+
cursor_stack: C,
2729
}
2830

2931
impl<'a> BinaryMatcher<'a> {
32+
/// Create a new BinaryMatcher instance with a statically allocated stack.
33+
/// The default stack size is 0x10 for the save and cursor stack.
3034
pub fn new(pattern: &'a dyn BinaryPattern, target: &'a dyn MatchTarget) -> Self {
35+
Self::new_with_stack(
36+
pattern,
37+
target,
38+
StaticStack::<0x10, u32>::new(),
39+
StaticStack::<0x10, usize>::new(),
40+
)
41+
}
42+
43+
/// Create a new BinaryMatcher instance with a heap allocated save and cursor stack
44+
pub fn new_heap_stack(
45+
pattern: &'a dyn BinaryPattern,
46+
target: &'a dyn MatchTarget,
47+
) -> BinaryMatcher<'a, HeapStack<u32>, HeapStack<usize>> {
48+
BinaryMatcher::new_with_stack(
49+
pattern,
50+
target,
51+
HeapStack::<u32>::new(),
52+
HeapStack::<usize>::new(),
53+
)
54+
}
55+
}
56+
57+
impl<'a, S: Stack<u32>, C: Stack<usize>> BinaryMatcher<'a, S, C> {
58+
/// Create a new binary matcher and supply the save and cursor stacks on your own
59+
pub fn new_with_stack(
60+
pattern: &'a dyn BinaryPattern,
61+
target: &'a dyn MatchTarget,
62+
mut save_stack: S,
63+
cursor_stack: C,
64+
) -> Self {
65+
save_stack.truncate(0);
66+
save_stack.push_value(0x00);
67+
3168
Self {
3269
pattern_atoms: pattern.atoms(),
3370
pattern_byte_sequence: pattern.byte_sequence(),
3471

3572
target,
3673

37-
save_stack: vec![0; 8],
38-
cursor_stack: vec![0; 8],
74+
save_stack,
75+
cursor_stack,
3976

4077
match_offset: 0,
4178
}
@@ -86,11 +123,20 @@ impl<'a> BinaryMatcher<'a> {
86123
}
87124

88125
Atom::CursorPush => {
89-
self.cursor_stack.push(data_cursor);
126+
if !self.cursor_stack.push_value(data_cursor) {
127+
/* TODO: Return error instead of abort search */
128+
return None;
129+
}
130+
90131
atom_cursor += 1;
91132
}
92133
Atom::CursorPop { advance } => {
93-
data_cursor = self.cursor_stack.pop().unwrap() + advance as usize;
134+
let Some(value) = self.cursor_stack.pop_value() else {
135+
/* TODO: Return error instead of abort search */
136+
return None;
137+
};
138+
139+
data_cursor = value + advance as usize;
94140
atom_cursor += 1;
95141
}
96142

@@ -157,18 +203,27 @@ impl<'a> BinaryMatcher<'a> {
157203
(u32::from_le_bytes(value.try_into().unwrap()), 4)
158204
}
159205
};
160-
self.save_stack.push(value);
206+
if !self.save_stack.push_value(value) {
207+
/* TODO: Return error instead of abort search */
208+
return None;
209+
}
161210

162211
atom_cursor += 1;
163212
data_cursor += width;
164213
}
165214

166215
Atom::SaveCursor => {
167-
self.save_stack.push(data_cursor as u32);
216+
if !self.save_stack.push_value(data_cursor as u32) {
217+
/* TODO: Return error instead of abort search */
218+
return None;
219+
}
168220
atom_cursor += 1;
169221
}
170222
Atom::SaveConstant(value) => {
171-
self.save_stack.push(value);
223+
if !self.save_stack.push_value(value) {
224+
/* TODO: Return error instead of abort search */
225+
return None;
226+
}
172227
atom_cursor += 1;
173228
}
174229
}
@@ -194,8 +249,10 @@ impl<'a> BinaryMatcher<'a> {
194249
}
195250

196251
self.match_offset = match_offset + 1;
197-
self.save_stack[0] = match_offset as u32;
198-
return Some(&self.save_stack);
252+
253+
let save_stack = self.save_stack.stack_mut();
254+
save_stack[0] = match_offset as u32;
255+
return Some(save_stack);
199256
}
200257

201258
None

bmatcher-core/src/stack.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use alloc::vec::Vec;
2+
3+
pub trait Stack<T> {
4+
fn new() -> Self;
5+
6+
fn len(&self) -> usize;
7+
fn reserve(&mut self, _size: usize) {}
8+
fn truncate(&mut self, size: usize);
9+
10+
fn push_value(&mut self, value: T) -> bool;
11+
fn pop_value(&mut self) -> Option<T>;
12+
13+
fn stack_mut(&mut self) -> &mut [T];
14+
}
15+
16+
pub struct StaticStack<const N: usize, T> {
17+
stack: [T; N],
18+
length: usize,
19+
}
20+
21+
impl<const N: usize, T: Default + Copy> Stack<T> for StaticStack<N, T> {
22+
fn new() -> Self {
23+
Self {
24+
stack: [Default::default(); N],
25+
length: 0,
26+
}
27+
}
28+
29+
fn len(&self) -> usize {
30+
self.length
31+
}
32+
33+
fn stack_mut(&mut self) -> &mut [T] {
34+
&mut self.stack[0..self.length]
35+
}
36+
37+
fn truncate(&mut self, size: usize) {
38+
self.length = size;
39+
assert!(size <= self.stack.len());
40+
}
41+
42+
fn push_value(&mut self, value: T) -> bool {
43+
if self.length == self.stack.len() {
44+
return false;
45+
}
46+
47+
self.stack[self.length] = value;
48+
self.length += 1;
49+
true
50+
}
51+
52+
fn pop_value(&mut self) -> Option<T> {
53+
if self.length > 0 {
54+
let value = self.stack[self.length];
55+
self.length -= 1;
56+
Some(value)
57+
} else {
58+
None
59+
}
60+
}
61+
}
62+
63+
pub struct HeapStack<T> {
64+
stack: Vec<T>,
65+
}
66+
67+
impl<T> Stack<T> for HeapStack<T> {
68+
fn new() -> Self {
69+
Self { stack: Vec::new() }
70+
}
71+
72+
fn len(&self) -> usize {
73+
self.stack.len()
74+
}
75+
76+
fn stack_mut(&mut self) -> &mut [T] {
77+
&mut self.stack
78+
}
79+
80+
fn reserve(&mut self, size: usize) {
81+
self.stack.reserve(size);
82+
}
83+
84+
fn truncate(&mut self, size: usize) {
85+
self.stack.truncate(size);
86+
}
87+
88+
fn push_value(&mut self, value: T) -> bool {
89+
self.stack.push(value);
90+
true
91+
}
92+
93+
fn pop_value(&mut self) -> Option<T> {
94+
self.stack.pop()
95+
}
96+
}

0 commit comments

Comments
 (0)