use std::collections::VecDeque;
use std::io::Read;
use std::ops::Range;
use std::path::Path;
use vyre::VyreBackend;
use vyre_foundation::match_result::Match;
use crate::scan::literal_set::{GpuLiteralSet, PendingFusedRegion};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CorpusWindow {
pub(crate) file_range: Range<usize>,
pub(crate) byte_offset: u64,
pub(crate) byte_len: usize,
pub(crate) global_region_base: u32,
}
impl CorpusWindow {
pub(crate) fn local_region_starts(&self, file_lengths: &[usize]) -> Vec<u32> {
let mut starts = Vec::with_capacity(self.file_range.len());
let mut offset = 0u32;
for &len in &file_lengths[self.file_range.clone()] {
starts.push(offset);
offset = offset.saturating_add(len as u32);
}
starts
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum PagedCorpusError {
ZeroBudget,
FileExceedsHaystackAbi { file_index: usize, len: usize },
TooManyRegions { count: usize },
}
impl std::fmt::Display for PagedCorpusError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ZeroBudget => write!(
f,
"paged_corpus: window budget is 0 bytes, so no file fits. Fix: pass a window_budget_bytes at least as large as the largest file (and within the resident haystack capacity)."
),
Self::FileExceedsHaystackAbi { file_index, len } => write!(
f,
"paged_corpus: file {file_index} is {len} bytes, larger than the u32 haystack ABI can address in one dispatch. Fix: pre-split that file into sub-{ceiling}-byte regions before paging.",
ceiling = u32::MAX
),
Self::TooManyRegions { count } => write!(
f,
"paged_corpus: corpus has {count} files but the region-id ABI is u32. Fix: coalesce fewer files per corpus or shard the corpus."
),
}
}
}
impl std::error::Error for PagedCorpusError {}
pub(crate) fn plan_corpus_windows(
file_lengths: &[usize],
window_budget_bytes: usize,
) -> Result<Vec<CorpusWindow>, PagedCorpusError> {
if window_budget_bytes == 0 {
return Err(PagedCorpusError::ZeroBudget);
}
if file_lengths.len() > u32::MAX as usize {
return Err(PagedCorpusError::TooManyRegions {
count: file_lengths.len(),
});
}
for (file_index, &len) in file_lengths.iter().enumerate() {
if len > u32::MAX as usize {
return Err(PagedCorpusError::FileExceedsHaystackAbi { file_index, len });
}
}
let mut windows = Vec::new();
let mut file_start = 0usize;
let mut byte_offset = 0u64;
while file_start < file_lengths.len() {
let mut file_end = file_start;
let mut window_bytes = 0usize;
while file_end < file_lengths.len() {
let next = file_lengths[file_end];
let grown = window_bytes.saturating_add(next);
if file_end > file_start && grown > window_budget_bytes {
break;
}
window_bytes = grown;
file_end += 1;
}
windows.push(CorpusWindow {
file_range: file_start..file_end,
byte_offset,
byte_len: window_bytes,
global_region_base: file_start as u32,
});
byte_offset = byte_offset.saturating_add(window_bytes as u64);
file_start = file_end;
}
Ok(windows)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GlobalMatch {
pub pattern_id: u32,
pub region_id: u32,
pub start: u64,
pub end: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PagedScanResult {
pub presence: Vec<u32>,
pub region_count: u32,
pub presence_words: u32,
pub matches: Vec<GlobalMatch>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PagedScanTiming {
pub windows: u32,
pub bytes_scanned: u64,
pub wall_ns: u64,
pub device_ns: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ShardTiming {
pub shard: u32,
pub windows: u32,
pub bytes_scanned: u64,
pub wall_ns: u64,
pub device_ns: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShardedScanTiming {
pub shards: Vec<ShardTiming>,
}
struct WindowStaging {
haystack: Vec<u8>,
region_starts: Vec<u32>,
own_len: usize,
own_region_count: usize,
byte_offset: u64,
global_region_base: u32,
}
fn paging_overlap(matcher: &GpuLiteralSet) -> usize {
matcher
.pattern_lengths
.iter()
.copied()
.max()
.unwrap_or(1)
.saturating_sub(1) as usize
}
fn stage_window(
files: &[&[u8]],
file_lengths: &[usize],
window: &CorpusWindow,
overlap: usize,
) -> WindowStaging {
let own_len = window.byte_len;
let mut haystack = Vec::with_capacity(own_len + overlap);
for file in &files[window.file_range.clone()] {
haystack.extend_from_slice(file);
}
let mut gathered = 0usize;
for file in &files[window.file_range.end..] {
if gathered >= overlap {
break;
}
let take = (overlap - gathered).min(file.len());
haystack.extend_from_slice(&file[..take]);
gathered += take;
}
let (region_starts, own_region_count) =
window_region_starts(window, file_lengths, gathered > 0);
WindowStaging {
haystack,
region_starts,
own_len,
own_region_count,
byte_offset: window.byte_offset,
global_region_base: window.global_region_base,
}
}
fn window_region_starts(
window: &CorpusWindow,
file_lengths: &[usize],
has_overlap: bool,
) -> (Vec<u32>, usize) {
let mut region_starts = window.local_region_starts(file_lengths);
let own_region_count = region_starts.len();
if has_overlap {
region_starts.push(window.byte_len as u32);
}
(region_starts, own_region_count)
}
fn window_presence_words(
win_presence: &[u32],
scan_region_count: usize,
) -> Result<usize, vyre::BackendError> {
if scan_region_count == 0 || win_presence.len() % scan_region_count != 0 {
return Err(vyre::BackendError::new(format!(
"scan_paged_fused: window presence length {} is not a multiple of its region count {scan_region_count}. Fix: internal invariant broke, report with the corpus shape.",
win_presence.len()
)));
}
Ok(win_presence.len() / scan_region_count)
}
fn map_window_matches(staging: &WindowStaging, win_matches: &[Match], out: &mut Vec<GlobalMatch>) {
let own_starts = &staging.region_starts[..staging.own_region_count];
for hit in win_matches {
if (hit.start as usize) >= staging.own_len {
continue; }
let local_region = own_starts
.partition_point(|&start| start <= hit.start)
.saturating_sub(1);
out.push(GlobalMatch {
pattern_id: hit.pattern_id,
region_id: staging.global_region_base + local_region as u32,
start: staging.byte_offset + u64::from(hit.start),
end: staging.byte_offset + u64::from(hit.end),
});
}
}
fn globalize_window(
staging: &WindowStaging,
win_presence: &[u32],
win_matches: &[Match],
presence_words: &mut usize,
presence: &mut Vec<u32>,
matches: &mut Vec<GlobalMatch>,
) -> Result<(), vyre::BackendError> {
let words = window_presence_words(win_presence, staging.region_starts.len())?;
if *presence_words == 0 {
*presence_words = words;
} else if words != *presence_words {
return Err(vyre::BackendError::new(format!(
"scan_paged_fused: presence word count changed across windows ({} -> {words}). Fix: internal invariant broke, report with the corpus shape.",
*presence_words
)));
}
presence.extend_from_slice(&win_presence[..staging.own_region_count * words]);
map_window_matches(staging, win_matches, matches);
Ok(())
}
fn finish_result(
presence: Vec<u32>,
region_count: u32,
presence_words: usize,
mut matches: Vec<GlobalMatch>,
) -> PagedScanResult {
matches.sort_unstable_by_key(|hit| (hit.region_id, hit.start, hit.end, hit.pattern_id));
PagedScanResult {
presence,
region_count,
presence_words: presence_words as u32,
matches,
}
}
fn plan_paged(
files: &[&[u8]],
window_budget_bytes: usize,
) -> Result<Option<(u32, Vec<usize>, Vec<CorpusWindow>)>, vyre::BackendError> {
let region_count = u32::try_from(files.len()).map_err(|_| {
vyre::BackendError::new(
"scan_paged_fused: file count exceeds the u32 region-id ABI. Fix: coalesce fewer files per corpus or shard the corpus.".to_string(),
)
})?;
let file_lengths: Vec<usize> = files.iter().map(|file| file.len()).collect();
let windows = plan_corpus_windows(&file_lengths, window_budget_bytes)
.map_err(|error| vyre::BackendError::new(error.to_string()))?;
if windows.is_empty() {
return Ok(None);
}
Ok(Some((region_count, file_lengths, windows)))
}
pub fn scan_paged_fused(
matcher: &GpuLiteralSet,
backend: &dyn VyreBackend,
files: &[&[u8]],
window_budget_bytes: usize,
max_matches: u32,
) -> Result<PagedScanResult, vyre::BackendError> {
let Some((region_count, file_lengths, windows)) = plan_paged(files, window_budget_bytes)?
else {
return Ok(finish_result(Vec::new(), 0, 0, Vec::new()));
};
let overlap = paging_overlap(matcher);
let max_window_bytes = windows
.iter()
.map(|window| window.byte_len)
.max()
.unwrap_or(0);
let max_window_regions = windows
.iter()
.map(|window| window.file_range.len())
.max()
.unwrap_or(0) as u32;
let session = matcher.prepare_resident_fused_scan(
backend,
max_window_bytes + overlap + 64,
max_window_regions + 1,
max_matches,
)?;
let mut presence: Vec<u32> = Vec::new();
let mut presence_words: usize = 0;
let mut matches: Vec<GlobalMatch> = Vec::new();
let mut win_presence: Vec<u32> = Vec::new();
let mut win_matches: Vec<Match> = Vec::new();
let mut scratch: Vec<u8> = Vec::new();
for window in &windows {
let staging = stage_window(files, &file_lengths, window, overlap);
session.scan_into(
backend,
&staging.haystack,
&staging.region_starts,
0,
&mut win_presence,
&mut win_matches,
&mut scratch,
)?;
if let Err(error) = globalize_window(
&staging,
&win_presence,
&win_matches,
&mut presence_words,
&mut presence,
&mut matches,
) {
let _ = session.free(backend);
return Err(error);
}
}
session.free(backend)?;
Ok(finish_result(
presence,
region_count,
presence_words,
matches,
))
}
pub fn scan_paged_fused_timed(
matcher: &GpuLiteralSet,
backend: &dyn VyreBackend,
files: &[&[u8]],
window_budget_bytes: usize,
max_matches: u32,
) -> Result<(PagedScanResult, PagedScanTiming), vyre::BackendError> {
let Some((region_count, file_lengths, windows)) = plan_paged(files, window_budget_bytes)?
else {
let timing = PagedScanTiming {
windows: 0,
bytes_scanned: 0,
wall_ns: 0,
device_ns: Some(0),
};
return Ok((finish_result(Vec::new(), 0, 0, Vec::new()), timing));
};
let overlap = paging_overlap(matcher);
let max_window_bytes = windows
.iter()
.map(|window| window.byte_len)
.max()
.unwrap_or(0);
let max_window_regions = windows
.iter()
.map(|window| window.file_range.len())
.max()
.unwrap_or(0) as u32;
let session = matcher.prepare_resident_fused_scan(
backend,
max_window_bytes + overlap + 64,
max_window_regions + 1,
max_matches,
)?;
let mut presence: Vec<u32> = Vec::new();
let mut presence_words: usize = 0;
let mut matches: Vec<GlobalMatch> = Vec::new();
let mut win_presence: Vec<u32> = Vec::new();
let mut win_matches: Vec<Match> = Vec::new();
let mut scratch: Vec<u8> = Vec::new();
let mut wall_ns: u64 = 0;
let mut device_ns_acc: Option<u64> = Some(0);
let mut bytes_scanned: u64 = 0;
for window in &windows {
let staging = stage_window(files, &file_lengths, window, overlap);
let timed = match session.scan_into_timed(
backend,
&staging.haystack,
&staging.region_starts,
0,
&mut win_presence,
&mut win_matches,
&mut scratch,
) {
Ok(timed) => timed,
Err(error) => {
let _ = session.free(backend);
return Err(error);
}
};
wall_ns = wall_ns.saturating_add(timed.wall_ns);
device_ns_acc = match (device_ns_acc, timed.device_ns) {
(Some(acc), Some(window_device)) => Some(acc.saturating_add(window_device)),
_ => None,
};
bytes_scanned = bytes_scanned.saturating_add(window.byte_len as u64);
if let Err(error) = globalize_window(
&staging,
&win_presence,
&win_matches,
&mut presence_words,
&mut presence,
&mut matches,
) {
let _ = session.free(backend);
return Err(error);
}
}
session.free(backend)?;
let timing = PagedScanTiming {
windows: windows.len() as u32,
bytes_scanned,
wall_ns,
device_ns: device_ns_acc,
};
Ok((
finish_result(presence, region_count, presence_words, matches),
timing,
))
}
pub fn scan_sharded_fused(
matcher: &GpuLiteralSet,
backends: &[&dyn VyreBackend],
files: &[&[u8]],
window_budget_bytes: usize,
max_matches: u32,
) -> Result<PagedScanResult, vyre::BackendError> {
scan_sharded_core(
matcher,
backends,
files,
window_budget_bytes,
max_matches,
None,
)
.map(|(result, _timing)| result)
}
pub fn scan_sharded_fused_timed(
matcher: &GpuLiteralSet,
backends: &[&dyn VyreBackend],
files: &[&[u8]],
window_budget_bytes: usize,
max_matches: u32,
) -> Result<(PagedScanResult, ShardedScanTiming), vyre::BackendError> {
scan_sharded_core(
matcher,
backends,
files,
window_budget_bytes,
max_matches,
None,
)
}
pub fn scan_sharded_fused_weighted(
matcher: &GpuLiteralSet,
backends: &[&dyn VyreBackend],
weights: &[u32],
files: &[&[u8]],
window_budget_bytes: usize,
max_matches: u32,
) -> Result<PagedScanResult, vyre::BackendError> {
if weights.len() != backends.len() {
return Err(vyre::BackendError::new(format!(
"scan_sharded_fused_weighted: {} weights for {} backends. Fix: pass exactly one throughput weight per device backend.",
weights.len(),
backends.len()
)));
}
scan_sharded_core(
matcher,
backends,
files,
window_budget_bytes,
max_matches,
Some(weights),
)
.map(|(result, _timing)| result)
}
fn shard_assignment(
window_byte_lens: &[usize],
shard_count: usize,
weights: Option<&[u32]>,
) -> Vec<usize> {
match weights {
None => (0..window_byte_lens.len())
.map(|index| index % shard_count.max(1))
.collect(),
Some(weights) => {
let mut assigned_bytes = vec![0u128; shard_count];
let mut assignment = Vec::with_capacity(window_byte_lens.len());
for &bytes in window_byte_lens {
let mut best_shard = 0usize;
let mut best_ratio = u128::MAX;
for shard in 0..shard_count {
let weight = u128::from(weights.get(shard).copied().unwrap_or(1).max(1));
let projected = (assigned_bytes[shard] + bytes as u128) / weight;
if projected < best_ratio {
best_ratio = projected;
best_shard = shard;
}
}
assigned_bytes[best_shard] += bytes as u128;
assignment.push(best_shard);
}
assignment
}
}
}
fn scan_sharded_core(
matcher: &GpuLiteralSet,
backends: &[&dyn VyreBackend],
files: &[&[u8]],
window_budget_bytes: usize,
max_matches: u32,
weights: Option<&[u32]>,
) -> Result<(PagedScanResult, ShardedScanTiming), vyre::BackendError> {
if backends.is_empty() {
return Err(vyre::BackendError::new(
"scan_sharded_fused: the backend set is empty. Fix: pass at least one device backend to shard the corpus across.".to_string(),
));
}
let mut shard_timings: Vec<ShardTiming> = (0..backends.len())
.map(|shard| ShardTiming {
shard: shard as u32,
windows: 0,
bytes_scanned: 0,
wall_ns: 0,
device_ns: Some(0),
})
.collect();
let Some((region_count, file_lengths, windows)) = plan_paged(files, window_budget_bytes)?
else {
return Ok((
finish_result(Vec::new(), 0, 0, Vec::new()),
ShardedScanTiming {
shards: shard_timings,
},
));
};
let overlap = paging_overlap(matcher);
let window_byte_lens: Vec<usize> = windows.iter().map(|window| window.byte_len).collect();
let assignment = shard_assignment(&window_byte_lens, backends.len(), weights);
let max_window_bytes = windows
.iter()
.map(|window| window.byte_len)
.max()
.unwrap_or(0);
let max_window_regions = windows
.iter()
.map(|window| window.file_range.len())
.max()
.unwrap_or(0) as u32;
let mut shard_window_indices: Vec<Vec<usize>> = vec![Vec::new(); backends.len()];
for (index, &shard) in assignment.iter().enumerate() {
shard_window_indices[shard].push(index);
}
struct ShardWindowOutput {
window_index: usize,
words: usize,
presence_block: Vec<u32>,
matches: Vec<GlobalMatch>,
}
struct ShardThreadResult {
shard: usize,
outputs: Vec<ShardWindowOutput>,
timing: ShardTiming,
}
let file_lengths_ref = &file_lengths;
let windows_ref = &windows;
let thread_results: Result<Vec<ShardThreadResult>, vyre::BackendError> = std::thread::scope(
|scope| {
let mut handles = Vec::with_capacity(backends.len());
for shard in 0..backends.len() {
let backend = backends[shard];
let indices = std::mem::take(&mut shard_window_indices[shard]);
handles.push(scope.spawn(
move || -> Result<ShardThreadResult, vyre::BackendError> {
let mut timing = ShardTiming {
shard: shard as u32,
windows: 0,
bytes_scanned: 0,
wall_ns: 0,
device_ns: Some(0),
};
if indices.is_empty() {
return Ok(ShardThreadResult {
shard,
outputs: Vec::new(),
timing,
});
}
let session = matcher.prepare_resident_fused_scan(
backend,
max_window_bytes + overlap + 64,
max_window_regions + 1,
max_matches,
)?;
let mut win_presence: Vec<u32> = Vec::new();
let mut win_matches: Vec<Match> = Vec::new();
let mut scratch: Vec<u8> = Vec::new();
let mut outputs = Vec::with_capacity(indices.len());
let mut scan_error: Option<vyre::BackendError> = None;
for index in indices {
let window = &windows_ref[index];
let staging = stage_window(files, file_lengths_ref, window, overlap);
let timed = match session.scan_into_timed(
backend,
&staging.haystack,
&staging.region_starts,
0,
&mut win_presence,
&mut win_matches,
&mut scratch,
) {
Ok(timed) => timed,
Err(error) => {
scan_error = Some(error);
break;
}
};
timing.windows += 1;
timing.bytes_scanned =
timing.bytes_scanned.saturating_add(window.byte_len as u64);
timing.wall_ns = timing.wall_ns.saturating_add(timed.wall_ns);
timing.device_ns = match (timing.device_ns, timed.device_ns) {
(Some(acc), Some(window_device)) => {
Some(acc.saturating_add(window_device))
}
_ => None,
};
let words = match window_presence_words(
&win_presence,
staging.region_starts.len(),
) {
Ok(words) => words,
Err(error) => {
scan_error = Some(error);
break;
}
};
let presence_block =
win_presence[..staging.own_region_count * words].to_vec();
let mut window_matches = Vec::new();
map_window_matches(&staging, &win_matches, &mut window_matches);
outputs.push(ShardWindowOutput {
window_index: index,
words,
presence_block,
matches: window_matches,
});
}
let free_error = session.free(backend).err();
if let Some(error) = scan_error {
return Err(error);
}
if let Some(error) = free_error {
return Err(error);
}
Ok(ShardThreadResult {
shard,
outputs,
timing,
})
},
));
}
let mut results = Vec::with_capacity(handles.len());
for handle in handles {
match handle.join() {
Ok(Ok(result)) => results.push(result),
Ok(Err(error)) => return Err(error),
Err(_) => {
return Err(vyre::BackendError::new(
"scan_sharded_fused: a per-device shard dispatch thread panicked. Fix: a device backend or its resident session raised an unrecoverable panic, inspect the backend logs; cross-device sharded dispatch fails closed rather than returning a partial result.".to_string(),
))
}
}
}
Ok(results)
},
);
let results = thread_results?;
let mut all_outputs: Vec<ShardWindowOutput> = Vec::new();
for result in results {
shard_timings[result.shard] = result.timing;
all_outputs.extend(result.outputs);
}
all_outputs.sort_by_key(|output| output.window_index);
let mut presence: Vec<u32> = Vec::new();
let mut presence_words: usize = 0;
let mut matches: Vec<GlobalMatch> = Vec::new();
for output in all_outputs {
if presence_words == 0 {
presence_words = output.words;
} else if output.words != presence_words {
return Err(vyre::BackendError::new(format!(
"scan_sharded_fused: presence word count changed across windows ({presence_words} -> {}). Fix: internal invariant broke, report with the corpus shape.",
output.words
)));
}
presence.extend_from_slice(&output.presence_block);
matches.extend(output.matches);
}
Ok((
finish_result(presence, region_count, presence_words, matches),
ShardedScanTiming {
shards: shard_timings,
},
))
}
pub struct PatternShard<'a> {
pub matcher: &'a GpuLiteralSet,
pub global_pattern_ids: &'a [u32],
}
pub fn scan_pattern_sharded(
shards: &[PatternShard<'_>],
backends: &[&dyn VyreBackend],
haystack: &[u8],
) -> Result<Vec<Match>, vyre::BackendError> {
if backends.is_empty() {
return Err(vyre::BackendError::new(
"scan_pattern_sharded: the backend set is empty. Fix: pass at least one device backend to stripe the rule database across.".to_string(),
));
}
let mut merged: Vec<Match> = Vec::new();
for (index, shard) in shards.iter().enumerate() {
let backend = backends[index % backends.len()];
let local_matches = shard.matcher.scan_all(backend, haystack)?;
for mut hit in local_matches {
let Some(&global_id) = shard.global_pattern_ids.get(hit.pattern_id as usize) else {
return Err(vyre::BackendError::new(format!(
"scan_pattern_sharded: shard {index} produced local pattern id {} but its global_pattern_ids map has only {} entries. Fix: give each shard a global id for every rule in its sub-matcher.",
hit.pattern_id,
shard.global_pattern_ids.len()
)));
};
hit.pattern_id = global_id;
merged.push(hit);
}
}
merged.sort_unstable();
Ok(merged)
}
const PAGED_PIPELINE_DEPTH: usize = 2;
pub fn scan_paged_fused_async(
matcher: &GpuLiteralSet,
backend: &dyn VyreBackend,
files: &[&[u8]],
window_budget_bytes: usize,
max_matches: u32,
) -> Result<PagedScanResult, vyre::BackendError> {
let Some((region_count, file_lengths, windows)) = plan_paged(files, window_budget_bytes)?
else {
return Ok(finish_result(Vec::new(), 0, 0, Vec::new()));
};
let overlap = paging_overlap(matcher);
let mut presence: Vec<u32> = Vec::new();
let mut presence_words: usize = 0;
let mut matches: Vec<GlobalMatch> = Vec::new();
let mut win_matches: Vec<Match> = Vec::new();
let mut inflight: VecDeque<(WindowStaging, PendingFusedRegion)> = VecDeque::new();
let retire = |staging: WindowStaging,
pending: PendingFusedRegion,
presence_words: &mut usize,
presence: &mut Vec<u32>,
matches: &mut Vec<GlobalMatch>,
win_matches: &mut Vec<Match>|
-> Result<(), vyre::BackendError> {
let win_presence = pending.await_into(win_matches)?;
globalize_window(
&staging,
&win_presence,
win_matches,
presence_words,
presence,
matches,
)
};
for window in &windows {
let staging = stage_window(files, &file_lengths, window, overlap);
let pending = matcher.scan_presence_and_positions_by_region_async(
backend,
&staging.haystack,
&staging.region_starts,
0,
max_matches,
)?;
inflight.push_back((staging, pending));
if inflight.len() >= PAGED_PIPELINE_DEPTH {
let (staging, pending) = inflight.pop_front().expect("depth reached");
retire(
staging,
pending,
&mut presence_words,
&mut presence,
&mut matches,
&mut win_matches,
)?;
}
}
while let Some((staging, pending)) = inflight.pop_front() {
retire(
staging,
pending,
&mut presence_words,
&mut presence,
&mut matches,
&mut win_matches,
)?;
}
Ok(finish_result(
presence,
region_count,
presence_words,
matches,
))
}
fn fill_window_from_paths(
paths: &[&Path],
window: &CorpusWindow,
overlap: usize,
haystack: &mut Vec<u8>,
) -> std::io::Result<usize> {
haystack.clear();
for path in &paths[window.file_range.clone()] {
std::fs::File::open(path)?.read_to_end(haystack)?;
}
let mut gathered = 0usize;
for path in &paths[window.file_range.end..] {
if gathered >= overlap {
break;
}
let want = (overlap - gathered) as u64;
let before = haystack.len();
std::fs::File::open(path)?
.take(want)
.read_to_end(haystack)?;
gathered += haystack.len() - before;
}
Ok(gathered)
}
pub fn scan_paths_paged(
matcher: &GpuLiteralSet,
backend: &dyn VyreBackend,
paths: &[&Path],
window_budget_bytes: usize,
max_matches: u32,
) -> Result<PagedScanResult, vyre::BackendError> {
let region_count = u32::try_from(paths.len()).map_err(|_| {
vyre::BackendError::new(
"scan_paths_paged: file count exceeds the u32 region-id ABI. Fix: fewer files per corpus or shard the corpus.".to_string(),
)
})?;
let mut file_lengths: Vec<usize> = Vec::with_capacity(paths.len());
for path in paths {
let len = std::fs::metadata(path)
.map_err(|error| {
vyre::BackendError::new(format!(
"scan_paths_paged: cannot stat {}: {error}. Fix: ensure every path is a readable regular file.",
path.display()
))
})?
.len();
let len = usize::try_from(len).map_err(|_| {
vyre::BackendError::new(format!(
"scan_paths_paged: {} is {len} bytes, larger than host usize. Fix: pre-split it.",
path.display()
))
})?;
file_lengths.push(len);
}
let windows = plan_corpus_windows(&file_lengths, window_budget_bytes)
.map_err(|error| vyre::BackendError::new(error.to_string()))?;
if windows.is_empty() {
return Ok(finish_result(Vec::new(), 0, 0, Vec::new()));
}
let overlap = paging_overlap(matcher);
let max_window_bytes = windows
.iter()
.map(|window| window.byte_len)
.max()
.unwrap_or(0);
let max_window_regions = windows
.iter()
.map(|window| window.file_range.len())
.max()
.unwrap_or(0) as u32;
let session = matcher.prepare_resident_fused_scan(
backend,
max_window_bytes + overlap + 64,
max_window_regions + 1,
max_matches,
)?;
let mut presence: Vec<u32> = Vec::new();
let mut presence_words: usize = 0;
let mut matches: Vec<GlobalMatch> = Vec::new();
let mut win_presence: Vec<u32> = Vec::new();
let mut win_matches: Vec<Match> = Vec::new();
let mut scratch: Vec<u8> = Vec::new();
for window in &windows {
let mut haystack: Vec<u8> = Vec::new();
let gathered = match fill_window_from_paths(paths, window, overlap, &mut haystack) {
Ok(gathered) => gathered,
Err(error) => {
let _ = session.free(backend);
return Err(vyre::BackendError::new(format!(
"scan_paths_paged: reading window files failed: {error}. Fix: ensure every path is a readable regular file."
)));
}
};
let (region_starts, own_region_count) =
window_region_starts(window, &file_lengths, gathered > 0);
let staging = WindowStaging {
haystack,
region_starts,
own_len: window.byte_len,
own_region_count,
byte_offset: window.byte_offset,
global_region_base: window.global_region_base,
};
session.scan_into(
backend,
&staging.haystack,
&staging.region_starts,
0,
&mut win_presence,
&mut win_matches,
&mut scratch,
)?;
if let Err(error) = globalize_window(
&staging,
&win_presence,
&win_matches,
&mut presence_words,
&mut presence,
&mut matches,
) {
let _ = session.free(backend);
return Err(error);
}
}
session.free(backend)?;
Ok(finish_result(
presence,
region_count,
presence_words,
matches,
))
}
const PAGED_PREFETCH_DEPTH: usize = 1;
pub fn scan_paths_paged_prefetched(
matcher: &GpuLiteralSet,
backend: &dyn VyreBackend,
paths: &[&Path],
window_budget_bytes: usize,
max_matches: u32,
) -> Result<PagedScanResult, vyre::BackendError> {
let region_count = u32::try_from(paths.len()).map_err(|_| {
vyre::BackendError::new(
"scan_paths_paged_prefetched: file count exceeds the u32 region-id ABI. Fix: fewer files per corpus or shard the corpus.".to_string(),
)
})?;
let mut file_lengths: Vec<usize> = Vec::with_capacity(paths.len());
for path in paths {
let len = std::fs::metadata(path)
.map_err(|error| {
vyre::BackendError::new(format!(
"scan_paths_paged_prefetched: cannot stat {}: {error}. Fix: ensure every path is a readable regular file.",
path.display()
))
})?
.len();
file_lengths.push(usize::try_from(len).map_err(|_| {
vyre::BackendError::new(format!(
"scan_paths_paged_prefetched: {} is {len} bytes, larger than host usize. Fix: pre-split it.",
path.display()
))
})?);
}
let windows = plan_corpus_windows(&file_lengths, window_budget_bytes)
.map_err(|error| vyre::BackendError::new(error.to_string()))?;
if windows.is_empty() {
return Ok(finish_result(Vec::new(), 0, 0, Vec::new()));
}
let overlap = paging_overlap(matcher);
let owned_paths: Vec<std::path::PathBuf> =
paths.iter().map(|path| path.to_path_buf()).collect();
let reader_windows = windows.clone();
#[allow(clippy::type_complexity)]
let (tx, rx) = std::sync::mpsc::sync_channel::<std::io::Result<(usize, Vec<u8>, usize)>>(
PAGED_PREFETCH_DEPTH,
);
let reader = std::thread::spawn(move || {
let path_refs: Vec<&Path> = owned_paths.iter().map(|path| path.as_path()).collect();
for (index, window) in reader_windows.iter().enumerate() {
let mut haystack = Vec::new();
let message = fill_window_from_paths(&path_refs, window, overlap, &mut haystack)
.map(|gathered| (index, haystack, gathered));
let forwarded_error = message.is_err();
if tx.send(message).is_err() || forwarded_error {
break;
}
}
});
let max_window_bytes = windows
.iter()
.map(|window| window.byte_len)
.max()
.unwrap_or(0);
let max_window_regions = windows
.iter()
.map(|window| window.file_range.len())
.max()
.unwrap_or(0) as u32;
let session = matcher.prepare_resident_fused_scan(
backend,
max_window_bytes + overlap + 64,
max_window_regions + 1,
max_matches,
)?;
let mut presence: Vec<u32> = Vec::new();
let mut presence_words: usize = 0;
let mut matches: Vec<GlobalMatch> = Vec::new();
let mut win_presence: Vec<u32> = Vec::new();
let mut win_matches: Vec<Match> = Vec::new();
let mut scratch: Vec<u8> = Vec::new();
let mut outcome: Result<(), vyre::BackendError> = Ok(());
for message in rx.iter() {
let (index, haystack, gathered) = match message {
Ok(payload) => payload,
Err(error) => {
outcome = Err(vyre::BackendError::new(format!(
"scan_paths_paged_prefetched: reading window files failed: {error}. Fix: ensure every path is a readable regular file."
)));
break;
}
};
let window = &windows[index];
let (region_starts, own_region_count) =
window_region_starts(window, &file_lengths, gathered > 0);
let staging = WindowStaging {
haystack,
region_starts,
own_len: window.byte_len,
own_region_count,
byte_offset: window.byte_offset,
global_region_base: window.global_region_base,
};
if let Err(error) = session.scan_into(
backend,
&staging.haystack,
&staging.region_starts,
0,
&mut win_presence,
&mut win_matches,
&mut scratch,
) {
outcome = Err(error);
break;
}
if let Err(error) = globalize_window(
&staging,
&win_presence,
&win_matches,
&mut presence_words,
&mut presence,
&mut matches,
) {
outcome = Err(error);
break;
}
}
drop(rx);
let _ = reader.join();
let _ = session.free(backend);
outcome?;
Ok(finish_result(
presence,
region_count,
presence_words,
matches,
))
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_plan_is_a_partition(file_lengths: &[usize], windows: &[CorpusWindow]) {
let mut expected_file = 0usize;
let mut expected_offset = 0u64;
for window in windows {
assert_eq!(
window.file_range.start, expected_file,
"windows must tile file indices with no gap/overlap"
);
assert_eq!(
window.global_region_base as usize, window.file_range.start,
"global_region_base must equal the window's first global file index"
);
assert_eq!(
window.byte_offset, expected_offset,
"byte offsets must be contiguous and monotone"
);
let bytes: usize = file_lengths[window.file_range.clone()].iter().sum();
assert_eq!(
window.byte_len, bytes,
"byte_len must sum the window's files"
);
expected_file = window.file_range.end;
expected_offset += bytes as u64;
}
assert_eq!(
expected_file,
file_lengths.len(),
"the windows must cover every file"
);
let total: u64 = file_lengths.iter().map(|&l| l as u64).sum();
assert_eq!(expected_offset, total, "the windows must cover every byte");
}
#[test]
fn empty_corpus_plans_to_no_windows() {
assert_eq!(plan_corpus_windows(&[], 1024), Ok(Vec::new()));
}
#[test]
fn zero_budget_fails_closed() {
assert_eq!(
plan_corpus_windows(&[10], 0),
Err(PagedCorpusError::ZeroBudget)
);
}
#[test]
fn all_files_in_one_window_when_under_budget() {
let lengths = [100, 200, 300];
let windows = plan_corpus_windows(&lengths, 1024).expect("plan");
assert_eq!(windows.len(), 1);
assert_eq!(windows[0].file_range, 0..3);
assert_eq!(windows[0].byte_len, 600);
assert_eq!(windows[0].global_region_base, 0);
assert_plan_is_a_partition(&lengths, &windows);
}
#[test]
fn splits_at_the_budget_boundary_with_stable_global_ids() {
let lengths = [100, 100, 100, 100, 100];
let windows = plan_corpus_windows(&lengths, 250).expect("plan");
assert_eq!(windows.len(), 3);
assert_eq!(windows[0].file_range, 0..2);
assert_eq!(windows[0].global_region_base, 0);
assert_eq!(windows[0].byte_offset, 0);
assert_eq!(windows[1].file_range, 2..4);
assert_eq!(windows[1].global_region_base, 2);
assert_eq!(windows[1].byte_offset, 200);
assert_eq!(windows[2].file_range, 4..5);
assert_eq!(windows[2].global_region_base, 4);
assert_eq!(windows[2].byte_offset, 400);
assert_plan_is_a_partition(&lengths, &windows);
}
#[test]
fn exact_budget_fit_packs_the_whole_window() {
let lengths = [128, 128];
let windows = plan_corpus_windows(&lengths, 256).expect("plan");
assert_eq!(windows.len(), 1);
assert_eq!(windows[0].byte_len, 256);
assert_plan_is_a_partition(&lengths, &windows);
}
#[test]
fn oversized_file_gets_its_own_window_and_progress_is_guaranteed() {
let lengths = [100, 500, 100];
let windows = plan_corpus_windows(&lengths, 250).expect("plan");
assert_eq!(windows.len(), 3);
assert_eq!(windows[0].file_range, 0..1); assert_eq!(windows[1].file_range, 1..2); assert_eq!(windows[1].byte_len, 500);
assert_eq!(windows[2].file_range, 2..3); assert_plan_is_a_partition(&lengths, &windows);
}
#[test]
fn a_run_of_tiny_files_coalesces_up_to_the_budget() {
let lengths = [10; 100]; let windows = plan_corpus_windows(&lengths, 55).expect("plan"); assert!(windows.iter().all(|w| w.byte_len <= 55));
for window in &windows[..windows.len() - 1] {
assert_eq!(window.file_range.len(), 5, "each full window packs 5 files");
}
assert_plan_is_a_partition(&lengths, &windows);
}
#[test]
fn local_region_starts_are_window_relative_first_zero() {
let lengths = [100, 100, 100, 100, 100];
let windows = plan_corpus_windows(&lengths, 250).expect("plan");
let starts = windows[1].local_region_starts(&lengths);
assert_eq!(starts, vec![0, 100]);
assert_eq!(starts[0], 0, "the first local region start must be 0");
}
#[test]
fn file_over_u32_abi_fails_closed() {
let lengths = [10, (u32::MAX as usize) + 1];
assert_eq!(
plan_corpus_windows(&lengths, usize::MAX),
Err(PagedCorpusError::FileExceedsHaystackAbi {
file_index: 1,
len: (u32::MAX as usize) + 1,
})
);
}
#[test]
fn every_error_variant_names_its_owner_and_fix_path() {
let errors = [
PagedCorpusError::ZeroBudget,
PagedCorpusError::FileExceedsHaystackAbi {
file_index: 0,
len: 1,
},
PagedCorpusError::TooManyRegions { count: 1 },
];
for error in &errors {
match error {
PagedCorpusError::ZeroBudget
| PagedCorpusError::FileExceedsHaystackAbi { .. }
| PagedCorpusError::TooManyRegions { .. } => {}
}
let rendered = error.to_string();
assert!(
rendered.starts_with("paged_corpus:"),
"refusal lacks the owner prefix: {rendered}"
);
assert!(
rendered.contains("Fix:"),
"refusal lacks a fix path: {rendered}"
);
}
}
#[test]
fn paged_fused_scan_equals_single_shot_on_gpu() {
use vyre_driver_wgpu::WgpuBackend;
let backend = match WgpuBackend::shared() {
Ok(backend) => backend,
Err(error) => {
eprintln!("no wgpu backend ({error}); skipping paged fused GPU parity test");
return;
}
};
let patterns: &[&[u8]] = &[b"XYZ", b"secret", b"AB"];
let matcher = GpuLiteralSet::compile(patterns);
let files: Vec<Vec<u8>> = vec![
b"aaaXYZbbb".to_vec(), b"cccsec".to_vec(), b"retdddsecret".to_vec(), b"eeeABfff".to_vec(), ];
let file_refs: Vec<&[u8]> = files.iter().map(|file| file.as_slice()).collect();
let budget = 16usize; let max_matches = 4_096u32;
let lengths: Vec<usize> = files.iter().map(|file| file.len()).collect();
let plan = plan_corpus_windows(&lengths, budget).expect("plan");
assert!(
plan.len() >= 2,
"the test corpus must span multiple windows; got {}",
plan.len()
);
let paged = scan_paged_fused(&matcher, backend.as_ref(), &file_refs, budget, max_matches)
.expect("paged fused scan");
let mut whole = Vec::new();
let mut region_starts = Vec::new();
for file in &files {
region_starts.push(whole.len() as u32);
whole.extend_from_slice(file);
}
let mut single_matches: Vec<Match> = Vec::new();
let single_presence = matcher
.scan_presence_and_positions_by_region(
backend.as_ref(),
&whole,
®ion_starts,
0,
max_matches,
&mut single_matches,
)
.expect("single-shot fused scan");
assert_eq!(
paged.presence, single_presence,
"paged per-region presence must equal the single-shot scan"
);
assert_eq!(paged.region_count as usize, files.len());
assert!(
paged.presence.iter().any(|&word| word != 0),
"the corpus must set presence bits (non-vacuous)"
);
let mut expected: Vec<GlobalMatch> = single_matches
.iter()
.map(|hit| {
let region = region_starts
.partition_point(|&start| start <= hit.start)
.saturating_sub(1);
GlobalMatch {
pattern_id: hit.pattern_id,
region_id: region as u32,
start: u64::from(hit.start),
end: u64::from(hit.end),
}
})
.collect();
expected.sort_unstable_by_key(|hit| (hit.region_id, hit.start, hit.end, hit.pattern_id));
assert_eq!(
paged.matches, expected,
"paged globalized matches must equal the single-shot scan"
);
assert!(
paged
.matches
.iter()
.any(|hit| hit.pattern_id == 1 && hit.region_id == 1),
"the window-boundary-spanning `secret` (region 1) must be found"
);
}
#[test]
fn timed_paged_fused_scan_equals_untimed_and_reports_timing_on_gpu() {
use vyre_driver_wgpu::WgpuBackend;
let backend = match WgpuBackend::shared() {
Ok(backend) => backend,
Err(error) => {
eprintln!("no wgpu backend ({error}); skipping timed paged GPU test");
return;
}
};
let patterns: &[&[u8]] = &[b"XYZ", b"secret", b"AB"];
let matcher = GpuLiteralSet::compile(patterns);
let files: Vec<Vec<u8>> = vec![
b"aaaXYZbbb".to_vec(),
b"cccsec".to_vec(),
b"retdddsecret".to_vec(),
b"eeeABfff".to_vec(),
];
let file_refs: Vec<&[u8]> = files.iter().map(|file| file.as_slice()).collect();
let budget = 16usize;
let max_matches = 4_096u32;
let untimed = scan_paged_fused(&matcher, backend.as_ref(), &file_refs, budget, max_matches)
.expect("untimed paged fused scan");
let (timed_result, timing) =
scan_paged_fused_timed(&matcher, backend.as_ref(), &file_refs, budget, max_matches)
.expect("timed paged fused scan");
assert_eq!(
timed_result, untimed,
"the timed paged scan result must equal the untimed paged scan result"
);
let lengths: Vec<usize> = files.iter().map(|file| file.len()).collect();
let plan = plan_corpus_windows(&lengths, budget).expect("plan");
assert_eq!(
timing.windows as usize,
plan.len(),
"timing.windows must equal the number of planned windows"
);
assert!(timing.windows >= 2, "the corpus must span multiple windows");
let corpus_bytes: u64 = files.iter().map(|file| file.len() as u64).sum();
assert_eq!(
timing.bytes_scanned, corpus_bytes,
"bytes_scanned must equal the corpus size (overlap excluded)"
);
assert!(timing.wall_ns > 0, "wall_ns must be a real measurement");
assert!(
timing.device_ns.is_some(),
"the wgpu backend has a device timer; device_ns must be Some, not a fabricated absence"
);
assert!(
timing.device_ns.unwrap() > 0,
"device_ns must be a real non-zero kernel time on the GPU"
);
}
#[test]
fn timed_paged_fused_scan_empty_corpus_reports_zero_timing() {
use vyre_driver_reference::CpuRefBackend;
let matcher = GpuLiteralSet::compile(&[b"secret".as_slice()]);
let (result, timing) = scan_paged_fused_timed(&matcher, &CpuRefBackend, &[], 64, 16)
.expect("empty timed scan");
assert_eq!(result.region_count, 0);
assert!(result.matches.is_empty());
assert_eq!(
timing,
PagedScanTiming {
windows: 0,
bytes_scanned: 0,
wall_ns: 0,
device_ns: Some(0)
},
"an empty corpus times as zero windows with a present (Some(0)) device aggregate"
);
}
#[test]
fn sharded_fused_scan_equals_single_device_across_device_set_sizes_on_gpu() {
use vyre_driver_wgpu::WgpuBackend;
let backend = match WgpuBackend::shared() {
Ok(backend) => backend,
Err(error) => {
eprintln!("no wgpu backend ({error}); skipping sharded fused GPU parity test");
return;
}
};
let device: &dyn VyreBackend = backend.as_ref();
let patterns: &[&[u8]] = &[b"XYZ", b"secret", b"AB"];
let matcher = GpuLiteralSet::compile(patterns);
let files: Vec<Vec<u8>> = vec![
b"aaaXYZbbb".to_vec(),
b"cccsec".to_vec(),
b"retdddsecret".to_vec(),
b"eeeABfff".to_vec(),
];
let file_refs: Vec<&[u8]> = files.iter().map(|file| file.as_slice()).collect();
let budget = 16usize; let max_matches = 4_096u32;
let single = scan_paged_fused(&matcher, device, &file_refs, budget, max_matches)
.expect("single-device paged scan");
let lengths: Vec<usize> = files.iter().map(|file| file.len()).collect();
assert!(
plan_corpus_windows(&lengths, budget).expect("plan").len() >= 2,
"the corpus must span multiple windows so sharding distributes work"
);
let one = scan_sharded_fused(&matcher, &[device], &file_refs, budget, max_matches)
.expect("sharded scan over 1 device");
assert_eq!(
one, single,
"sharded scan over a 1-device set must equal the single-device paged scan"
);
let three = scan_sharded_fused(
&matcher,
&[device, device, device],
&file_refs,
budget,
max_matches,
)
.expect("sharded scan over 3 devices");
assert_eq!(
three, single,
"sharded scan over a 3-device set must equal the single-device paged scan (parity_policy)"
);
assert!(
three
.matches
.iter()
.any(|hit| hit.pattern_id == 1 && hit.region_id == 1),
"the window-boundary-spanning `secret` must survive multi-shard aggregation"
);
let weighted = scan_sharded_fused_weighted(
&matcher,
&[device, device],
&[3, 1],
&file_refs,
budget,
max_matches,
)
.expect("weighted sharded scan");
assert_eq!(
weighted, single,
"throughput-weighted sharding must equal the single-device paged scan"
);
assert!(
scan_sharded_fused(&matcher, &[], &file_refs, budget, max_matches).is_err(),
"an empty backend set must fail closed, not silently scan nothing"
);
assert!(
scan_sharded_fused_weighted(
&matcher,
&[device, device],
&[1],
&file_refs,
budget,
max_matches
)
.is_err(),
"a weights/backends length mismatch must fail closed"
);
}
#[test]
fn parallel_sharded_dispatch_across_four_concurrent_handles_equals_single_shot_on_gpu() {
use vyre_driver_wgpu::WgpuBackend;
let backend = match WgpuBackend::shared() {
Ok(backend) => backend,
Err(error) => {
eprintln!("no wgpu backend ({error}); skipping parallel sharded stress test");
return;
}
};
let device: &dyn VyreBackend = backend.as_ref();
let patterns: &[&[u8]] = &[b"XYZ", b"secret", b"AB"];
let matcher = GpuLiteralSet::compile(patterns);
let mut files: Vec<Vec<u8>> = Vec::new();
for i in 0..32usize {
if i == 10 {
files.push(b"begin10sec".to_vec()); } else if i == 11 {
files.push(b"retTAILb11".to_vec()); } else {
let mut file = Vec::new();
file.extend_from_slice(b"pad");
if i % 3 == 0 {
file.extend_from_slice(b"XYZ");
}
if i % 4 == 0 {
file.extend_from_slice(b"secret");
}
if i % 5 == 0 {
file.extend_from_slice(b"AB");
}
file.extend_from_slice(format!("z{i:02}").as_bytes());
files.push(file);
}
}
let file_refs: Vec<&[u8]> = files.iter().map(|file| file.as_slice()).collect();
let budget = 24usize; let max_matches = 4_096u32;
let lengths: Vec<usize> = files.iter().map(|file| file.len()).collect();
let window_count = plan_corpus_windows(&lengths, budget).expect("plan").len();
assert!(
window_count >= 8,
"the stress corpus must span many windows so 4 shards run concurrently; got {window_count}"
);
let single = scan_paged_fused(&matcher, device, &file_refs, budget, max_matches)
.expect("single-device paged scan");
let devices: [&dyn VyreBackend; 4] = [device, device, device, device];
let (parallel, timing) =
scan_sharded_fused_timed(&matcher, &devices, &file_refs, budget, max_matches)
.expect("parallel 4-shard sharded scan");
assert_eq!(
parallel, single,
"parallel 4-shard dispatch must equal the single-device paged scan (parity is interleave-independent)"
);
assert!(
parallel.presence.iter().any(|&word| word != 0),
"the corpus must set presence bits (non-vacuous)"
);
assert!(
parallel.matches.iter().any(|hit| hit.pattern_id == 1),
"the corpus contains `secret` matches that must survive parallel aggregation"
);
assert_eq!(
timing.shards.len(),
4,
"one timing slot per device in the 4-handle set"
);
for (shard, entry) in timing.shards.iter().enumerate() {
assert_eq!(
entry.shard as usize, shard,
"shard timing is in device-set order"
);
assert!(
entry.windows > 0,
"shard {shard} did no work, parallel dispatch failed to distribute across all 4 devices"
);
}
let total_windows: u32 = timing.shards.iter().map(|entry| entry.windows).sum();
assert_eq!(
total_windows as usize, window_count,
"every planned window must be dispatched by exactly one shard"
);
let total_bytes: u64 = timing.shards.iter().map(|entry| entry.bytes_scanned).sum();
let corpus_bytes: u64 = files.iter().map(|file| file.len() as u64).sum();
assert_eq!(
total_bytes, corpus_bytes,
"the per-shard bytes_scanned must sum to the corpus size (overlap excluded)"
);
}
#[test]
fn shard_assignment_round_robins_and_honors_throughput_weights() {
let lens = [10usize, 10, 10, 10];
assert_eq!(shard_assignment(&lens, 3, None), vec![0, 1, 2, 0]);
assert_eq!(
shard_assignment(&lens, 3, Some(&[1, 1, 1])),
vec![0, 1, 2, 0]
);
let assignment = shard_assignment(&lens, 2, Some(&[3, 1]));
let shard0 = assignment.iter().filter(|&&s| s == 0).count();
let shard1 = assignment.iter().filter(|&&s| s == 1).count();
assert_eq!(
(shard0, shard1),
(3, 1),
"a 3:1 throughput weight must give shard 0 three of four equal windows"
);
let assignment = shard_assignment(&lens, 2, Some(&[0, 1]));
assert!(
assignment.contains(&0) && assignment.contains(&1),
"a zero-weight shard must still receive work (treated as weight 1)"
);
}
mod shard_assignment_props {
use super::*;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(4_000))]
#[test]
fn shard_assignment_is_a_valid_total_partition(
window_lens in proptest::collection::vec(1usize..=1_000_000, 1..64),
shard_count in 1usize..=8,
use_weights in any::<bool>(),
weight_seed in any::<u64>(),
) {
let weights: Vec<u32> = (0..shard_count)
.map(|i| ((weight_seed >> ((i % 8) * 8)) as u32) % 5)
.collect();
let assignment = if use_weights {
shard_assignment(&window_lens, shard_count, Some(&weights))
} else {
shard_assignment(&window_lens, shard_count, None)
};
prop_assert_eq!(assignment.len(), window_lens.len());
for &shard in &assignment {
prop_assert!(
shard < shard_count,
"assignment {} out of range for {} shards",
shard,
shard_count
);
}
let mut per_shard = vec![0u64; shard_count];
for (index, &shard) in assignment.iter().enumerate() {
per_shard[shard] += window_lens[index] as u64;
}
let total: u64 = window_lens.iter().map(|&l| l as u64).sum();
prop_assert_eq!(
per_shard.iter().sum::<u64>(),
total,
"byte-work must be conserved across shards"
);
if !use_weights {
for (index, &shard) in assignment.iter().enumerate() {
prop_assert_eq!(
shard,
index % shard_count,
"unweighted assignment must be round-robin"
);
}
}
}
}
}
#[test]
fn sharded_timed_scan_reports_honest_per_shard_timing_on_gpu() {
use vyre_driver_wgpu::WgpuBackend;
let backend = match WgpuBackend::shared() {
Ok(backend) => backend,
Err(error) => {
eprintln!("no wgpu backend ({error}); skipping sharded timed GPU test");
return;
}
};
let device: &dyn VyreBackend = backend.as_ref();
let patterns: &[&[u8]] = &[b"XYZ", b"secret", b"AB"];
let matcher = GpuLiteralSet::compile(patterns);
let files: Vec<Vec<u8>> = vec![
b"aaaXYZbbb".to_vec(), b"cccsec".to_vec(), b"retdddsecret".to_vec(), b"eeeABfff".to_vec(), ];
let file_refs: Vec<&[u8]> = files.iter().map(|file| file.as_slice()).collect();
let corpus_bytes: u64 = files.iter().map(|file| file.len() as u64).sum();
let budget = 16usize;
let max_matches = 4_096u32;
let set: [&dyn VyreBackend; 2] = [device, device];
let untimed =
scan_sharded_fused(&matcher, &set, &file_refs, budget, max_matches).expect("untimed");
let (timed_result, timing) =
scan_sharded_fused_timed(&matcher, &set, &file_refs, budget, max_matches)
.expect("timed sharded scan");
assert_eq!(
timed_result, untimed,
"timed sharded result must equal the untimed sharded result"
);
assert_eq!(timing.shards.len(), 2);
assert_eq!(timing.shards[0].shard, 0);
assert_eq!(timing.shards[1].shard, 1);
let total_windows: u32 = timing.shards.iter().map(|shard| shard.windows).sum();
let plan_windows = plan_corpus_windows(
&files.iter().map(|file| file.len()).collect::<Vec<_>>(),
budget,
)
.expect("plan")
.len() as u32;
assert_eq!(
total_windows, plan_windows,
"per-shard window counts must sum to the total window count"
);
assert_eq!(
timing.shards[0].windows, 2,
"round-robin gives shard 0 windows 0 and 2"
);
assert_eq!(
timing.shards[1].windows, 1,
"round-robin gives shard 1 window 1"
);
let total_bytes: u64 = timing.shards.iter().map(|shard| shard.bytes_scanned).sum();
assert_eq!(
total_bytes, corpus_bytes,
"per-shard byte-work must sum to the corpus size (overlap excluded)"
);
for shard in &timing.shards {
if shard.windows > 0 {
assert!(shard.wall_ns > 0, "an active shard must report wall time");
assert!(
shard.device_ns.is_some_and(|ns| ns > 0),
"an active shard on the GPU must report non-zero device time"
);
}
}
}
#[test]
fn pattern_sharded_scan_equals_full_rule_database_on_gpu() {
use vyre_driver_wgpu::WgpuBackend;
let backend = match WgpuBackend::shared() {
Ok(backend) => backend,
Err(error) => {
eprintln!("no wgpu backend ({error}); skipping pattern-sharded GPU parity test");
return;
}
};
let device: &dyn VyreBackend = backend.as_ref();
let full_patterns: &[&[u8]] = &[b"AKIA", b"secret", b"token", b"AB"];
let full = GpuLiteralSet::compile(full_patterns);
let haystack: &[u8] =
b"AKIA here, a secret token, and AB plus another secret and a token trailing";
let mut expected = full.scan_all(device, haystack).expect("full scan");
expected.sort_unstable();
assert!(!expected.is_empty(), "the corpus must match some rules");
let shard0_matcher = GpuLiteralSet::compile(&[b"AKIA".as_slice(), b"token".as_slice()]);
let shard1_matcher = GpuLiteralSet::compile(&[b"secret".as_slice(), b"AB".as_slice()]);
let shards = [
PatternShard {
matcher: &shard0_matcher,
global_pattern_ids: &[0, 2], },
PatternShard {
matcher: &shard1_matcher,
global_pattern_ids: &[1, 3], },
];
let striped_one =
scan_pattern_sharded(&shards, &[device], haystack).expect("striped scan (1 device)");
assert_eq!(
striped_one, expected,
"striped rule-database scan must equal the full-database scan"
);
let striped_two = scan_pattern_sharded(&shards, &[device, device], haystack)
.expect("striped scan (2 devices)");
assert_eq!(
striped_two, expected,
"striped scan across a 2-device set must equal the full-database scan"
);
assert!(
scan_pattern_sharded(&shards, &[], haystack).is_err(),
"an empty backend set must fail closed"
);
let bad_matcher = GpuLiteralSet::compile(&[b"AKIA".as_slice(), b"token".as_slice()]);
let bad = [PatternShard {
matcher: &bad_matcher,
global_pattern_ids: &[0], }];
let error = scan_pattern_sharded(&bad, &[device], b"a token here")
.expect_err("a malformed shard map must fail closed, not drop the finding");
assert!(
error.to_string().contains("global_pattern_ids"),
"the error must name the missing global id mapping: {error}"
);
}
#[test]
fn fill_window_from_paths_reads_own_bytes_plus_overlap_prefix() {
let dir = std::env::temp_dir().join(format!("vyre_paged_fill_{}", std::process::id()));
std::fs::create_dir_all(&dir).expect("create temp dir");
let files: [&[u8]; 3] = [b"AAAA", b"BBBB", b"CCCC"];
let mut paths = Vec::new();
for (index, bytes) in files.iter().enumerate() {
let path = dir.join(format!("{index}.bin"));
std::fs::write(&path, bytes).expect("write file");
paths.push(path);
}
let path_refs: Vec<&Path> = paths.iter().map(|path| path.as_path()).collect();
let lengths = [4usize, 4, 4];
let windows = plan_corpus_windows(&lengths, 8).expect("plan");
assert_eq!(windows[0].file_range, 0..2);
let mut haystack = Vec::new();
let gathered =
fill_window_from_paths(&path_refs, &windows[0], 3, &mut haystack).expect("fill");
let _ = std::fs::remove_dir_all(&dir);
assert_eq!(
&haystack[..8],
b"AAAABBBB",
"own bytes are the window's files"
);
assert_eq!(
&haystack[8..],
b"CCC",
"then exactly 3 overlap bytes of the next file"
);
assert_eq!(gathered, 3);
}
#[test]
fn scan_paths_paged_equals_in_memory_on_gpu() {
use vyre_driver_wgpu::WgpuBackend;
let backend = match WgpuBackend::shared() {
Ok(backend) => backend,
Err(error) => {
eprintln!("no wgpu backend ({error}); skipping disk-paged GPU parity test");
return;
}
};
let patterns: &[&[u8]] = &[b"XYZ", b"secret", b"AB"];
let matcher = GpuLiteralSet::compile(patterns);
let files: Vec<Vec<u8>> = vec![
b"aaaXYZbbb".to_vec(),
b"cccsec".to_vec(),
b"retdddsecret".to_vec(),
b"eeeABfff".to_vec(),
];
let file_refs: Vec<&[u8]> = files.iter().map(|file| file.as_slice()).collect();
let budget = 16usize;
let max_matches = 4_096u32;
let dir = std::env::temp_dir().join(format!("vyre_paged_paths_{}", std::process::id()));
std::fs::create_dir_all(&dir).expect("create temp dir");
let mut paths = Vec::new();
for (index, bytes) in files.iter().enumerate() {
let path = dir.join(format!("{index:02}.bin"));
std::fs::write(&path, bytes).expect("write file");
paths.push(path);
}
let path_refs: Vec<&Path> = paths.iter().map(|path| path.as_path()).collect();
let in_memory =
scan_paged_fused(&matcher, backend.as_ref(), &file_refs, budget, max_matches)
.expect("in-memory paged scan");
let from_disk =
scan_paths_paged(&matcher, backend.as_ref(), &path_refs, budget, max_matches)
.expect("disk paged scan");
let _ = std::fs::remove_dir_all(&dir);
let lengths: Vec<usize> = files.iter().map(|file| file.len()).collect();
assert!(plan_corpus_windows(&lengths, budget).expect("plan").len() >= 2);
assert_eq!(
from_disk, in_memory,
"the disk-backed paged scan must equal the in-memory paged scan"
);
assert!(!from_disk.matches.is_empty(), "non-vacuous");
}
#[test]
fn scan_paths_paged_prefetched_equals_sync_on_gpu() {
use vyre_driver_wgpu::WgpuBackend;
let backend = match WgpuBackend::shared() {
Ok(backend) => backend,
Err(error) => {
eprintln!("no wgpu backend ({error}); skipping prefetched disk-paged GPU test");
return;
}
};
let patterns: &[&[u8]] = &[b"XYZ", b"secret", b"AB"];
let matcher = GpuLiteralSet::compile(patterns);
let files: Vec<Vec<u8>> = vec![
b"aaaXYZbbb".to_vec(),
b"cccsec".to_vec(),
b"retdddsecret".to_vec(),
b"eeeABfff".to_vec(),
];
let budget = 16usize;
let max_matches = 4_096u32;
let dir = std::env::temp_dir().join(format!("vyre_paged_prefetch_{}", std::process::id()));
std::fs::create_dir_all(&dir).expect("create temp dir");
let mut paths = Vec::new();
for (index, bytes) in files.iter().enumerate() {
let path = dir.join(format!("{index:02}.bin"));
std::fs::write(&path, bytes).expect("write file");
paths.push(path);
}
let path_refs: Vec<&Path> = paths.iter().map(|path| path.as_path()).collect();
let sync = scan_paths_paged(&matcher, backend.as_ref(), &path_refs, budget, max_matches)
.expect("synchronous disk paged scan");
let prefetched = scan_paths_paged_prefetched(
&matcher,
backend.as_ref(),
&path_refs,
budget,
max_matches,
)
.expect("prefetched disk paged scan");
let _ = std::fs::remove_dir_all(&dir);
let lengths: Vec<usize> = files.iter().map(|file| file.len()).collect();
assert!(plan_corpus_windows(&lengths, budget).expect("plan").len() >= 2);
assert_eq!(
prefetched, sync,
"the prefetched disk paged scan must equal the synchronous disk paged scan"
);
assert!(!prefetched.matches.is_empty(), "non-vacuous");
}
#[test]
fn async_paged_fused_scan_equals_sync_on_gpu() {
use vyre_driver_wgpu::WgpuBackend;
let backend = match WgpuBackend::shared() {
Ok(backend) => backend,
Err(error) => {
eprintln!("no wgpu backend ({error}); skipping async paged GPU parity test");
return;
}
};
let patterns: &[&[u8]] = &[b"XYZ", b"secret", b"AB"];
let matcher = GpuLiteralSet::compile(patterns);
let files: Vec<Vec<u8>> = vec![
b"aaaXYZbbb".to_vec(),
b"cccsec".to_vec(),
b"retdddsecret".to_vec(),
b"eeeABfff".to_vec(),
];
let file_refs: Vec<&[u8]> = files.iter().map(|file| file.as_slice()).collect();
let budget = 16usize;
let max_matches = 4_096u32;
let lengths: Vec<usize> = files.iter().map(|file| file.len()).collect();
assert!(
plan_corpus_windows(&lengths, budget).expect("plan").len() >= 2,
"the test corpus must span multiple windows"
);
let sync = scan_paged_fused(&matcher, backend.as_ref(), &file_refs, budget, max_matches)
.expect("sync paged scan");
let overlapped =
scan_paged_fused_async(&matcher, backend.as_ref(), &file_refs, budget, max_matches)
.expect("async paged scan");
assert_eq!(
overlapped, sync,
"the async pipelined paged scan must equal the synchronous paged scan bit-for-bit"
);
assert!(
!overlapped.matches.is_empty(),
"the corpus must produce matches (non-vacuous)"
);
assert!(
overlapped
.matches
.iter()
.any(|hit| hit.pattern_id == 1 && hit.region_id == 1),
"the window-boundary-spanning `secret` (region 1) must be found in the async path too"
);
}
}