Skip to main content

vyre_driver/backend/
regex_accelerator.rs

1//! Regex accelerator backend capability contract.
2//!
3//! Hardware regex engines are useful only when the backend exposes an explicit
4//! capability record. This module keeps unsupported devices fail-closed and
5//! makes software fallback, stream mode, match schema, parity, and transfer
6//! accounting visible to benchmark evidence.
7
8use super::BackendError;
9
10/// Schema version for regex accelerator benchmark evidence.
11pub const REGEX_ACCELERATOR_EVIDENCE_SCHEMA_VERSION: u32 = 1;
12
13/// Regex accelerator class advertised by a backend.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum RegexAcceleratorClass {
16    /// RXP-like hardware regex acceleration.
17    RxpLike,
18    /// DPU-attached regex acceleration.
19    Dpu,
20    /// FPGA-style regex offload.
21    Fpga,
22    /// Software reference or fallback path.
23    Software,
24}
25
26impl RegexAcceleratorClass {
27    /// Stable label for evidence artifacts.
28    #[must_use]
29    pub const fn as_str(self) -> &'static str {
30        match self {
31            Self::RxpLike => "rxp_like",
32            Self::Dpu => "dpu",
33            Self::Fpga => "fpga",
34            Self::Software => "software",
35        }
36    }
37}
38
39/// Regex stream mode supported by an accelerator.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum RegexAcceleratorStreamMode {
42    /// No accelerator stream mode is available.
43    Unavailable,
44    /// Independent block scans.
45    Block,
46    /// Stateless streaming over segmented input.
47    Streaming,
48    /// Stateful streaming that preserves cross-chunk automata state.
49    StatefulStreaming,
50}
51
52impl RegexAcceleratorStreamMode {
53    /// Stable label for evidence artifacts.
54    #[must_use]
55    pub const fn as_str(self) -> &'static str {
56        match self {
57            Self::Unavailable => "unavailable",
58            Self::Block => "block",
59            Self::Streaming => "streaming",
60            Self::StatefulStreaming => "stateful_streaming",
61        }
62    }
63}
64
65/// Match schema emitted by an accelerator.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67pub enum RegexAcceleratorMatchSchema {
68    /// No accelerator match schema is available.
69    Unavailable,
70    /// Start/end offsets only.
71    Offsets,
72    /// Pattern id plus start/end offsets.
73    PatternIdOffsets,
74    /// Pattern id, stream id, and start/end offsets.
75    StreamPatternIdOffsets,
76}
77
78impl RegexAcceleratorMatchSchema {
79    /// Stable label for evidence artifacts.
80    #[must_use]
81    pub const fn as_str(self) -> &'static str {
82        match self {
83            Self::Unavailable => "unavailable",
84            Self::Offsets => "offsets",
85            Self::PatternIdOffsets => "pattern_id_offsets",
86            Self::StreamPatternIdOffsets => "stream_pattern_id_offsets",
87        }
88    }
89}
90
91/// Backend capability for hardware or software regex acceleration.
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub struct RegexAcceleratorCapability {
94    /// Backend id that owns this capability.
95    pub backend: &'static str,
96    /// Accelerator class.
97    pub accelerator_class: RegexAcceleratorClass,
98    /// True only when the backend can execute this accelerator class.
99    pub supported: bool,
100    /// Device signature for supported accelerators.
101    pub device_signature: &'static str,
102    /// Maximum rule count accepted by the accelerator.
103    pub rule_capacity: u32,
104    /// Stream mode accepted by the accelerator.
105    pub stream_mode: RegexAcceleratorStreamMode,
106    /// Match schema emitted by the accelerator.
107    pub match_schema: RegexAcceleratorMatchSchema,
108    /// Unsupported reason for fail-closed capability records.
109    pub unsupported_reason: &'static str,
110}
111
112impl RegexAcceleratorCapability {
113    /// Construct a fail-closed unsupported capability record.
114    #[must_use]
115    pub const fn unsupported(
116        backend: &'static str,
117        accelerator_class: RegexAcceleratorClass,
118        unsupported_reason: &'static str,
119    ) -> Self {
120        Self {
121            backend,
122            accelerator_class,
123            supported: false,
124            device_signature: "",
125            rule_capacity: 0,
126            stream_mode: RegexAcceleratorStreamMode::Unavailable,
127            match_schema: RegexAcceleratorMatchSchema::Unavailable,
128            unsupported_reason,
129        }
130    }
131
132    /// Construct a supported accelerator capability record.
133    #[must_use]
134    pub const fn supported(
135        backend: &'static str,
136        accelerator_class: RegexAcceleratorClass,
137        device_signature: &'static str,
138        rule_capacity: u32,
139        stream_mode: RegexAcceleratorStreamMode,
140        match_schema: RegexAcceleratorMatchSchema,
141    ) -> Self {
142        Self {
143            backend,
144            accelerator_class,
145            supported: true,
146            device_signature,
147            rule_capacity,
148            stream_mode,
149            match_schema,
150            unsupported_reason: "",
151        }
152    }
153
154    /// Fail closed when a caller requires hardware regex acceleration.
155    ///
156    /// # Errors
157    ///
158    /// Returns [`BackendError::UnsupportedFeature`] when this backend does not
159    /// advertise the requested accelerator class.
160    pub fn require_supported(self) -> Result<Self, BackendError> {
161        if self.supported {
162            return Ok(self);
163        }
164        Err(BackendError::UnsupportedFeature {
165            name: format!("regex_accelerator:{}", self.accelerator_class.as_str()),
166            backend: self.backend.to_string(),
167        })
168    }
169
170    /// Emit benchmark evidence for this regex accelerator capability.
171    #[must_use]
172    pub const fn evidence(self, transfer_bytes: u64) -> RegexAcceleratorEvidence {
173        RegexAcceleratorEvidence {
174            schema_version: REGEX_ACCELERATOR_EVIDENCE_SCHEMA_VERSION,
175            backend: self.backend,
176            accelerator_class: self.accelerator_class,
177            supported: self.supported,
178            device_signature: self.device_signature,
179            rule_capacity: self.rule_capacity,
180            stream_mode: self.stream_mode,
181            match_schema: self.match_schema,
182            unsupported_reason: self.unsupported_reason,
183            transfer_bytes,
184            match_parity_required: true,
185        }
186    }
187}
188
189/// Regex accelerator benchmark evidence.
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub struct RegexAcceleratorEvidence {
192    /// Evidence schema version.
193    pub schema_version: u32,
194    /// Backend id that produced this evidence.
195    pub backend: &'static str,
196    /// Accelerator class under comparison.
197    pub accelerator_class: RegexAcceleratorClass,
198    /// True only when a real capability record exists.
199    pub supported: bool,
200    /// Device signature for supported accelerators.
201    pub device_signature: &'static str,
202    /// Maximum rule count accepted by the accelerator.
203    pub rule_capacity: u32,
204    /// Stream mode accepted by the accelerator.
205    pub stream_mode: RegexAcceleratorStreamMode,
206    /// Match schema emitted by the accelerator.
207    pub match_schema: RegexAcceleratorMatchSchema,
208    /// Unsupported reason when no accelerator is available.
209    pub unsupported_reason: &'static str,
210    /// Host/device transfer bytes attributed to the accelerator path.
211    pub transfer_bytes: u64,
212    /// True when software and accelerator outputs must be compared.
213    pub match_parity_required: bool,
214}
215
216impl RegexAcceleratorEvidence {
217    /// Return true when the evidence cannot overclaim accelerator support.
218    #[must_use]
219    pub fn is_complete(self) -> bool {
220        if self.schema_version != REGEX_ACCELERATOR_EVIDENCE_SCHEMA_VERSION {
221            return false;
222        }
223        if !self.match_parity_required || self.backend.is_empty() {
224            return false;
225        }
226        if self.supported {
227            !self.device_signature.is_empty()
228                && self.rule_capacity != 0
229                && self.stream_mode != RegexAcceleratorStreamMode::Unavailable
230                && self.match_schema != RegexAcceleratorMatchSchema::Unavailable
231                && self.unsupported_reason.is_empty()
232        } else {
233            self.device_signature.is_empty()
234                && self.rule_capacity == 0
235                && self.stream_mode == RegexAcceleratorStreamMode::Unavailable
236                && self.match_schema == RegexAcceleratorMatchSchema::Unavailable
237                && !self.unsupported_reason.is_empty()
238        }
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn unsupported_regex_accelerator_fails_closed_with_evidence() {
248        let capability = RegexAcceleratorCapability::unsupported(
249            "wgpu",
250            RegexAcceleratorClass::RxpLike,
251            "backend has no RXP-like regex accelerator",
252        );
253
254        let error = capability
255            .require_supported()
256            .expect_err("Fix: unsupported regex accelerator capability must fail closed");
257        match error {
258            BackendError::UnsupportedFeature { name, backend } => {
259                assert_eq!(name, "regex_accelerator:rxp_like");
260                assert_eq!(backend, "wgpu");
261            }
262            other => panic!("expected UnsupportedFeature, got {other:?}"),
263        }
264        let evidence = capability.evidence(0);
265        assert!(!evidence.supported);
266        assert_eq!(evidence.unsupported_reason, "backend has no RXP-like regex accelerator");
267        assert_eq!(evidence.stream_mode, RegexAcceleratorStreamMode::Unavailable);
268        assert_eq!(evidence.match_schema, RegexAcceleratorMatchSchema::Unavailable);
269        assert!(evidence.match_parity_required);
270        assert!(evidence.is_complete());
271    }
272
273    #[test]
274    fn supported_regex_accelerator_reports_device_signature_and_schema() {
275        let capability = RegexAcceleratorCapability::supported(
276            "rxp",
277            RegexAcceleratorClass::RxpLike,
278            "bluefield-rxp:v1",
279            4096,
280            RegexAcceleratorStreamMode::StatefulStreaming,
281            RegexAcceleratorMatchSchema::StreamPatternIdOffsets,
282        );
283
284        let supported = capability
285            .require_supported()
286            .expect("Fix: supported regex accelerator capability should pass");
287        let evidence = supported.evidence(8192);
288
289        assert!(evidence.supported);
290        assert_eq!(evidence.device_signature, "bluefield-rxp:v1");
291        assert_eq!(evidence.rule_capacity, 4096);
292        assert_eq!(
293            evidence.stream_mode,
294            RegexAcceleratorStreamMode::StatefulStreaming
295        );
296        assert_eq!(
297            evidence.match_schema,
298            RegexAcceleratorMatchSchema::StreamPatternIdOffsets
299        );
300        assert_eq!(evidence.transfer_bytes, 8192);
301        assert!(evidence.match_parity_required);
302        assert!(evidence.is_complete());
303    }
304}