use crate::engine::dfa::buffers::{buffer_bytes, gpu_err, match_buffer_size};
use crate::engine::dfa::{BYTE_CLASSES, MAX_DFA_MATCHES, SENTINEL_NO_ACCEPT};
use vyre::error::{Error, Result};
pub(crate) fn compile_pipeline(device: &wgpu::Device) -> Result<wgpu::ComputePipeline> {
vyre::ops::registry::gate::verify_certificate(
vyre::ops::match_ops::dfa_scan::DfaScan::SPEC.id(),
)
.map_err(|source| Error::Dfa {
message: source.to_string(),
})?;
let wgsl = vyre::lower::wgsl::lower(&vyre::ops::match_ops::dfa_scan::DfaScan::program())
.map_err(|source| Error::Dfa {
message: format!("failed to lower DFA scan IR to WGSL: {source}. Fix: reject this runtime build or repair the DFA scan IR composition."),
})?;
crate::runtime::compile_compute_pipeline(device, "vyre dfa pipeline", &wgsl, "main")
.map_err(|source| Error::Dfa {
message: format!(
"failed to compile DFA scan pipeline: {source}. Fix: repair the DFA scan WGSL or GPU runtime configuration."
),
})
}
pub(crate) fn validate_compile_inputs(
device: &wgpu::Device,
transitions: &[u32],
state_count: usize,
accept_states: &[(u32, u32)],
output_links: &[u32],
pattern_lengths: &[u32],
max_matches: u32,
) -> Result<()> {
if state_count == 0 {
return gpu_err(
"DFA state_count is zero. Fix: compile a table with at least the start state.",
);
}
if u32::try_from(state_count).is_err() {
return gpu_err(&format!(
"DFA state_count {state_count} exceeds u32::MAX. Fix: split the automaton or reduce states."
));
}
if max_matches == 0 {
return gpu_err("DFA max_matches is zero. Fix: use a positive match capacity.");
}
if max_matches > MAX_DFA_MATCHES {
return gpu_err(&format!(
"DFA max_matches {max_matches} exceeds {MAX_DFA_MATCHES}. Fix: lower max_matches or shard the scan."
));
}
let expected = state_count
.checked_mul(BYTE_CLASSES)
.ok_or_else(|| Error::Dfa {
message: format!(
"DFA state_count {state_count} * 256 overflows usize. Fix: reduce state_count."
),
})?;
if transitions.len() != expected {
return gpu_err(&format!(
"DFA transition table has {} entries but state_count * 256 is {expected}. Fix: pass a full byte-indexed transition table.",
transitions.len()
));
}
if output_links.len() != state_count {
return gpu_err(&format!(
"DFA output_links has {} entries but state_count is {state_count}. Fix: pass one output link per state.",
output_links.len()
));
}
for (index, &target) in transitions.iter().enumerate() {
let target_index = usize::try_from(target).map_err(|source| Error::Dfa {
message: format!(
"DFA transition {index} target {target} cannot fit usize: {source}. Fix: reject this transition table on this platform."
),
})?;
if target_index >= state_count {
return gpu_err(&format!(
"DFA transition {index} targets state {target}, outside 0..{state_count}. Fix: ensure every transition target is valid."
));
}
}
let mut seen_accept = vec![false; state_count];
for &(state, pattern_id) in accept_states {
let state_index = usize::try_from(state).map_err(|source| Error::Dfa {
message: format!(
"DFA accept state {state} cannot fit usize: {source}. Fix: reject this accept table on this platform."
),
})?;
if state_index >= state_count {
return gpu_err(&format!(
"DFA accept state {state} is outside 0..{state_count}. Fix: remove or correct the accept entry."
));
}
if seen_accept[state_index] {
return gpu_err(&format!(
"DFA accept state {state} appears more than once. Fix: choose one direct pattern id per state and use output_links for suffix accepts."
));
}
seen_accept[state_index] = true;
let pattern_index = usize::try_from(pattern_id).map_err(|source| Error::Dfa {
message: format!(
"DFA accept pattern {pattern_id} cannot fit usize: {source}. Fix: reject this accept table on this platform."
),
})?;
match pattern_lengths.get(pattern_index) {
Some(0) => {
return gpu_err(&format!(
"DFA accept pattern {pattern_id} has zero length. Fix: exclude empty patterns from GPU DFA scans."
));
}
Some(_) => {}
None => {
return gpu_err(&format!(
"DFA accept pattern {pattern_id} has no pattern length. Fix: provide pattern_lengths[{pattern_id}]."
));
}
}
}
validate_output_links(state_count, output_links)?;
let limit = u64::from(device.limits().max_storage_buffer_binding_size);
for (label, bytes) in [
("transition", buffer_bytes(transitions.len())?),
("accept", buffer_bytes(state_count)?),
("output link", buffer_bytes(state_count)?),
("pattern length", buffer_bytes(pattern_lengths.len())?),
("match", match_buffer_size(max_matches)?),
] {
if bytes > limit {
return gpu_err(&format!(
"DFA {label} buffer is {bytes} bytes but adapter max_storage_buffer_binding_size is {limit}. Fix: reduce DFA size or use a larger adapter."
));
}
}
Ok(())
}
pub fn validate_output_links(state_count: usize, output_links: &[u32]) -> Result<()> {
for (state, &target) in output_links.iter().enumerate() {
let target_index = if target == SENTINEL_NO_ACCEPT {
None
} else {
Some(usize::try_from(target).map_err(|source| Error::Dfa {
message: format!(
"DFA output link target {target} cannot fit usize: {source}. Fix: reject this output-link table on this platform."
),
})?)
};
if target_index.is_some_and(|index| index >= state_count) {
return gpu_err(&format!(
"DFA output link from state {state} targets state {target}, outside 0..{state_count}. Fix: use a valid state or 0xFFFF_FFFF."
));
}
let mut seen = vec![false; state_count];
let mut next = target;
while next != SENTINEL_NO_ACCEPT {
let index = usize::try_from(next).map_err(|source| Error::Dfa {
message: format!(
"DFA output link state {next} cannot fit usize: {source}. Fix: reject this output-link chain on this platform."
),
})?;
if seen[index] {
return gpu_err(&format!(
"DFA output link chain from state {state} cycles at state {next}. Fix: emit acyclic output links."
));
}
seen[index] = true;
next = output_links[index];
}
}
Ok(())
}
pub(crate) fn build_accept_map(
state_count: usize,
accept_states: &[(u32, u32)],
) -> Result<Vec<u32>> {
let mut accept_map = vec![SENTINEL_NO_ACCEPT; state_count];
for &(state, pattern_id) in accept_states {
let index = usize::try_from(state).map_err(|source| Error::Dfa {
message: format!(
"DFA accept state {state} cannot fit usize while building accept map: {source}. Fix: reject this malformed accept table."
),
})?;
let slot = accept_map.get_mut(index).ok_or_else(|| Error::Dfa {
message: format!(
"DFA accept state {state} is outside 0..{state_count} while building accept map. Fix: reject this malformed accept table."
),
})?;
*slot = pattern_id;
}
Ok(accept_map)
}