Skip to main content

blvm_consensus/
optimizations.rs

1//! BLVM Runtime Optimization Passes
2//!
3//! Additional optimization passes for 10-30% performance gains
4//!
5//! This module provides runtime optimization passes:
6//! - Constant folding (pre-computed constants)
7//! - Bounds check optimization (proven bounds)
8//! - Inlining hints (hot function markers)
9//! - Memory layout optimization (cache-friendly structures)
10//!
11//! Reference: Orange Paper Section 13.1 - Performance Considerations
12
13use crate::constants::*;
14
15/// Pre-computed constants for constant folding optimization
16///
17/// These constants are computed at compile time to avoid runtime computation
18/// in hot paths. Reference: BLVM Optimization Pass 2 - Constant Folding
19#[cfg(feature = "production")]
20pub mod precomputed_constants {
21    use super::*;
22
23    /// Pre-computed: 2^64 - 1 (used for wrapping arithmetic checks)
24    pub const U64_MAX: u64 = u64::MAX;
25
26    /// Pre-computed: MAX_MONEY as u64 (for comparisons)
27    pub const MAX_MONEY_U64: u64 = MAX_MONEY as u64;
28
29    /// Pre-computed: Inverse of SATOSHIS_PER_BTC (for BTC conversion)
30    pub const BTC_PER_SATOSHI: f64 = 1.0 / (SATOSHIS_PER_BTC as f64);
31
32    /// Pre-computed: 2^32 - 1 (for 32-bit wrapping checks)
33    pub const U32_MAX: u32 = u32::MAX;
34
35    /// Pre-computed: Number of satoshis in 1 BTC (for readability)
36    pub const ONE_BTC_SATOSHIS: i64 = SATOSHIS_PER_BTC;
37}
38
39/// Memory layout optimization: Cache-friendly hash array
40///
41/// Optimizes hash array access for cache locality.
42/// Uses 32-byte aligned structures for better cache performance.
43///
44/// This structure ensures each hash is aligned to a 32-byte boundary, which:
45/// - Reduces cache line splits
46/// - Improves prefetching behavior
47/// - Better fits modern CPU cache architectures (64-byte cache lines)
48///
49/// Reference: BLVM Optimization Pass 3 - Memory Layout Optimization
50/// Cache-aligned hash for optimized batch operations
51#[repr(align(32))]
52#[derive(Clone)]
53pub struct CacheAlignedHash([u8; 32]);
54
55impl CacheAlignedHash {
56    #[inline]
57    pub fn new(hash: [u8; 32]) -> Self {
58        Self(hash)
59    }
60
61    #[inline]
62    pub fn as_bytes(&self) -> &[u8; 32] {
63        &self.0
64    }
65}
66
67/// Memory prefetching optimization
68///
69/// Provides platform-specific prefetch hints to improve cache performance
70/// for sequential memory accesses. Used before batch UTXO lookups and
71/// other sequential data structure traversals.
72///
73/// Reference: BLVM Optimization Pass 1.3 - Memory Prefetching
74#[cfg(feature = "production")]
75pub mod prefetch {
76    /// Prefetch data for read access
77    ///
78    /// Hints the CPU to prefetch data into cache before it's needed.
79    /// This improves performance for sequential memory access patterns.
80    ///
81    /// # Safety
82    /// The pointer must be valid, but it doesn't need to be dereferenceable
83    /// at the time of the call. The prefetch is a hint and may be ignored.
84    #[cfg(target_arch = "x86_64")]
85    #[inline(always)]
86    pub unsafe fn prefetch_read(ptr: *const i8) {
87        use std::arch::x86_64::{_MM_HINT_T0, _mm_prefetch};
88        unsafe {
89            _mm_prefetch(ptr, _MM_HINT_T0);
90        }
91    }
92
93    #[cfg(target_arch = "aarch64")]
94    #[inline(always)]
95    pub unsafe fn prefetch_read(ptr: *const i8) {
96        // std::arch::aarch64::_prefetch requires the unstable
97        // `stdarch_aarch64_prefetch` feature (issue #117217) and is not yet
98        // available on stable Rust.  Use inline asm instead: `core::arch::asm!`
99        // is stable since 1.59 and emits the identical PRFM instruction.
100        // PRFM PLDL1KEEP = Prefetch for Load, L1, temporal (≡ _prefetch hint T0).
101        core::arch::asm!(
102            "prfm pldl1keep, [{addr}]",
103            addr = in(reg) ptr,
104            options(nostack, readonly, preserves_flags)
105        );
106    }
107
108    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
109    #[inline(always)]
110    pub unsafe fn prefetch_read(_ptr: *const i8) {
111        // No-op for unsupported architectures
112    }
113
114    /// Prefetch a slice of data for sequential access
115    ///
116    /// Prefetches the next cache line(s) of data to improve sequential access.
117    /// Safe wrapper around prefetch_read that works with slices.
118    #[inline(always)]
119    pub fn prefetch_slice<T>(slice: &[T], index: usize) {
120        if index < slice.len() {
121            unsafe {
122                let ptr = slice.as_ptr().add(index) as *const i8;
123                prefetch_read(ptr);
124            }
125        }
126    }
127
128    /// Prefetch multiple elements ahead in a slice
129    ///
130    /// Prefetches elements at `index + offset` to prepare for future access.
131    /// Useful for sequential loops where you know you'll access elements ahead.
132    #[inline(always)]
133    pub fn prefetch_ahead<T>(slice: &[T], index: usize, offset: usize) {
134        let prefetch_index = index.saturating_add(offset);
135        prefetch_slice(slice, prefetch_index);
136    }
137}
138
139/// Inlining hints for hot functions
140///
141/// Functions marked with HOT_INLINE should be aggressively inlined.
142/// These are called in tight loops and benefit from inlining.
143#[macro_export]
144#[cfg(feature = "production")]
145macro_rules! hot_inline {
146    () => {
147        #[inline(always)]
148    };
149}
150
151/// Constant folding: Pre-compute common hash results
152///
153/// Caches common hash pre-images for constant folding.
154#[cfg(feature = "production")]
155pub mod constant_folding {
156    /// Pre-computed: SHA256 of empty string
157    pub const EMPTY_STRING_HASH: [u8; 32] = [
158        0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9,
159        0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52,
160        0xb8, 0x55,
161    ];
162
163    /// Pre-computed: Double SHA256 of empty string
164    pub const EMPTY_STRING_DOUBLE_HASH: [u8; 32] = [
165        0x5d, 0xf6, 0xe0, 0xe2, 0x76, 0x13, 0x59, 0xf3, 0x73, 0x9a, 0x1c, 0x6f, 0x87, 0x40, 0x64,
166        0x0a, 0xf1, 0x2e, 0xc7, 0xc3, 0x72, 0x4a, 0x5c, 0x2c, 0xa5, 0xf3, 0x0f, 0x26, 0x60, 0x87,
167        0x7e, 0x6b,
168    ];
169
170    /// Check if input matches empty string hash (constant folding)
171    #[inline(always)]
172    pub fn is_empty_hash(hash: &[u8; 32]) -> bool {
173        *hash == EMPTY_STRING_HASH
174    }
175
176    /// Check if input matches empty string double hash (constant folding)
177    #[inline(always)]
178    pub fn is_empty_double_hash(hash: &[u8; 32]) -> bool {
179        *hash == EMPTY_STRING_DOUBLE_HASH
180    }
181
182    /// Constant-fold: Check if hash is zero (all zeros)
183    #[inline(always)]
184    pub fn is_zero_hash(hash: &[u8; 32]) -> bool {
185        hash.iter().all(|&b| b == 0)
186    }
187}
188
189/// SIMD Vectorization: Batch hash operations
190///
191/// Provides batch hash processing for parallel hash operations.
192/// Leverages existing SIMD in sha2 crate (asm feature) + Rayon for CPU-core parallelization.
193///
194/// Provides batch functions for:
195/// - SHA256 and double SHA256 (Bitcoin standard)
196/// - RIPEMD160 and HASH160 (OP_HASH160)
197///
198/// Uses chunked processing for better cache locality and parallelizes across CPU cores
199/// when batch size is large enough (≥8 items).
200///
201/// Reference: BLVM Optimization Pass 5 - SIMD Vectorization
202#[cfg(feature = "production")]
203pub mod simd_vectorization {
204    use crate::crypto::OptimizedSha256;
205    use digest::Digest;
206    use ripemd::Ripemd160;
207
208    /// Minimum batch size for parallelization (overhead not worth it for smaller batches).
209    /// batch_sha256 uses OptimizedSha256 (SHA-NI when available) for consistency with batch_double_sha256_aligned.
210    const PARALLEL_THRESHOLD: usize = 8;
211
212    /// Chunk size for cache-friendly processing. Hardware-derived via ibd_tuning.
213    #[inline]
214    fn chunk_size() -> usize {
215        blvm_primitives::ibd_tuning::hash_batch_chunk_size()
216    }
217
218    /// Batch SHA256: Compute SHA256 for multiple independent inputs
219    ///
220    /// # Arguments
221    /// * `inputs` - Slice of byte slices to hash
222    ///
223    /// # Returns
224    /// Vector of 32-byte hashes, one per input (in same order)
225    ///
226    /// # Performance
227    /// - Small batches (< 4 items): Sequential (overhead not worth parallelization)
228    /// - Medium batches (4-7 items): Chunked sequential
229    /// - Large batches (≥8 items): Multi-core parallelization with Rayon
230    ///
231    /// # Optimizations
232    /// - Uses sha2 crate with "asm" feature for optimized assembly
233    /// - For large batches, leverages Rayon for multi-core parallelization
234    /// - AVX2 batch optimization available via `crypto::avx2_batch` module
235    pub fn batch_sha256(inputs: &[&[u8]]) -> Vec<[u8; 32]> {
236        if inputs.is_empty() {
237            return Vec::new();
238        }
239
240        // Small batches: sequential processing. Use OptimizedSha256 (SHA-NI when available).
241        if inputs.len() < 4 {
242            let hasher = OptimizedSha256::new();
243            return inputs.iter().map(|input| hasher.hash(input)).collect();
244        }
245
246        // Medium batches: chunked sequential processing
247        if inputs.len() < PARALLEL_THRESHOLD {
248            let hasher = OptimizedSha256::new();
249            let mut results = Vec::with_capacity(inputs.len());
250            for chunk in inputs.chunks(chunk_size()) {
251                for input in chunk {
252                    results.push(hasher.hash(input));
253                }
254            }
255            return results;
256        }
257
258        // Large batches: Try AVX2 first, then fallback to multi-core parallelization
259        #[cfg(target_arch = "x86_64")]
260        {
261            use crate::crypto::sha256_avx2;
262            if sha256_avx2::is_avx2_available() {
263                // Use AVX2 batch processing for chunks of 8
264                use crate::crypto::avx2_batch;
265                return avx2_batch::batch_sha256_avx2(inputs);
266            }
267        }
268
269        // Fallback: serial chunked processing. The previous `par_chunks` rayon path was
270        // disastrous in IBD: N validation workers × per-block calls × shared rayon pool =
271        // catastrophic oversubscription. SHA-NI single-thread is fast enough; cross-block
272        // parallelism (worker pool) is the only level we want.
273        let hasher = OptimizedSha256::new();
274        let mut results = Vec::with_capacity(inputs.len());
275        for chunk in inputs.chunks(chunk_size()) {
276            for input in chunk {
277                results.push(hasher.hash(input));
278            }
279        }
280        results
281    }
282
283    /// Batch double SHA256: Compute SHA256(SHA256(x)) for multiple inputs
284    ///
285    /// This is Bitcoin's standard hash function used for transaction IDs, block hashes, etc.
286    ///
287    /// # Arguments
288    /// * `inputs` - Slice of byte slices to hash
289    ///
290    /// # Returns
291    /// Vector of 32-byte hashes, one per input (in same order)
292    pub fn batch_double_sha256(inputs: &[&[u8]]) -> Vec<[u8; 32]> {
293        // Use aligned version for better cache performance
294        batch_double_sha256_aligned(inputs)
295            .into_iter()
296            .map(|h| *h.as_bytes())
297            .collect()
298    }
299
300    /// Batch double SHA256 with cache-aligned output
301    ///
302    /// Returns cache-aligned hash structures for better memory performance.
303    /// Uses 32-byte alignment for optimal cache line utilization.
304    ///
305    /// # Arguments
306    /// * `inputs` - Slice of byte slices to hash
307    ///
308    /// # Returns
309    /// Vector of cache-aligned 32-byte hashes, one per input (in same order)
310    pub fn batch_double_sha256_aligned(inputs: &[&[u8]]) -> Vec<super::CacheAlignedHash> {
311        if inputs.is_empty() {
312            return Vec::new();
313        }
314
315        // Small batches: sequential processing (overhead not worth it)
316        // Use OptimizedSha256 (SHA-NI when available) instead of sha2
317        let hasher = OptimizedSha256::new();
318        if inputs.len() < 4 {
319            return inputs
320                .iter()
321                .map(|input| super::CacheAlignedHash::new(hasher.hash256(input)))
322                .collect();
323        }
324
325        // Medium batches: chunked sequential processing
326        if inputs.len() < PARALLEL_THRESHOLD {
327            let mut results = Vec::with_capacity(inputs.len());
328            for chunk in inputs.chunks(chunk_size()) {
329                for input in chunk {
330                    results.push(super::CacheAlignedHash::new(hasher.hash256(input)));
331                }
332            }
333            return results;
334        }
335
336        // Serial chunked processing — see `batch_sha256` for rationale (rayon oversubscribes
337        // the pool when N IBD workers each push hashing batches; SHA-NI keeps the per-worker
338        // path fast on its own thread).
339        let hasher = OptimizedSha256::new();
340        let mut results = Vec::with_capacity(inputs.len());
341        for chunk in inputs.chunks(chunk_size()) {
342            for input in chunk {
343                results.push(super::CacheAlignedHash::new(hasher.hash256(input)));
344            }
345        }
346        results
347    }
348
349    /// Batch RIPEMD160: Compute RIPEMD160 for multiple inputs
350    ///
351    /// # Arguments
352    /// * `inputs` - Slice of byte slices to hash
353    ///
354    /// # Returns
355    /// Vector of 20-byte hashes, one per input (in same order)
356    pub fn batch_ripemd160(inputs: &[&[u8]]) -> Vec<[u8; 20]> {
357        if inputs.is_empty() {
358            return Vec::new();
359        }
360
361        // Small batches: sequential processing
362        if inputs.len() < 4 {
363            return inputs
364                .iter()
365                .map(|input| {
366                    let hash = Ripemd160::digest(input);
367                    let mut result = [0u8; 20];
368                    result.copy_from_slice(&hash);
369                    result
370                })
371                .collect();
372        }
373
374        // Medium batches: chunked sequential processing
375        if inputs.len() < PARALLEL_THRESHOLD {
376            let mut results = Vec::with_capacity(inputs.len());
377            for chunk in inputs.chunks(chunk_size()) {
378                for input in chunk {
379                    let hash = Ripemd160::digest(input);
380                    let mut result = [0u8; 20];
381                    result.copy_from_slice(&hash);
382                    results.push(result);
383                }
384            }
385            return results;
386        }
387
388        // Serial chunked processing — same rationale as `batch_sha256`: cross-block
389        // parallelism is provided by the IBD worker pool; rayon par_chunks here
390        // oversubscribes the global pool when N workers each call this per-block.
391        let mut results = Vec::with_capacity(inputs.len());
392        for chunk in inputs.chunks(chunk_size()) {
393            for input in chunk {
394                let hash = Ripemd160::digest(input);
395                let mut result = [0u8; 20];
396                result.copy_from_slice(&hash);
397                results.push(result);
398            }
399        }
400        results
401    }
402
403    /// Batch HASH160: Compute RIPEMD160(SHA256(x)) for multiple inputs
404    ///
405    /// This is Bitcoin's HASH160 operation (OP_HASH160 in script).
406    ///
407    /// # Arguments
408    /// * `inputs` - Slice of byte slices to hash
409    ///
410    /// # Returns
411    /// Vector of 20-byte hashes, one per input (in same order)
412    pub fn batch_hash160(inputs: &[&[u8]]) -> Vec<[u8; 20]> {
413        if inputs.is_empty() {
414            return Vec::new();
415        }
416
417        // Small batches: sequential processing. Use OptimizedSha256 (SHA-NI) for SHA256 part.
418        if inputs.len() < 4 {
419            let hasher = OptimizedSha256::new();
420            return inputs
421                .iter()
422                .map(|input| {
423                    let sha256_hash: [u8; 32] = hasher.hash(input);
424                    let ripemd160_hash = Ripemd160::digest(sha256_hash);
425                    let mut result = [0u8; 20];
426                    result.copy_from_slice(&ripemd160_hash);
427                    result
428                })
429                .collect();
430        }
431
432        // Medium batches: chunked sequential processing
433        if inputs.len() < PARALLEL_THRESHOLD {
434            let hasher = OptimizedSha256::new();
435            let mut results = Vec::with_capacity(inputs.len());
436            for chunk in inputs.chunks(chunk_size()) {
437                for input in chunk {
438                    let sha256_hash: [u8; 32] = hasher.hash(input);
439                    let ripemd160_hash = Ripemd160::digest(sha256_hash);
440                    let mut result = [0u8; 20];
441                    result.copy_from_slice(&ripemd160_hash);
442                    results.push(result);
443                }
444            }
445            return results;
446        }
447
448        // Serial chunked processing — see `batch_sha256` for rationale.
449        let hasher = OptimizedSha256::new();
450        let mut results = Vec::with_capacity(inputs.len());
451        for chunk in inputs.chunks(chunk_size()) {
452            for input in chunk {
453                let sha256_hash: [u8; 32] = hasher.hash(input);
454                let ripemd160_hash = Ripemd160::digest(sha256_hash);
455                let mut result = [0u8; 20];
456                result.copy_from_slice(&ripemd160_hash);
457                results.push(result);
458            }
459        }
460        results
461    }
462}
463
464#[cfg(feature = "production")]
465pub use constant_folding::*;
466#[cfg(feature = "production")]
467pub use precomputed_constants::*;
468
469/// Proven bounds for runtime optimization
470///
471/// These bounds are proven by formal verification and can be used
472/// for runtime optimizations without additional safety checks.
473///
474/// Proven runtime bounds for BLVM optimizations
475///
476/// These bounds have been formally proven and are used for runtime optimizations.
477/// Unlike proof-time limits (in `_helpers::proof_limits`), these represent actual
478/// Bitcoin limits that have been proven to hold in all cases.
479///
480/// Reference: BLVM Optimization Pass
481#[cfg(feature = "production")]
482pub mod proven_bounds {
483    use crate::constants::{MAX_INPUTS, MAX_OUTPUTS};
484
485    /// Maximum transaction size (proven by formal verification in transaction.rs)
486    pub const MAX_TX_SIZE_PROVEN: usize = 100000; // Bytes
487
488    /// Maximum block size (proven by formal verification in block.rs)
489    pub const MAX_BLOCK_SIZE_PROVEN: usize = 4000000; // Bytes (4MB)
490
491    /// Maximum inputs per transaction (proven by formal verification)
492    /// References actual Bitcoin limit from constants.rs
493    pub const MAX_INPUTS_PROVEN: usize = MAX_INPUTS;
494
495    /// Maximum outputs per transaction (proven by formal verification)
496    /// References actual Bitcoin limit from constants.rs
497    pub const MAX_OUTPUTS_PROVEN: usize = MAX_OUTPUTS;
498
499    /// Maximum transactions per block (proven by formal verification)
500    /// Note: Bitcoin limit is effectively unbounded by consensus rules, but practical limit
501    /// is around 10,000 transactions per block based on block size limits.
502    pub const MAX_TRANSACTIONS_PROVEN: usize = 10000;
503
504    /// Maximum previous headers for difficulty adjustment (proven by formal verification)
505    pub const MAX_PREV_HEADERS_PROVEN: usize = 5;
506}
507
508/// Optimized access using proven bounds
509///
510/// Uses bounds proven by formal verification to optimize runtime access.
511/// This is safe because formal proofs guarantee these bounds hold.
512///
513/// Reference: Formal proofs in transaction.rs, block.rs, mining.rs, pow.rs, etc.
514/// These proofs formally verify that certain bounds always hold, allowing us to
515/// use optimized access patterns without runtime bounds checks.
516#[cfg(feature = "production")]
517pub mod optimized_access {
518    use super::proven_bounds;
519
520    /// Get element with proven bounds check
521    ///
522    /// Uses proven maximum sizes to optimize bounds checking.
523    /// For transactions proven to have <= MAX_INPUTS_PROVEN inputs,
524    /// we can use optimized access patterns.
525    ///
526    /// # Safety
527    /// This function is safe because formal proofs guarantee bounds.
528    /// However, it still returns `Option` to handle cases where:
529    /// - Runtime bounds differ from proof bounds (should not happen in practice)
530    /// - Defensive programming (fail-safe)
531    ///
532    /// # Panics
533    /// Never panics - always returns `None` if out of bounds.
534    ///
535    /// # Examples
536    /// ```rust
537    /// use blvm_consensus::optimizations::optimized_access::get_proven;
538    /// use blvm_consensus::types::Transaction;
539    ///
540    /// # let tx = Transaction { version: 1, inputs: vec![].into(), outputs: vec![].into(), lock_time: 0 };
541    /// # let index = 0;
542    /// if let Some(input) = get_proven(&tx.inputs, index) {
543    ///     // Safe to use
544    /// }
545    /// ```
546    #[inline(always)]
547    pub fn get_proven<T>(slice: &[T], index: usize) -> Option<&T> {
548        // Formal proofs have proven index < MAX_SIZE in various proofs
549        // We can use unsafe access for proven-safe indices
550        // This is safe because formal proofs guarantee bounds
551        if index < slice.len() {
552            unsafe { Some(slice.get_unchecked(index)) }
553        } else {
554            None
555        }
556    }
557
558    /// Pre-allocate buffer using proven maximum size
559    ///
560    /// Uses proven maximum sizes to avoid reallocation.
561    /// For example, transaction buffers can be pre-sized to MAX_TX_SIZE_PROVEN.
562    #[inline(always)]
563    pub fn prealloc_proven<T>(max_size: usize) -> Vec<T> {
564        // Pre-allocate to proven maximum to avoid reallocation
565        Vec::with_capacity(max_size)
566    }
567
568    /// Pre-allocate transaction buffer using proven maximum
569    #[inline(always)]
570    pub fn prealloc_tx_buffer() -> Vec<u8> {
571        prealloc_proven::<u8>(proven_bounds::MAX_TX_SIZE_PROVEN)
572    }
573
574    /// Pre-allocate block buffer using proven maximum
575    #[inline(always)]
576    pub fn prealloc_block_buffer() -> Vec<u8> {
577        prealloc_proven::<u8>(proven_bounds::MAX_BLOCK_SIZE_PROVEN)
578    }
579
580    /// Get element with proven bounds (alias for get_proven for compatibility)
581    #[inline(always)]
582    pub fn get_proven_by_<T>(slice: &[T], index: usize) -> Option<&T> {
583        get_proven(slice, index)
584    }
585}
586
587/// Alias module for _optimized_access (for backward compatibility)
588#[cfg(feature = "production")]
589pub mod _optimized_access {
590    use super::optimized_access;
591
592    /// Get element with proven bounds
593    #[inline(always)]
594    pub fn get_proven_by_<T>(slice: &[T], index: usize) -> Option<&T> {
595        optimized_access::get_proven(slice, index)
596    }
597}
598
599/// Re-export prealloc helpers for convenience
600#[cfg(feature = "production")]
601pub use optimized_access::{prealloc_block_buffer, prealloc_tx_buffer};
602
603/// Reference implementations for equivalence proofs
604///
605/// These are safe versions of optimized functions, used to prove
606/// that optimizations are correct via formal verification.
607#[cfg(feature = "production")]
608pub mod reference_implementations {
609    /// Reference (safe) implementation of get_proven
610    /// This is the version we prove equivalence against
611    #[inline(always)]
612    pub fn get_proven_reference<T>(slice: &[T], index: usize) -> Option<&T> {
613        slice.get(index) // Safe version
614    }
615}
616
617/// Runtime assertions for optimization correctness
618///
619/// These functions provide runtime checks in debug builds to verify
620/// that optimizations match their reference implementations.
621#[cfg(all(
622    feature = "production",
623    any(debug_assertions, feature = "runtime-invariants")
624))]
625pub mod runtime_assertions {
626    use super::optimized_access::get_proven;
627    use super::reference_implementations::get_proven_reference;
628
629    /// Checked version of get_proven with runtime assertions
630    ///
631    /// This function performs runtime checks in debug builds to ensure
632    /// the optimized implementation matches the reference implementation.
633    #[inline(always)]
634    pub fn get_proven_checked<T>(slice: &[T], index: usize) -> Option<&T> {
635        let result_optimized = get_proven(slice, index);
636        let result_reference = get_proven_reference(slice, index);
637
638        // Runtime check: both must agree
639        debug_assert_eq!(
640            result_optimized.is_some(),
641            result_reference.is_some(),
642            "Optimization correctness check failed: optimized and reference disagree on Some/None"
643        );
644
645        if let (Some(opt_val), Some(ref_val)) = (result_optimized, result_reference) {
646            debug_assert_eq!(
647                opt_val as *const T, ref_val as *const T,
648                "Optimization correctness check failed: optimized and reference return different pointers"
649            );
650        }
651
652        result_optimized
653    }
654}