## Coverage Report

#### Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/trueno/src/backends/scalar.rs

    Line
    Count
    Source
    1
    //! Scalar (non-SIMD) backend implementation
    2
    //!
    3
    //! This is the portable baseline implementation that works on all platforms.
    4
    //! It uses simple loops without any SIMD instructions.
    5
    //!
    6
    //! # Performance
    7
    //!
    8
    //! This backend provides correctness reference but no SIMD acceleration.
    9
    //! Expected to be 8-32x slower than SIMD backends on operations with 1K+ elements.
    10
    11
    use super::VectorBackend;
    12
    13
    /// Scalar backend (portable, no SIMD)
    14
    pub struct ScalarBackend;
    15
    16
    impl VectorBackend for ScalarBackend {
    17
        // SAFETY: This function is safe because:
    18
        // 1. All slice accesses are bounds-checked by Rust iterator/indexing
    19
        // 2. No raw pointer arithmetic is performed
    20
        // 3. Marked unsafe only to match VectorBackend trait interface
    21
    0
        unsafe fn add(a: &[f32], b: &[f32], result: &mut [f32]) {
    22
    0
            for i in 0..a.len() {
    23
    0
                result[i] = a[i] + b[i];
    24
    0
            }
    25
    0
        }
    26
    27
        // SAFETY: This function is safe because:
    28
        // 1. All slice accesses are bounds-checked by Rust iterator/indexing
    29
        // 2. No raw pointer arithmetic is performed
    30
        // 3. Marked unsafe only to match VectorBackend trait interface
    31
    0
        unsafe fn sub(a: &[f32], b: &[f32], result: &mut [f32]) {
    32
    0
            for i in 0..a.len() {
    33
    0
                result[i] = a[i] - b[i];
    34
    0
            }
    35
    0
        }
    36
    37
        // SAFETY: This function is safe because:
    38
        // 1. All slice accesses are bounds-checked by Rust iterator/indexing
    39
        // 2. No raw pointer arithmetic is performed
    40
        // 3. Marked unsafe only to match VectorBackend trait interface
    41
    0
        unsafe fn mul(a: &[f32], b: &[f32], result: &mut [f32]) {
    42
    0
            for i in 0..a.len() {
    43
    0
                result[i] = a[i] * b[i];
    44
    0
            }
    45
    0
        }
    46
    47
        // SAFETY: This function is safe because:
    48
        // 1. All slice accesses are bounds-checked by Rust iterator/indexing
    49
        // 2. No raw pointer arithmetic is performed
    50
        // 3. Marked unsafe only to match VectorBackend trait interface
    51
    0
        unsafe fn div(a: &[f32], b: &[f32], result: &mut [f32]) {
    52
    0
            for i in 0..a.len() {
    53
    0
                result[i] = a[i] / b[i];
    54
    0
            }
    55
    0
        }
    56
    57
        // SAFETY: This function is safe because:
    58
        // 1. All slice accesses are bounds-checked by Rust iterator/indexing
    59
        // 2. No raw pointer arithmetic is performed
    60
        // 3. Marked unsafe only to match VectorBackend trait interface
    61
        //
    62
        // OPTIMIZATION: 4× unrolling with mul_add for better ILP and auto-vectorization.
    63
        // This follows the cuda-tile pattern for improved throughput (spec: cuda-tile-behavior.md).
    64
        // Using f32::mul_add provides FMA semantics where available, improving accuracy.
    65
        #[inline(always)]
    66
    0
        unsafe fn dot(a: &[f32], b: &[f32]) -> f32 {
    67
    0
            let len = a.len();
    68
    0
            let chunks = len / 4;
    69
    70
            // 4 independent accumulators for better ILP (cuda-tile inspired optimization)
    71
    0
            let mut acc0 = 0.0f32;
    72
    0
            let mut acc1 = 0.0f32;
    73
    0
            let mut acc2 = 0.0f32;
    74
    0
            let mut acc3 = 0.0f32;
    75
    76
            // Process 4 elements at a time with independent accumulation chains
    77
    0
            for i in 0..chunks {
    78
    0
                let base = i * 4;
    79
    0
                acc0 = a[base].mul_add(b[base], acc0);
    80
    0
                acc1 = a[base + 1].mul_add(b[base + 1], acc1);
    81
    0
                acc2 = a[base + 2].mul_add(b[base + 2], acc2);
    82
    0
                acc3 = a[base + 3].mul_add(b[base + 3], acc3);
    83
    0
            }
    84
    85
            // Combine all 4 accumulators
    86
    0
            let mut sum = (acc0 + acc1) + (acc2 + acc3);
    87
    88
            // Handle remainder
    89
    0
            for i in (chunks * 4)..len {
    90
    0
                sum = a[i].mul_add(b[i], sum);
    91
    0
            }
    92
    93
    0
            sum
    94
    0
        }
    95
    96
        // SAFETY: This function is safe because:
    97
        // 1. All slice accesses are bounds-checked by Rust iterator
    98
        // 2. No raw pointer arithmetic is performed
    99
        // 3. Marked unsafe only to match VectorBackend trait interface
    100
    0
        unsafe fn sum(a: &[f32]) -> f32 {
    101
    0
            let mut total = 0.0;
    102
    0
            for &val in a {
    103
    0
                total += val;
    104
    0
            }
    105
    0
            total
    106
    0
        }
    107
    108
        // SAFETY: This function is safe because:
    109
        // 1. All slice accesses are bounds-checked by Rust slicing/iteration
    110
        // 2. Caller must ensure slice is non-empty (a[0] access)
    111
        // 3. Marked unsafe only to match VectorBackend trait interface
    112
    0
        unsafe fn max(a: &[f32]) -> f32 {
    113
    0
            let mut maximum = a[0];
    114
    0
            for &val in &a[1..] {
    115
    0
                if val > maximum {
    116
    0
                    maximum = val;
    117
    0
                }
    118
            }
    119
    0
            maximum
    120
    0
        }
    121
    122
        // SAFETY: This function is safe because:
    123
        // 1. All slice accesses are bounds-checked by Rust slicing/iteration
    124
        // 2. Caller must ensure slice is non-empty (a[0] access)
    125
        // 3. Marked unsafe only to match VectorBackend trait interface
    126
    0
        unsafe fn min(a: &[f32]) -> f32 {
    127
    0
            let mut minimum = a[0];
    128
    0
            for &val in &a[1..] {
    129
    0
                if val < minimum {
    130
    0
                    minimum = val;
    131
    0
                }
    132
            }
    133
    0
            minimum
    134
    0
        }
    135
    136
        // SAFETY: This function is safe because:
    137
        // 1. All slice accesses are bounds-checked by Rust iterator
    138
        // 2. Caller must ensure slice is non-empty (a[0] access)
    139
        // 3. Marked unsafe only to match VectorBackend trait interface
    140
    0
        unsafe fn argmax(a: &[f32]) -> usize {
    141
    0
            let mut max_value = a[0];
    142
    0
            let mut max_index = 0;
    143
    0
            for (i, &val) in a.iter().enumerate() {
    144
    0
                if val > max_value {
    145
    0
                    max_value = val;
    146
    0
                    max_index = i;
    147
    0
                }
    148
            }
    149
    0
            max_index
    150
    0
        }
    151
    152
        // SAFETY: This function is safe because:
    153
        // 1. All slice accesses are bounds-checked by Rust iterator
    154
        // 2. Caller must ensure slice is non-empty (a[0] access)
    155
        // 3. Marked unsafe only to match VectorBackend trait interface
    156
    0
        unsafe fn argmin(a: &[f32]) -> usize {
    157
    0
            let mut min_value = a[0];
    158
    0
            let mut min_index = 0;
    159
    0
            for (i, &val) in a.iter().enumerate() {
    160
    0
                if val < min_value {
    161
    0
                    min_value = val;
    162
    0
                    min_index = i;
    163
    0
                }
    164
            }
    165
    0
            min_index
    166
    0
        }
    167
    168
        // SAFETY: This function is safe because:
    169
        // 1. All slice accesses are bounds-checked by Rust iterator
    170
        // 2. Kahan summation uses only safe floating-point arithmetic
    171
        // 3. Marked unsafe only to match VectorBackend trait interface
    172
    0
        unsafe fn sum_kahan(a: &[f32]) -> f32 {
    173
    0
            let mut sum = 0.0;
    174
    0
            let mut c = 0.0; // Compensation for lost low-order bits
    175
    176
    0
            for &value in a {
    177
    0
                let y = value - c; // Subtract the compensation
    178
    0
                let t = sum + y; // Add to sum
    179
    0
                c = (t - sum) - y; // Update compensation
    180
    0
                sum = t; // Update sum
    181
    0
            }
    182
    183
    0
            sum
    184
    0
        }
    185
    186
        // SAFETY: This function is safe because:
    187
        // 1. All slice accesses are bounds-checked by Rust iterator
    188
        // 2. Empty check prevents undefined behavior
    189
        // 3. Marked unsafe only to match VectorBackend trait interface
    190
    0
        unsafe fn norm_l2(a: &[f32]) -> f32 {
    191
    0
            if a.is_empty() {
    192
    0
                return 0.0;
    193
    0
            }
    194
    195
    0
            let mut sum_of_squares = 0.0;
    196
    0
            for &val in a {
    197
    0
                sum_of_squares += val * val;
    198
    0
            }
    199
    0
            sum_of_squares.sqrt()
    200
    0
        }
    201
    202
        // SAFETY: This function is safe because:
    203
        // 1. All slice accesses are bounds-checked by Rust iterator
    204
        // 2. Empty check prevents undefined behavior
    205
        // 3. Marked unsafe only to match VectorBackend trait interface
    206
    0
        unsafe fn norm_l1(a: &[f32]) -> f32 {
    207
    0
            if a.is_empty() {
    208
    0
                return 0.0;
    209
    0
            }
    210
    211
    0
            let mut sum = 0.0;
    212
    0
            for &val in a {
    213
    0
                sum += val.abs();
    214
    0
            }
    215
    0
            sum
    216
    0
        }
    217
    218
        // SAFETY: This function is safe because:
    219
        // 1. All slice accesses are bounds-checked by Rust iterator
    220
        // 2. Empty check prevents undefined behavior
    221
        // 3. Marked unsafe only to match VectorBackend trait interface
    222
    0
        unsafe fn norm_linf(a: &[f32]) -> f32 {
    223
    0
            if a.is_empty() {
    224
    0
                return 0.0;
    225
    0
            }
    226
    227
    0
            let mut max_val = 0.0_f32;
    228
    0
            for &val in a {
    229
    0
                let abs_val = val.abs();
    230
    0
                if abs_val > max_val {
    231
    0
                    max_val = abs_val;
    232
    0
                }
    233
            }
    234
    0
            max_val
    235
    0
        }
    236
    237
        // SAFETY: This function is safe because:
    238
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    239
        // 2. No raw pointer arithmetic is performed
    240
        // 3. Marked unsafe only to match VectorBackend trait interface
    241
    0
        unsafe fn scale(a: &[f32], scalar: f32, result: &mut [f32]) {
    242
    0
            for (i, &val) in a.iter().enumerate() {
    243
    0
                result[i] = val * scalar;
    244
    0
            }
    245
    0
        }
    246
    247
        // SAFETY: This function is safe because:
    248
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    249
        // 2. No raw pointer arithmetic is performed
    250
        // 3. Marked unsafe only to match VectorBackend trait interface
    251
    0
        unsafe fn abs(a: &[f32], result: &mut [f32]) {
    252
    0
            for (i, &val) in a.iter().enumerate() {
    253
    0
                result[i] = val.abs();
    254
    0
            }
    255
    0
        }
    256
    257
        // SAFETY: This function is safe because:
    258
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    259
        // 2. No raw pointer arithmetic is performed
    260
        // 3. Marked unsafe only to match VectorBackend trait interface
    261
    0
        unsafe fn clamp(a: &[f32], min_val: f32, max_val: f32, result: &mut [f32]) {
    262
    0
            for (i, &val) in a.iter().enumerate() {
    263
    0
                result[i] = val.max(min_val).min(max_val);
    264
    0
            }
    265
    0
        }
    266
    267
        // SAFETY: This function is safe because:
    268
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate/zip
    269
        // 2. No raw pointer arithmetic is performed
    270
        // 3. Marked unsafe only to match VectorBackend trait interface
    271
    0
        unsafe fn lerp(a: &[f32], b: &[f32], t: f32, result: &mut [f32]) {
    272
    0
            for (i, (&a_val, &b_val)) in a.iter().zip(b.iter()).enumerate() {
    273
    0
                // result = a + t * (b - a)
    274
    0
                result[i] = a_val + t * (b_val - a_val);
    275
    0
            }
    276
    0
        }
    277
    278
        // SAFETY: This function is safe because:
    279
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate/zip
    280
        // 2. No raw pointer arithmetic is performed
    281
        // 3. Marked unsafe only to match VectorBackend trait interface
    282
    0
        unsafe fn fma(a: &[f32], b: &[f32], c: &[f32], result: &mut [f32]) {
    283
    0
            for (i, ((&a_val, &b_val), &c_val)) in a.iter().zip(b.iter()).zip(c.iter()).enumerate() {
    284
    0
                // result = a * b + c
    285
    0
                result[i] = a_val * b_val + c_val;
    286
    0
            }
    287
    0
        }
    288
    289
        // SAFETY: This function is safe because:
    290
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    291
        // 2. No raw pointer arithmetic is performed
    292
        // 3. Marked unsafe only to match VectorBackend trait interface
    293
    0
        unsafe fn relu(a: &[f32], result: &mut [f32]) {
    294
    0
            for (i, &val) in a.iter().enumerate() {
    295
    0
                result[i] = if val > 0.0 { val } else { 0.0 };
    296
            }
    297
    0
        }
    298
    299
        // SAFETY: This function is safe because:
    300
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    301
        // 2. No raw pointer arithmetic is performed
    302
        // 3. Marked unsafe only to match VectorBackend trait interface
    303
    0
        unsafe fn exp(a: &[f32], result: &mut [f32]) {
    304
    0
            for (i, &val) in a.iter().enumerate() {
    305
    0
                result[i] = val.exp();
    306
    0
            }
    307
    0
        }
    308
    309
        // SAFETY: This function is safe because:
    310
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    311
        // 2. Clamping prevents exp() overflow
    312
        // 3. Marked unsafe only to match VectorBackend trait interface
    313
    0
        unsafe fn sigmoid(a: &[f32], result: &mut [f32]) {
    314
    0
            for (i, &val) in a.iter().enumerate() {
    315
                // Handle extreme values for numerical stability
    316
    0
                result[i] = if val < -50.0 {
    317
    0
                    0.0 // exp(-x) would overflow, but sigmoid approaches 0
    318
    0
                } else if val > 50.0 {
    319
    0
                    1.0 // exp(-x) underflows to 0, sigmoid approaches 1
    320
                } else {
    321
    0
                    1.0 / (1.0 + (-val).exp())
    322
                };
    323
            }
    324
    0
        }
    325
    326
        // SAFETY: This function is safe because:
    327
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    328
        // 2. No raw pointer arithmetic is performed
    329
        // 3. Marked unsafe only to match VectorBackend trait interface
    330
    0
        unsafe fn gelu(a: &[f32], result: &mut [f32]) {
    331
            // GELU approximation: 0.5 * x * (1 + tanh(sqrt(2/π) * (x + 0.044715 * x³)))
    332
            const SQRT_2_OVER_PI: f32 = 0.797_884_6;
    333
            const COEFF: f32 = 0.044715;
    334
    335
    0
            for (i, &x) in a.iter().enumerate() {
    336
    0
                let x3 = x * x * x;
    337
    0
                let inner = SQRT_2_OVER_PI * (x + COEFF * x3);
    338
    0
                result[i] = 0.5 * x * (1.0 + inner.tanh());
    339
    0
            }
    340
    0
        }
    341
    342
        // SAFETY: This function is safe because:
    343
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    344
        // 2. Clamping prevents exp() overflow
    345
        // 3. Marked unsafe only to match VectorBackend trait interface
    346
    0
        unsafe fn swish(a: &[f32], result: &mut [f32]) {
    347
            // Swish: x * sigmoid(x) = x / (1 + exp(-x))
    348
    0
            for (i, &x) in a.iter().enumerate() {
    349
    0
                if x < -50.0 {
    350
    0
                    result[i] = 0.0; // x * 0 = 0
    351
    0
                } else if x > 50.0 {
    352
    0
                    result[i] = x; // x * 1 = x
    353
    0
                } else {
    354
    0
                    let sigmoid = 1.0 / (1.0 + (-x).exp());
    355
    0
                    result[i] = x * sigmoid;
    356
    0
                }
    357
            }
    358
    0
        }
    359
    360
        // SAFETY: This function is safe because:
    361
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    362
        // 2. No raw pointer arithmetic is performed
    363
        // 3. Marked unsafe only to match VectorBackend trait interface
    364
    0
        unsafe fn tanh(a: &[f32], result: &mut [f32]) {
    365
            // tanh(x) = (exp(2x) - 1) / (exp(2x) + 1)
    366
    0
            for (i, &x) in a.iter().enumerate() {
    367
    0
                result[i] = x.tanh();
    368
    0
            }
    369
    0
        }
    370
    371
        // SAFETY: This function is safe because:
    372
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    373
        // 2. No raw pointer arithmetic is performed
    374
        // 3. Marked unsafe only to match VectorBackend trait interface
    375
    0
        unsafe fn sqrt(a: &[f32], result: &mut [f32]) {
    376
    0
            for (i, &val) in a.iter().enumerate() {
    377
    0
                result[i] = val.sqrt();
    378
    0
            }
    379
    0
        }
    380
    381
        // SAFETY: This function is safe because:
    382
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    383
        // 2. No raw pointer arithmetic is performed
    384
        // 3. Marked unsafe only to match VectorBackend trait interface
    385
    0
        unsafe fn recip(a: &[f32], result: &mut [f32]) {
    386
    0
            for (i, &val) in a.iter().enumerate() {
    387
    0
                result[i] = val.recip();
    388
    0
            }
    389
    0
        }
    390
    391
        // SAFETY: This function is safe because:
    392
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    393
        // 2. No raw pointer arithmetic is performed
    394
        // 3. Marked unsafe only to match VectorBackend trait interface
    395
    0
        unsafe fn ln(a: &[f32], result: &mut [f32]) {
    396
    0
            for (i, &val) in a.iter().enumerate() {
    397
    0
                result[i] = val.ln();
    398
    0
            }
    399
    0
        }
    400
    401
        // SAFETY: This function is safe because:
    402
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    403
        // 2. No raw pointer arithmetic is performed
    404
        // 3. Marked unsafe only to match VectorBackend trait interface
    405
    0
        unsafe fn log2(a: &[f32], result: &mut [f32]) {
    406
    0
            for (i, &val) in a.iter().enumerate() {
    407
    0
                result[i] = val.log2();
    408
    0
            }
    409
    0
        }
    410
    411
        // SAFETY: This function is safe because:
    412
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    413
        // 2. No raw pointer arithmetic is performed
    414
        // 3. Marked unsafe only to match VectorBackend trait interface
    415
    0
        unsafe fn log10(a: &[f32], result: &mut [f32]) {
    416
    0
            for (i, &val) in a.iter().enumerate() {
    417
    0
                result[i] = val.log10();
    418
    0
            }
    419
    0
        }
    420
    421
        // SAFETY: This function is safe because:
    422
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    423
        // 2. No raw pointer arithmetic is performed
    424
        // 3. Marked unsafe only to match VectorBackend trait interface
    425
    0
        unsafe fn sin(a: &[f32], result: &mut [f32]) {
    426
    0
            for (i, &val) in a.iter().enumerate() {
    427
    0
                result[i] = val.sin();
    428
    0
            }
    429
    0
        }
    430
    431
        // SAFETY: This function is safe because:
    432
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    433
        // 2. No raw pointer arithmetic is performed
    434
        // 3. Marked unsafe only to match VectorBackend trait interface
    435
    0
        unsafe fn cos(a: &[f32], result: &mut [f32]) {
    436
    0
            for (i, &val) in a.iter().enumerate() {
    437
    0
                result[i] = val.cos();
    438
    0
            }
    439
    0
        }
    440
    441
        // SAFETY: This function is safe because:
    442
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    443
        // 2. No raw pointer arithmetic is performed
    444
        // 3. Marked unsafe only to match VectorBackend trait interface
    445
    0
        unsafe fn tan(a: &[f32], result: &mut [f32]) {
    446
    0
            for (i, &val) in a.iter().enumerate() {
    447
    0
                result[i] = val.tan();
    448
    0
            }
    449
    0
        }
    450
    451
        // SAFETY: This function is safe because:
    452
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    453
        // 2. No raw pointer arithmetic is performed
    454
        // 3. Marked unsafe only to match VectorBackend trait interface
    455
    0
        unsafe fn floor(a: &[f32], result: &mut [f32]) {
    456
    0
            for (i, &val) in a.iter().enumerate() {
    457
    0
                result[i] = val.floor();
    458
    0
            }
    459
    0
        }
    460
    461
        // SAFETY: This function is safe because:
    462
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    463
        // 2. No raw pointer arithmetic is performed
    464
        // 3. Marked unsafe only to match VectorBackend trait interface
    465
    0
        unsafe fn ceil(a: &[f32], result: &mut [f32]) {
    466
    0
            for (i, &val) in a.iter().enumerate() {
    467
    0
                result[i] = val.ceil();
    468
    0
            }
    469
    0
        }
    470
    471
        // SAFETY: This function is safe because:
    472
        // 1. All slice accesses are bounds-checked by Rust iterator/enumerate
    473
        // 2. No raw pointer arithmetic is performed
    474
        // 3. Marked unsafe only to match VectorBackend trait interface
    475
    0
        unsafe fn round(a: &[f32], result: &mut [f32]) {
    476
    0
            for (i, &val) in a.iter().enumerate() {
    477
    0
                result[i] = val.round();
    478
    0
            }
    479
    0
        }
    480
    }
    481
    482
    #[cfg(test)]
    483
    mod tests {
    484
        use super::*;
    485
    486
        #[test]
    487
        fn test_scalar_add() {
    488
            let a = [1.0, 2.0, 3.0, 4.0];
    489
            let b = [5.0, 6.0, 7.0, 8.0];
    490
            let mut result = [0.0; 4];
    491
            // SAFETY: Test code calling backend trait methods marked unsafe
    492
            unsafe {
    493
                ScalarBackend::add(&a, &b, &mut result);
    494
            }
    495
            assert_eq!(result, [6.0, 8.0, 10.0, 12.0]);
    496
        }
    497
    498
        #[test]
    499
        fn test_scalar_mul() {
    500
            let a = [1.0, 2.0, 3.0, 4.0];
    501
            let b = [2.0, 3.0, 4.0, 5.0];
    502
            let mut result = [0.0; 4];
    503
            // SAFETY: Test code calling backend trait methods marked unsafe
    504
            unsafe {
    505
                ScalarBackend::mul(&a, &b, &mut result);
    506
            }
    507
            assert_eq!(result, [2.0, 6.0, 12.0, 20.0]);
    508
        }
    509
    510
        #[test]
    511
        fn test_scalar_dot() {
    512
            let a = [1.0, 2.0, 3.0];
    513
            let b = [4.0, 5.0, 6.0];
    514
            // SAFETY: Test code calling backend trait methods marked unsafe
    515
            let result = unsafe { ScalarBackend::dot(&a, &b) };
    516
            assert_eq!(result, 32.0); // 1*4 + 2*5 + 3*6 = 32
    517
        }
    518
    519
        #[test]
    520
        fn test_scalar_sum() {
    521
            let a = [1.0, 2.0, 3.0, 4.0];
    522
            // SAFETY: Test code calling backend trait methods marked unsafe
    523
            let result = unsafe { ScalarBackend::sum(&a) };
    524
            assert_eq!(result, 10.0);
    525
        }
    526
    527
        #[test]
    528
        fn test_scalar_max() {
    529
            let a = [1.0, 5.0, 3.0, 2.0];
    530
            // SAFETY: Test code calling backend trait methods marked unsafe
    531
            let result = unsafe { ScalarBackend::max(&a) };
    532
            assert_eq!(result, 5.0);
    533
        }
    534
    535
        #[test]
    536
        fn test_scalar_min() {
    537
            let a = [1.0, 5.0, 3.0, 2.0];
    538
            // SAFETY: Test code calling backend trait methods marked unsafe
    539
            let result = unsafe { ScalarBackend::min(&a) };
    540
            assert_eq!(result, 1.0);
    541
        }
    542
    543
        #[test]
    544
        fn test_scalar_sub() {
    545
            let a = [5.0, 6.0, 7.0, 8.0];
    546
            let b = [1.0, 2.0, 3.0, 4.0];
    547
            let mut result = [0.0; 4];
    548
            // SAFETY: Test code calling backend trait methods marked unsafe
    549
            unsafe {
    550
                ScalarBackend::sub(&a, &b, &mut result);
    551
            }
    552
            assert_eq!(result, [4.0, 4.0, 4.0, 4.0]);
    553
        }
    554
    555
        #[test]
    556
        fn test_scalar_div() {
    557
            let a = [10.0, 20.0, 30.0, 40.0];
    558
            let b = [2.0, 4.0, 5.0, 8.0];
    559
            let mut result = [0.0; 4];
    560
            // SAFETY: Test code calling backend trait methods marked unsafe
    561
            unsafe {
    562
                ScalarBackend::div(&a, &b, &mut result);
    563
            }
    564
            assert_eq!(result, [5.0, 5.0, 6.0, 5.0]);
    565
        }
    566
    567
        #[test]
    568
        fn test_scalar_argmax() {
    569
            let a = [1.0, 5.0, 3.0, 2.0];
    570
            // SAFETY: Test code calling backend trait methods marked unsafe
    571
            let result = unsafe { ScalarBackend::argmax(&a) };
    572
            assert_eq!(result, 1); // Index of 5.0
    573
        }
    574
    575
        #[test]
    576
        fn test_scalar_argmin() {
    577
            let a = [5.0, 1.0, 3.0, 2.0];
    578
            // SAFETY: Test code calling backend trait methods marked unsafe
    579
            let result = unsafe { ScalarBackend::argmin(&a) };
    580
            assert_eq!(result, 1); // Index of 1.0
    581
        }
    582
    583
        #[test]
    584
        fn test_scalar_sum_kahan() {
    585
            let a = [1.0, 2.0, 3.0, 4.0];
    586
            // SAFETY: Test code calling backend trait methods marked unsafe
    587
            let result = unsafe { ScalarBackend::sum_kahan(&a) };
    588
            assert_eq!(result, 10.0);
    589
        }
    590
    591
        #[test]
    592
        fn test_scalar_norm_l1() {
    593
            let a = [1.0, -2.0, 3.0, -4.0];
    594
            // SAFETY: Test code calling backend trait methods marked unsafe
    595
            let result = unsafe { ScalarBackend::norm_l1(&a) };
    596
            assert_eq!(result, 10.0); // |1| + |-2| + |3| + |-4| = 10
    597
        }
    598
    599
        #[test]
    600
        fn test_scalar_norm_l2() {
    601
            let a = [3.0, 4.0];
    602
            // SAFETY: Test code calling backend trait methods marked unsafe
    603
            let result = unsafe { ScalarBackend::norm_l2(&a) };
    604
            assert_eq!(result, 5.0); // sqrt(3² + 4²) = 5
    605
        }
    606
    607
        #[test]
    608
        fn test_scalar_scale() {
    609
            let a = [1.0, 2.0, 3.0, 4.0];
    610
            let mut result = [0.0; 4];
    611
            // SAFETY: Test code calling backend trait methods marked unsafe
    612
            unsafe {
    613
                ScalarBackend::scale(&a, 2.0, &mut result);
    614
            }
    615
            assert_eq!(result, [2.0, 4.0, 6.0, 8.0]);
    616
        }
    617
    618
        #[test]
    619
        fn test_scalar_clamp() {
    620
            let a = [1.0, 5.0, 10.0, 15.0];
    621
            let mut result = [0.0; 4];
    622
            // SAFETY: Test code calling backend trait methods marked unsafe
    623
            unsafe {
    624
                ScalarBackend::clamp(&a, 3.0, 12.0, &mut result);
    625
            }
    626
            assert_eq!(result, [3.0, 5.0, 10.0, 12.0]);
    627
        }
    628
    629
        #[test]
    630
        fn test_scalar_lerp() {
    631
            let a = [0.0, 10.0, 20.0];
    632
            let b = [100.0, 110.0, 120.0];
    633
            let mut result = [0.0; 3];
    634
            // SAFETY: Test code calling backend trait methods marked unsafe
    635
            unsafe {
    636
                ScalarBackend::lerp(&a, &b, 0.5, &mut result);
    637
            }
    638
            assert_eq!(result, [50.0, 60.0, 70.0]); // Midpoint between a and b
    639
        }
    640
    641
        #[test]
    642
        fn test_scalar_fma() {
    643
            let a = [1.0, 2.0, 3.0];
    644
            let b = [2.0, 3.0, 4.0];
    645
            let c = [5.0, 6.0, 7.0];
    646
            let mut result = [0.0; 3];
    647
            // SAFETY: Test code calling backend trait methods marked unsafe
    648
            unsafe {
    649
                ScalarBackend::fma(&a, &b, &c, &mut result);
    650
            }
    651
            // FMA: a*b + c
    652
            assert_eq!(result, [7.0, 12.0, 19.0]); // [1*2+5, 2*3+6, 3*4+7]
    653
        }
    654
    655
        #[test]
    656
        fn test_scalar_relu() {
    657
            let a = [-3.0, -1.0, 0.0, 1.0, 3.0];
    658
            let mut result = [0.0; 5];
    659
            // SAFETY: Test code calling backend trait methods marked unsafe
    660
            unsafe {
    661
                ScalarBackend::relu(&a, &mut result);
    662
            }
    663
            assert_eq!(result, [0.0, 0.0, 0.0, 1.0, 3.0]);
    664
        }
    665
    666
        #[test]
    667
        fn test_scalar_sigmoid() {
    668
            let a = [-51.0, -1.0, 0.0, 1.0, 51.0];
    669
            let mut result = [0.0; 5];
    670
            // SAFETY: Test code calling backend trait methods marked unsafe
    671
            unsafe {
    672
                ScalarBackend::sigmoid(&a, &mut result);
    673
            }
    674
            // sigmoid(-51) = 0, sigmoid(0) = 0.5, sigmoid(51) = 1
    675
            assert_eq!(result[0], 0.0); // Clamped to 0 for numerical stability
    676
            assert!((result[1] - 0.2689).abs() < 0.001); // sigmoid(-1)
    677
            assert_eq!(result[2], 0.5); // sigmoid(0)
    678
            assert!((result[3] - 0.7311).abs() < 0.001); // sigmoid(1)
    679
            assert_eq!(result[4], 1.0); // Clamped to 1 for numerical stability
    680
        }
    681
    682
        #[test]
    683
        fn test_scalar_gelu() {
    684
            let a = [-2.0, -1.0, 0.0, 1.0, 2.0];
    685
            let mut result = [0.0; 5];
    686
            // SAFETY: Test code calling backend trait methods marked unsafe
    687
            unsafe {
    688
                ScalarBackend::gelu(&a, &mut result);
    689
            }
    690
            // GELU approximation values
    691
            assert!((result[0] - (-0.0454)).abs() < 0.01); // gelu(-2)
    692
            assert!((result[1] - (-0.1588)).abs() < 0.01); // gelu(-1)
    693
            assert_eq!(result[2], 0.0); // gelu(0) = 0
    694
            assert!((result[3] - 0.8413).abs() < 0.01); // gelu(1)
    695
            assert!((result[4] - 1.9545).abs() < 0.01); // gelu(2)
    696
        }
    697
    698
        #[test]
    699
        fn test_scalar_swish() {
    700
            let a = [-51.0, -1.0, 0.0, 1.0, 51.0];
    701
            let mut result = [0.0; 5];
    702
            // SAFETY: Test code calling backend trait methods marked unsafe
    703
            unsafe {
    704
                ScalarBackend::swish(&a, &mut result);
    705
            }
    706
            // swish(x) = x * sigmoid(x)
    707
            assert_eq!(result[0], 0.0); // x * 0 = 0 (numerical stability)
    708
            assert!((result[1] - (-0.2689)).abs() < 0.001); // -1 * sigmoid(-1)
    709
            assert_eq!(result[2], 0.0); // 0 * sigmoid(0) = 0
    710
            assert!((result[3] - 0.7311).abs() < 0.001); // 1 * sigmoid(1)
    711
            assert_eq!(result[4], 51.0); // x * 1 = x (numerical stability)
    712
        }
    713
    714
        // cuda-tile-behavior.md: Falsification test #81 - Backend equivalence
    715
        #[test]
    716
        fn test_scalar_dot_unrolled_various_sizes() {
    717
            // Test various sizes to exercise all code paths in unrolled implementation:
    718
            // - 0 elements (edge case)
    719
            // - 1-3 elements (remainder only)
    720
            // - 4 elements (exactly one unrolled chunk)
    721
            // - 5-7 elements (one chunk + remainder)
    722
            // - 8 elements (two chunks)
    723
            // - 100 elements (realistic workload)
    724
            // - 1000 elements (larger workload)
    725
            let sizes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 100, 1000];
    726
    727
            for &size in &sizes {
    728
                if size == 0 {
    729
                    continue; // Empty slice edge case
    730
                }
    731
    732
                let a: Vec<f32> = (0..size).map(|i| (i as f32) * 0.1).collect();
    733
                let b: Vec<f32> = (0..size).map(|i| ((size - i) as f32) * 0.1).collect();
    734
    735
                // Calculate expected result using naive implementation
    736
                let expected: f32 = a.iter().zip(&b).map(|(x, y)| x * y).sum();
    737
                // SAFETY: CPU feature verified at runtime, slices bounds-checked
    738
                let result = unsafe { ScalarBackend::dot(&a, &b) };
    739
    740
                // Tolerance accounts for FP reordering from unrolling (different accumulator summing order)
    741
                // Use relative tolerance for larger sums where absolute error grows with magnitude
    742
                let tolerance = (1e-5 * expected.abs()).max(1e-4);
    743
                assert!(
    744
                    (result - expected).abs() < tolerance,
    745
                    "dot mismatch at size {}: got={}, expected={}, tolerance={}",
    746
                    size,
    747
                    result,
    748
                    expected,
    749
                    tolerance
    750
                );
    751
            }
    752
        }
    753
    754
        // cuda-tile-behavior.md: Falsification test #92 - FMA single-rounding accuracy
    755
        #[test]
    756
        fn test_scalar_dot_mul_add_accuracy() {
    757
            // Test that mul_add provides better accuracy than separate mul+add
    758
            // FMA has single rounding vs two roundings for separate operations
    759
            let a = vec![1.0000001_f32; 1000];
    760
            let b = vec![1.0000001_f32; 1000];
    761
    762
            // SAFETY: CPU feature verified at runtime, slices bounds-checked
    763
            let result = unsafe { ScalarBackend::dot(&a, &b) };
    764
    765
            // Expected: 1000 * 1.0000001 * 1.0000001 ≈ 1000.0002
    766
            // With FMA, error should be smaller due to single rounding
    767
            let expected = 1000.0 * 1.0000001_f32 * 1.0000001_f32;
    768
            assert!(
    769
                (result - expected).abs() < 1e-3,
    770
                "FMA accuracy test: got={}, expected={}",
    771
                result,
    772
                expected
    773
            );
    774
        }
    775
    }