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
}