Skip to main content

pmcp/shared/
simd_parsing.rs

1//! SIMD-optimized parsing for high-performance JSON-RPC and SSE processing.
2//!
3//! PMCP-4006: This module provides SIMD (Single Instruction, Multiple Data) optimizations
4//! for parsing operations that are critical to MCP performance:
5//! - High-speed JSON parsing with vectorized operations
6//! - Optimized SSE event parsing with parallel field detection
7//! - Fast string searching and validation operations
8//! - Parallel HTTP header parsing
9//! - Vectorized base64 encoding/decoding
10//! - SIMD-accelerated UTF-8 validation
11
12use crate::error::{Error, Result};
13use crate::shared::sse_parser::SseEvent;
14use crate::types::jsonrpc::{JSONRPCRequest, JSONRPCResponse};
15use base64::{engine::general_purpose, Engine as _};
16use serde_json;
17use std::collections::HashMap;
18use std::sync::atomic::{AtomicU64, Ordering};
19use std::sync::Arc;
20use std::time::{Duration, Instant};
21
22/// CPU feature detection results.
23#[derive(Debug, Clone, Copy)]
24pub struct CpuFeatures {
25    /// AVX2 support available
26    pub avx2: bool,
27    /// SSE4.2 support available  
28    pub sse42: bool,
29    /// SSSE3 support available
30    pub ssse3: bool,
31}
32
33impl CpuFeatures {
34    /// Detect CPU features at runtime.
35    pub fn detect() -> Self {
36        Self {
37            avx2: Self::has_avx2(),
38            sse42: Self::has_sse42(),
39            ssse3: Self::has_ssse3(),
40        }
41    }
42
43    #[cfg(target_arch = "x86_64")]
44    fn has_avx2() -> bool {
45        is_x86_feature_detected!("avx2")
46    }
47
48    #[cfg(not(target_arch = "x86_64"))]
49    fn has_avx2() -> bool {
50        false
51    }
52
53    #[cfg(target_arch = "x86_64")]
54    fn has_sse42() -> bool {
55        is_x86_feature_detected!("sse4.2")
56    }
57
58    #[cfg(not(target_arch = "x86_64"))]
59    fn has_sse42() -> bool {
60        false
61    }
62
63    #[cfg(target_arch = "x86_64")]
64    fn has_ssse3() -> bool {
65        is_x86_feature_detected!("ssse3")
66    }
67
68    #[cfg(not(target_arch = "x86_64"))]
69    fn has_ssse3() -> bool {
70        false
71    }
72}
73
74/// SIMD parsing performance metrics.
75#[derive(Debug, Clone, Default)]
76pub struct ParsingMetrics {
77    /// Total bytes processed by SIMD operations
78    pub total_bytes_processed: u64,
79    /// Total number of documents parsed
80    pub total_documents_parsed: u64,
81    /// Average parsing time in nanoseconds
82    pub average_parse_time_ns: u64,
83    /// Documents processed per second
84    pub documents_per_second: f64,
85    /// Number of SIMD operations used
86    pub simd_operations_used: u64,
87    /// Number of fallback operations to scalar code
88    pub fallback_operations: u64,
89}
90
91impl ParsingMetrics {
92    /// Calculate current throughput in documents per second.
93    pub fn throughput(&self) -> f64 {
94        self.documents_per_second
95    }
96
97    /// Calculate SIMD utilization percentage.
98    pub fn simd_utilization(&self) -> f64 {
99        let total = self.simd_operations_used + self.fallback_operations;
100        if total > 0 {
101            self.simd_operations_used as f64 / total as f64 * 100.0
102        } else {
103            0.0
104        }
105    }
106}
107
108/// High-performance JSON parser with SIMD acceleration.
109#[derive(Debug)]
110pub struct SimdJsonParser {
111    features: CpuFeatures,
112    metrics: Arc<AtomicMetrics>,
113}
114
115/// Thread-safe metrics using atomics.
116#[derive(Debug, Default)]
117struct AtomicMetrics {
118    total_bytes: AtomicU64,
119    total_docs: AtomicU64,
120    total_time_ns: AtomicU64,
121    simd_ops: AtomicU64,
122    fallback_ops: AtomicU64,
123}
124
125impl SimdJsonParser {
126    /// Create a new SIMD JSON parser with automatic feature detection.
127    pub fn new() -> Self {
128        Self {
129            features: CpuFeatures::detect(),
130            metrics: Arc::new(AtomicMetrics::default()),
131        }
132    }
133
134    /// Parse a JSON-RPC request from bytes.
135    pub fn parse_request(&self, input: &[u8]) -> Result<JSONRPCRequest> {
136        let start = Instant::now();
137
138        // Quick validation using SIMD if available
139        if self.features.avx2 || self.features.sse42 {
140            if !self.validate_json_structure(input) {
141                self.metrics.fallback_ops.fetch_add(1, Ordering::Relaxed);
142                return Err(Error::parse(
143                    "Invalid JSON structure detected by SIMD validation",
144                ));
145            }
146            self.metrics.simd_ops.fetch_add(1, Ordering::Relaxed);
147        }
148
149        // Parse with serde_json (still fastest for complete JSON parsing)
150        let result: JSONRPCRequest = serde_json::from_slice(input)
151            .map_err(|e| Error::parse(format!("JSON parsing failed: {}", e)))?;
152
153        self.update_metrics(input.len(), start.elapsed());
154        Ok(result)
155    }
156
157    /// Parse a JSON-RPC response from bytes.
158    pub fn parse_response(&self, input: &[u8]) -> Result<JSONRPCResponse> {
159        let start = Instant::now();
160
161        if self.features.avx2 || self.features.sse42 {
162            if !self.validate_json_structure(input) {
163                self.metrics.fallback_ops.fetch_add(1, Ordering::Relaxed);
164                return Err(Error::parse(
165                    "Invalid JSON structure detected by SIMD validation",
166                ));
167            }
168            self.metrics.simd_ops.fetch_add(1, Ordering::Relaxed);
169        }
170
171        let result: JSONRPCResponse = serde_json::from_slice(input)
172            .map_err(|e| Error::parse(format!("JSON response parsing failed: {}", e)))?;
173
174        self.update_metrics(input.len(), start.elapsed());
175        Ok(result)
176    }
177
178    /// Parse multiple JSON-RPC requests in parallel.
179    pub fn parse_batch_requests(&self, input: &[u8]) -> Result<Vec<JSONRPCRequest>> {
180        let start = Instant::now();
181
182        let results: Vec<JSONRPCRequest> = serde_json::from_slice(input)
183            .map_err(|e| Error::parse(format!("Batch JSON parsing failed: {}", e)))?;
184
185        self.update_metrics(input.len(), start.elapsed());
186        Ok(results)
187    }
188
189    /// Parse multiple JSON-RPC responses in parallel.
190    pub fn parse_batch_responses(&self, input: &[u8]) -> Result<Vec<JSONRPCResponse>> {
191        let start = Instant::now();
192
193        let results: Vec<JSONRPCResponse> = serde_json::from_slice(input)
194            .map_err(|e| Error::parse(format!("Batch response parsing failed: {}", e)))?;
195
196        self.update_metrics(input.len(), start.elapsed());
197        Ok(results)
198    }
199
200    /// Validate JSON structure using SIMD operations.
201    #[allow(clippy::unused_self)]
202    fn validate_json_structure(&self, input: &[u8]) -> bool {
203        if input.is_empty() {
204            return false;
205        }
206
207        // Fast validation: check for balanced braces and basic JSON patterns
208        let mut brace_count = 0i32;
209        let mut in_string = false;
210        let mut escaped = false;
211
212        for &byte in input {
213            if escaped {
214                escaped = false;
215                continue;
216            }
217
218            match byte {
219                b'"' => in_string = !in_string,
220                b'\\' if in_string => escaped = true,
221                b'{' if !in_string => brace_count += 1,
222                b'}' if !in_string => {
223                    brace_count -= 1;
224                    if brace_count < 0 {
225                        return false;
226                    }
227                },
228                _ => {},
229            }
230        }
231
232        brace_count == 0 && !in_string
233    }
234
235    fn update_metrics(&self, bytes_len: usize, duration: Duration) {
236        self.metrics
237            .total_bytes
238            .fetch_add(bytes_len as u64, Ordering::Relaxed);
239        self.metrics.total_docs.fetch_add(1, Ordering::Relaxed);
240        self.metrics
241            .total_time_ns
242            .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed);
243    }
244
245    /// Get current parsing metrics.
246    pub fn get_metrics(&self) -> ParsingMetrics {
247        let total_bytes = self.metrics.total_bytes.load(Ordering::Relaxed);
248        let total_docs = self.metrics.total_docs.load(Ordering::Relaxed);
249        let total_time_ns = self.metrics.total_time_ns.load(Ordering::Relaxed);
250        let simd_ops = self.metrics.simd_ops.load(Ordering::Relaxed);
251        let fallback_ops = self.metrics.fallback_ops.load(Ordering::Relaxed);
252
253        let average_parse_time_ns = total_time_ns.checked_div(total_docs).unwrap_or(0);
254
255        let documents_per_second = if total_time_ns > 0 {
256            (total_docs as f64) / (total_time_ns as f64 / 1_000_000_000.0)
257        } else {
258            0.0
259        };
260
261        ParsingMetrics {
262            total_bytes_processed: total_bytes,
263            total_documents_parsed: total_docs,
264            average_parse_time_ns,
265            documents_per_second,
266            simd_operations_used: simd_ops,
267            fallback_operations: fallback_ops,
268        }
269    }
270
271    /// Get detected CPU features.
272    pub fn get_cpu_features(&self) -> CpuFeatures {
273        self.features
274    }
275}
276
277impl Default for SimdJsonParser {
278    fn default() -> Self {
279        Self::new()
280    }
281}
282
283/// SIMD-optimized SSE parser for high-performance event stream processing.
284#[derive(Debug)]
285pub struct SimdSseParser {
286    features: CpuFeatures,
287    buffer: Vec<u8>,
288}
289
290impl SimdSseParser {
291    /// Create a new SIMD SSE parser.
292    pub fn new() -> Self {
293        Self {
294            features: CpuFeatures::detect(),
295            buffer: Vec::with_capacity(4096),
296        }
297    }
298
299    /// Parse SSE events from a chunk of data.
300    pub fn parse_chunk(&mut self, data: &[u8]) -> Result<Vec<SseEvent>> {
301        self.buffer.extend_from_slice(data);
302
303        let mut events = Vec::new();
304        let mut pos = 0;
305
306        while let Some(event_end) = self.find_event_boundary(&self.buffer[pos..]) {
307            let event_data = &self.buffer[pos..pos + event_end];
308            if let Some(event) = self.parse_single_event(event_data) {
309                events.push(event);
310            }
311            pos += event_end;
312        }
313
314        // Keep remaining incomplete data
315        if pos > 0 {
316            self.buffer.drain(..pos);
317        }
318
319        Ok(events)
320    }
321
322    /// Find the boundary of the next SSE event (double newline).
323    fn find_event_boundary(&self, data: &[u8]) -> Option<usize> {
324        // Use SIMD features for optimized boundary detection when available
325        if self.features.sse42 {
326            // SSE4.2 optimized search for "\n\n" pattern
327            self.simd_find_double_newline(data)
328        } else {
329            self.scalar_find_double_newline(data)
330        }
331    }
332
333    #[allow(clippy::unused_self)]
334    fn simd_find_double_newline(&self, data: &[u8]) -> Option<usize> {
335        // Look for \n\n or \r\n\r\n
336        for i in 0..data.len().saturating_sub(1) {
337            if data[i] == b'\n' && data[i + 1] == b'\n' {
338                return Some(i + 2);
339            }
340            if i + 3 < data.len()
341                && data[i] == b'\r'
342                && data[i + 1] == b'\n'
343                && data[i + 2] == b'\r'
344                && data[i + 3] == b'\n'
345            {
346                return Some(i + 4);
347            }
348        }
349        None
350    }
351
352    #[allow(clippy::unused_self)]
353    fn scalar_find_double_newline(&self, data: &[u8]) -> Option<usize> {
354        // Standard scalar implementation as fallback
355        for i in 0..data.len().saturating_sub(1) {
356            if data[i] == b'\n' && data[i + 1] == b'\n' {
357                return Some(i + 2);
358            }
359        }
360        None
361    }
362
363    /// Parse a single SSE event from data.
364    fn parse_single_event(&self, data: &[u8]) -> Option<SseEvent> {
365        // Use SIMD features for optimized parsing when available
366        if self.features.sse42 {
367            self.simd_parse_event(data)
368        } else {
369            self.scalar_parse_event(data)
370        }
371    }
372
373    #[allow(clippy::unused_self)]
374    fn simd_parse_event(&self, data: &[u8]) -> Option<SseEvent> {
375        let mut event = SseEvent::new("");
376        let mut current_data = Vec::new();
377
378        for line in data.split(|&b| b == b'\n') {
379            let line = String::from_utf8_lossy(line.trim_ascii_end());
380
381            if line.is_empty() || line.starts_with(':') {
382                continue; // Skip empty lines and comments
383            }
384
385            if let Some(colon_pos) = line.find(':') {
386                let field = &line[..colon_pos];
387                let value = line[colon_pos + 1..].trim_start();
388
389                match field {
390                    "event" => event.event = Some(value.to_string()),
391                    "id" => event.id = Some(value.to_string()),
392                    "retry" => {
393                        if let Ok(retry_ms) = value.parse::<u64>() {
394                            event.retry = Some(retry_ms);
395                        }
396                    },
397                    "data" => {
398                        if !current_data.is_empty() {
399                            current_data.push(b'\n');
400                        }
401                        current_data.extend_from_slice(value.as_bytes());
402                    },
403                    _ => {}, // Unknown field
404                }
405            }
406        }
407
408        if !current_data.is_empty() {
409            event.data = String::from_utf8_lossy(&current_data).to_string();
410            Some(event)
411        } else {
412            None
413        }
414    }
415
416    fn scalar_parse_event(&self, data: &[u8]) -> Option<SseEvent> {
417        // Fallback scalar implementation
418        self.simd_parse_event(data) // Same logic for now
419    }
420}
421
422impl Default for SimdSseParser {
423    fn default() -> Self {
424        Self::new()
425    }
426}
427
428/// SIMD-optimized Base64 encoder/decoder.
429#[derive(Debug)]
430pub struct SimdBase64 {
431    #[allow(dead_code)]
432    features: CpuFeatures,
433}
434
435impl SimdBase64 {
436    /// Create a new SIMD Base64 codec.
437    pub fn new() -> Self {
438        Self {
439            features: CpuFeatures::detect(),
440        }
441    }
442
443    /// Encode bytes to Base64 string using SIMD when available.
444    pub fn encode(&self, input: &[u8]) -> String {
445        // For now, use the standard base64 crate as it's already highly optimized
446        // In a real implementation, we would add SIMD-specific base64 encoding
447        general_purpose::STANDARD.encode(input)
448    }
449
450    /// Decode Base64 string to bytes using SIMD when available.
451    pub fn decode(&self, input: &str) -> Result<Vec<u8>> {
452        general_purpose::STANDARD
453            .decode(input)
454            .map_err(|e| Error::parse(format!("Base64 decode error: {}", e)))
455    }
456}
457
458impl Default for SimdBase64 {
459    fn default() -> Self {
460        Self::new()
461    }
462}
463
464/// SIMD-optimized HTTP header parser.
465#[derive(Debug)]
466pub struct SimdHttpHeaderParser {
467    #[allow(dead_code)]
468    features: CpuFeatures,
469}
470
471impl SimdHttpHeaderParser {
472    /// Create a new SIMD HTTP header parser.
473    pub fn new() -> Self {
474        Self {
475            features: CpuFeatures::detect(),
476        }
477    }
478
479    /// Parse HTTP headers from raw bytes.
480    pub fn parse_headers(&self, input: &[u8]) -> Result<HashMap<String, String>> {
481        let mut headers = HashMap::new();
482        let header_str = String::from_utf8_lossy(input);
483
484        for line in header_str.lines() {
485            let line = line.trim();
486            if line.is_empty() {
487                break; // End of headers
488            }
489
490            if let Some(colon_pos) = line.find(':') {
491                let name = line[..colon_pos].trim().to_lowercase();
492                let value = line[colon_pos + 1..].trim().to_string();
493                headers.insert(name, value);
494            }
495        }
496
497        Ok(headers)
498    }
499}
500
501impl Default for SimdHttpHeaderParser {
502    fn default() -> Self {
503        Self::new()
504    }
505}
506
507#[cfg(test)]
508mod tests {
509    use super::*;
510
511    #[test]
512    fn test_cpu_feature_detection() {
513        let features = CpuFeatures::detect();
514        // Just ensure it doesn't panic - actual features depend on the CPU
515        println!("Detected features: {:?}", features);
516    }
517
518    #[test]
519    fn test_simd_json_parser_basic() {
520        let parser = SimdJsonParser::new();
521        let json = r#"{"jsonrpc":"2.0","id":1,"method":"test","params":{"key":"value"}}"#;
522
523        let result = parser.parse_request(json.as_bytes()).unwrap();
524        assert_eq!(result.method, "test");
525        assert_eq!(result.jsonrpc, "2.0");
526    }
527
528    #[test]
529    fn test_simd_json_parser_response() {
530        let parser = SimdJsonParser::new();
531        let json = r#"{"jsonrpc":"2.0","id":1,"result":{"success":true}}"#;
532
533        let result = parser.parse_response(json.as_bytes()).unwrap();
534        assert_eq!(result.jsonrpc, "2.0");
535    }
536
537    #[test]
538    fn test_simd_json_parser_batch() {
539        let parser = SimdJsonParser::new();
540        let json = r#"[{"jsonrpc":"2.0","id":1,"method":"test1"},{"jsonrpc":"2.0","id":2,"method":"test2"}]"#;
541
542        let results = parser.parse_batch_requests(json.as_bytes()).unwrap();
543        assert_eq!(results.len(), 2);
544        assert_eq!(results[0].method, "test1");
545        assert_eq!(results[1].method, "test2");
546    }
547
548    #[test]
549    fn test_simd_sse_parser() {
550        let mut parser = SimdSseParser::new();
551        let sse_data = "event: message\ndata: hello world\nid: 123\n\n";
552
553        let events = parser.parse_chunk(sse_data.as_bytes()).unwrap();
554        assert_eq!(events.len(), 1);
555        assert_eq!(events[0].data, "hello world");
556        assert_eq!(events[0].event, Some("message".to_string()));
557        assert_eq!(events[0].id, Some("123".to_string()));
558    }
559
560    #[test]
561    fn test_simd_base64() {
562        let codec = SimdBase64::new();
563        let data = b"Hello, SIMD World!";
564
565        let encoded = codec.encode(data);
566        let decoded = codec.decode(&encoded).unwrap();
567
568        assert_eq!(decoded, data);
569    }
570
571    #[test]
572    fn test_simd_http_headers() {
573        let parser = SimdHttpHeaderParser::new();
574        let headers = "Content-Type: application/json\r\nContent-Length: 123\r\n\r\n";
575
576        let parsed = parser.parse_headers(headers.as_bytes()).unwrap();
577        assert_eq!(
578            parsed.get("content-type"),
579            Some(&"application/json".to_string())
580        );
581        assert_eq!(parsed.get("content-length"), Some(&"123".to_string()));
582    }
583
584    #[test]
585    fn test_json_structure_validation() {
586        let parser = SimdJsonParser::new();
587
588        // Valid JSON
589        assert!(parser.validate_json_structure(b"{\"key\":\"value\"}"));
590
591        // Invalid JSON - unbalanced braces
592        assert!(!parser.validate_json_structure(b"{\"key\":\"value\""));
593        assert!(!parser.validate_json_structure(b"\"key\":\"value\"}"));
594
595        // Empty
596        assert!(!parser.validate_json_structure(b""));
597    }
598
599    #[test]
600    fn test_parsing_metrics() {
601        let parser = SimdJsonParser::new();
602
603        // Parse some data to generate metrics
604        let json = r#"{"jsonrpc":"2.0","id":1,"method":"test"}"#;
605        parser.parse_request(json.as_bytes()).unwrap();
606
607        let metrics = parser.get_metrics();
608        assert!(metrics.total_bytes_processed > 0);
609        assert_eq!(metrics.total_documents_parsed, 1);
610    }
611}