-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfearless_simd_20260615.html
More file actions
761 lines (704 loc) · 213 KB
/
Copy pathfearless_simd_20260615.html
File metadata and controls
761 lines (704 loc) · 213 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Code Audit — Interactive</title>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0f172a;color:#e2e8f0;font-size:14px;line-height:1.5}
.app{max-width:1100px;margin:0 auto;padding:1.5rem 1rem}
.top{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;margin-bottom:1rem;flex-wrap:wrap}
h1{font-size:1.5rem;font-weight:700;color:#f1f5f9}
.meta{font-size:.8rem;color:#64748b;margin-top:.2rem}
.top-right{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap}
.project-info{margin-bottom:1rem;padding:.75rem .9rem;background:#1e293b;border:1px solid #334155;border-radius:8px}
.project-info .pi-name{font-size:.95rem;font-weight:700;color:#f1f5f9}
.project-info .pi-desc{font-size:.82rem;color:#cbd5e1;margin-top:.25rem}
.project-info .pi-row{display:flex;flex-wrap:wrap;gap:.4rem .9rem;margin-top:.45rem;font-size:.78rem;color:#94a3b8}
.project-info .pi-row .pi-k{color:#64748b}
.project-info a{color:#60a5fa;text-decoration:none}
.project-info a:hover{text-decoration:underline}
.project-info code{font-family:"JetBrains Mono","Fira Code",Consolas,monospace;font-size:.74rem;color:#93c5fd}
.pi-repo-btn{display:inline-flex;align-items:center;gap:.3rem;padding:.18rem .5rem;background:#0f172a;border:1px solid #3b82f6;border-radius:6px;color:#60a5fa !important;font-weight:600}
.pi-repo-btn:hover{background:#1d283a;text-decoration:none !important}
#search{background:#1e293b;border:1px solid #334155;color:#e2e8f0;padding:.4rem .75rem;border-radius:6px;font-size:.875rem;width:220px;outline:none}
#search:focus{border-color:#3b82f6}
.group-label{font-size:.8rem;color:#94a3b8;display:flex;align-items:center;gap:.4rem}
select{background:#1e293b;border:1px solid #334155;color:#e2e8f0;padding:.35rem .6rem;border-radius:6px;font-size:.8rem;outline:none;cursor:pointer}
select:focus{border-color:#3b82f6}
.filterbar{display:flex;flex-direction:column;gap:.4rem;margin-bottom:.6rem;padding:.6rem .75rem;background:#1e293b;border:1px solid #334155;border-radius:8px}
.filter-row{display:flex;flex-wrap:wrap;gap:.3rem;align-items:center}
.filter-lbl{font-size:.65rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#475569;white-space:nowrap;margin-right:.25rem}
.filter-toggle{font-size:.65rem;font-weight:600;padding:.18rem .5rem;border-radius:4px;border:1px solid #334155;background:#0f172a;color:#94a3b8;cursor:pointer;margin-left:auto;white-space:nowrap;letter-spacing:.04em}
.filter-toggle:hover{background:#1e293b;color:#e2e8f0;border-color:#475569}
.badge{font-size:.68rem;font-weight:700;padding:.18rem .5rem;border-radius:4px;border:1px solid transparent;cursor:pointer;letter-spacing:.03em;transition:opacity .12s,filter .12s;user-select:none}
.badge.off{opacity:.28;filter:grayscale(.5)}
.sev-critical{background:#450a0a;color:#fca5a5;border-color:#991b1b}
.sev-high {background:#431407;color:#fdba74;border-color:#9a3412}
.sev-medium {background:#422006;color:#fcd34d;border-color:#854d0e}
.sev-low {background:#0c1a3a;color:#93c5fd;border-color:#1d4ed8}
.sev-negligible{background:#1e293b;color:#64748b;border-color:#334155}
.type-badge{background:#0c1e33;color:#7dd3fc;border-color:#0369a1}
.easy-badge{background:#052e16;color:#86efac;border-color:#16a34a}
.st-open{background:#1e293b;color:#475569;border-color:#334155}
.st-fixed{background:#052e16;color:#4ade80;border-color:#16a34a}
.st-wont{background:#431407;color:#fdba74;border-color:#9a3412}
.st-fp{background:#2e1065;color:#c4b5fd;border-color:#7c3aed}
#progress-wrap{display:flex;align-items:center;gap:.75rem;margin-bottom:.6rem;flex-wrap:wrap}
#progress-bar{flex:1;min-width:120px;height:7px;background:#1e293b;border-radius:4px;overflow:hidden;border:1px solid #334155}
#progress-fill{height:100%;background:#22c55e;border-radius:4px;transition:width .35s ease;width:0}
#progress-text{font-size:.75rem;color:#64748b;white-space:nowrap}
.action-btn{font-size:.72rem;padding:.25rem .6rem;border-radius:5px;border:1px solid #334155;background:#1e293b;color:#94a3b8;cursor:pointer;white-space:nowrap}
.action-btn:hover{background:#334155;color:#e2e8f0}
#hidden-bar{margin-bottom:.6rem}
.hidden-inner{display:flex;flex-wrap:wrap;gap:.35rem;align-items:center}
.hidden-lbl{font-size:.72rem;color:#475569}
.show-btn{font-size:.7rem;padding:.15rem .45rem;border-radius:4px;background:#1e293b;color:#94a3b8;border:1px solid #334155;cursor:pointer}
.show-btn:hover{background:#334155;color:#e2e8f0}
.show-all-btn{font-size:.7rem;padding:.15rem .45rem;border-radius:4px;background:#2563eb;color:#fff;border:none;cursor:pointer}
.show-all-btn:hover{background:#1d4ed8}
.group-section{margin-bottom:1.5rem}
.group-header{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem;padding-bottom:.35rem;border-bottom:1px solid #334155}
.group-title{font-size:.95rem;font-weight:700;color:#cbd5e1}
.group-title.all-done{color:#4ade80}
.group-count{font-size:.72rem;color:#475569;background:#1e293b;padding:.1rem .4rem;border-radius:10px;white-space:nowrap}
.icon-btn{background:transparent;border:none;color:#334155;cursor:pointer;font-size:.85rem;padding:.05rem .2rem;line-height:1}
.icon-btn:hover{color:#64748b}
.hide-btn{margin-left:auto;font-size:.68rem;padding:.12rem .45rem;border-radius:4px;background:transparent;color:#334155;border:1px solid #1e293b;cursor:pointer}
.hide-btn:hover{background:#1e293b;color:#64748b}
.card{background:#1e293b;border:1px solid #334155;border-radius:8px;margin-bottom:.6rem;overflow:hidden;transition:opacity .15s}
.card.resolved{opacity:.55;border-left:3px solid #16a34a}
.card.resolved:hover{opacity:.85}
.card-header{display:flex;align-items:center;gap:.5rem;padding:.45rem .85rem;background:#162032;border-bottom:1px solid #334155;flex-wrap:wrap;cursor:pointer;user-select:none}
.card-header:hover{background:#1a2840}
.card-arrow{font-size:.65rem;color:#334155;flex-shrink:0}
.card-id{font-size:.75rem;font-weight:700;color:#64748b;font-family:monospace;letter-spacing:.04em}
.card-loc{font-size:.72rem;color:#64748b;font-family:"JetBrains Mono","Fira Code",Consolas,monospace;letter-spacing:.02em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.status-btn{margin-left:auto;font-size:.68rem;font-weight:600;padding:.15rem .5rem;border-radius:4px;border:1px solid transparent;cursor:pointer;white-space:nowrap;transition:filter .1s}
.status-btn:hover{filter:brightness(1.25)}
.copy-btn{font-size:.68rem;font-weight:600;padding:.15rem .5rem;border-radius:4px;border:1px solid #334155;background:#0f172a;color:#94a3b8;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s}
.copy-btn:hover{background:#334155;color:#e2e8f0}
.card-body{padding:.85rem .9rem}
.field-lbl{font-size:.85rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:#7dd3fc;margin:1rem 0 .35rem;padding-bottom:.18rem;border-bottom:1px solid #1e293b}
.field-lbl:first-child{margin-top:0}
.desc,.fix-txt{font-size:.875rem;color:#cbd5e1;line-height:1.55}
.desc p,.fix-txt p{margin:.4rem 0}
.desc p:first-child,.fix-txt p:first-child{margin-top:0}
.desc p:last-child,.fix-txt p:last-child{margin-bottom:0}
.loc-block{margin:.3rem 0 .6rem}
.loc-ref{font-size:.8rem;color:#38bdf8;font-family:"JetBrains Mono","Fira Code",Consolas,monospace;font-weight:600;margin-bottom:.22rem}
.loc-lines{color:#475569;font-weight:400}
pre{background:#0f172a;color:#cdd6f4;border-radius:5px;padding:.6rem .8rem;overflow-x:auto;white-space:pre;font-size:.8rem;line-height:1.55;font-family:"JetBrains Mono","Fira Code",Consolas,monospace;border:1px solid #1e293b;max-height:320px}
.desc pre.md-code,.fix-txt pre.md-code{margin:.5rem 0;font-size:.82rem;color:#e2e8f0;background:#0b1224;border:1px solid #1e293b;max-height:none}
code.md-inline{font-family:"JetBrains Mono","Fira Code",Consolas,monospace;font-size:.86em;background:#0b1224;color:#fca5a5;border:1px solid #1e293b;border-radius:3px;padding:.04rem .32rem;white-space:pre-wrap;word-break:break-word}
.not-found{font-size:.8rem;color:#334155;font-style:italic}
mark{background:#854d0e;color:#fef9c3;border-radius:2px;padding:0 .1rem}
.no-results{text-align:center;color:#334155;padding:3rem 1rem;font-size:.9rem}
</style>
</head>
<body>
<script type="application/json" id="D">[{"id":"PANIC_1","type":"panic","type_label":"Panic","severity":"high","easy":false,"description":"All scalar `shl_*` and `shr_*` fallback implementations for fixed-shift operations\nuse `T::shl(a[n], shift)` / `T::shr(a[n], shift)` where `shift: u32`. In Rust, the\nstandard `Shl<u32>` and `Shr<u32>` trait implementations for primitive integer types\npanic in debug builds when the shift amount is >= the bit width of the type (e.g. >= 8\nfor i8/u8, >= 16 for i16/u16, >= 32 for i32/u32). Hardware SIMD instructions\ntypically clamp or mask the shift count rather than panicking, so this diverges from\nexpected behavior and will crash debug-mode callers that pass a shift count at or above\nthe bit width.\n\nAffected shift-by-scalar functions include: shl_i8x16, shr_i8x16, shl_u8x16,\nshr_u8x16, shl_i16x8, shr_i16x8, shl_u16x8, shr_u16x8, shl_i32x4, shr_i32x4,\nshl_u32x4, shr_u32x4, and all x32/x64 wider variants that delegate to these.","fix":"Use `T::wrapping_shl(a, shift)` / `T::wrapping_shr(a, shift)` which mask the shift amount and never panic, matching hardware behavior.","locations":[{"ref":"fearless_simd/src/generated/fallback.rs:768-788","code":" 768 | fn shl_i8x16(self, a: i8x16<Self>, shift: u32) -> i8x16<Self> {\n 769 | [\n 770 | i8::shl(a[0usize], shift),\n 771 | i8::shl(a[1usize], shift),\n 772 | i8::shl(a[2usize], shift),\n 773 | i8::shl(a[3usize], shift),\n 774 | i8::shl(a[4usize], shift),\n 775 | i8::shl(a[5usize], shift),\n 776 | i8::shl(a[6usize], shift),\n 777 | i8::shl(a[7usize], shift),\n 778 | i8::shl(a[8usize], shift),\n 779 | i8::shl(a[9usize], shift),\n 780 | i8::shl(a[10usize], shift),\n 781 | i8::shl(a[11usize], shift),\n 782 | i8::shl(a[12usize], shift),\n 783 | i8::shl(a[13usize], shift),\n 784 | i8::shl(a[14usize], shift),\n 785 | i8::shl(a[15usize], shift),\n 786 | ]\n 787 | .simd_into(self)\n 788 | }","lines":21},{"ref":"fearless_simd/src/generated/fallback.rs:812-832","code":" 812 | fn shr_i8x16(self, a: i8x16<Self>, shift: u32) -> i8x16<Self> {\n 813 | [\n 814 | i8::shr(a[0usize], shift),\n 815 | i8::shr(a[1usize], shift),\n 816 | i8::shr(a[2usize], shift),\n 817 | i8::shr(a[3usize], shift),\n 818 | i8::shr(a[4usize], shift),\n 819 | i8::shr(a[5usize], shift),\n 820 | i8::shr(a[6usize], shift),\n 821 | i8::shr(a[7usize], shift),\n 822 | i8::shr(a[8usize], shift),\n 823 | i8::shr(a[9usize], shift),\n 824 | i8::shr(a[10usize], shift),\n 825 | i8::shr(a[11usize], shift),\n 826 | i8::shr(a[12usize], shift),\n 827 | i8::shr(a[13usize], shift),\n 828 | i8::shr(a[14usize], shift),\n 829 | i8::shr(a[15usize], shift),\n 830 | ]\n 831 | .simd_into(self)\n 832 | }","lines":21},{"ref":"fearless_simd/src/generated/fallback.rs:1393-1412","code":" 1393 | fn shl_u8x16(self, a: u8x16<Self>, shift: u32) -> u8x16<Self> {\n 1394 | [\n 1395 | u8::shl(a[0usize], shift),\n 1396 | u8::shl(a[1usize], shift),\n 1397 | u8::shl(a[2usize], shift),\n 1398 | u8::shl(a[3usize], shift),\n 1399 | u8::shl(a[4usize], shift),\n 1400 | u8::shl(a[5usize], shift),\n 1401 | u8::shl(a[6usize], shift),\n 1402 | u8::shl(a[7usize], shift),\n 1403 | u8::shl(a[8usize], shift),\n 1404 | u8::shl(a[9usize], shift),\n 1405 | u8::shl(a[10usize], shift),\n 1406 | u8::shl(a[11usize], shift),\n 1407 | u8::shl(a[12usize], shift),\n 1408 | u8::shl(a[13usize], shift),\n 1409 | u8::shl(a[14usize], shift),\n 1410 | u8::shl(a[15usize], shift),\n 1411 | ]\n 1412 | .simd_into(self)","lines":20},{"ref":"fearless_simd/src/generated/fallback.rs:1437-1457","code":" 1437 | fn shr_u8x16(self, a: u8x16<Self>, shift: u32) -> u8x16<Self> {\n 1438 | [\n 1439 | u8::shr(a[0usize], shift),\n 1440 | u8::shr(a[1usize], shift),\n 1441 | u8::shr(a[2usize], shift),\n 1442 | u8::shr(a[3usize], shift),\n 1443 | u8::shr(a[4usize], shift),\n 1444 | u8::shr(a[5usize], shift),\n 1445 | u8::shr(a[6usize], shift),\n 1446 | u8::shr(a[7usize], shift),\n 1447 | u8::shr(a[8usize], shift),\n 1448 | u8::shr(a[9usize], shift),\n 1449 | u8::shr(a[10usize], shift),\n 1450 | u8::shr(a[11usize], shift),\n 1451 | u8::shr(a[12usize], shift),\n 1452 | u8::shr(a[13usize], shift),\n 1453 | u8::shr(a[14usize], shift),\n 1454 | u8::shr(a[15usize], shift),\n 1455 | ]\n 1456 | .simd_into(self)\n 1457 | }","lines":21},{"ref":"fearless_simd/src/generated/fallback.rs:2277-2289","code":" 2277 | fn shl_i16x8(self, a: i16x8<Self>, shift: u32) -> i16x8<Self> {\n 2278 | [\n 2279 | i16::shl(a[0usize], shift),\n 2280 | i16::shl(a[1usize], shift),\n 2281 | i16::shl(a[2usize], shift),\n 2282 | i16::shl(a[3usize], shift),\n 2283 | i16::shl(a[4usize], shift),\n 2284 | i16::shl(a[5usize], shift),\n 2285 | i16::shl(a[6usize], shift),\n 2286 | i16::shl(a[7usize], shift),\n 2287 | ]\n 2288 | .simd_into(self)\n 2289 | }","lines":13},{"ref":"fearless_simd/src/generated/fallback.rs:2305-2321","code":" 2305 | fn shr_i16x8(self, a: i16x8<Self>, shift: u32) -> i16x8<Self> {\n 2306 | [\n 2307 | i16::shr(a[0usize], shift),\n 2308 | i16::shr(a[1usize], shift),\n 2309 | i16::shr(a[2usize], shift),\n 2310 | i16::shr(a[3usize], shift),\n 2311 | i16::shr(a[4usize], shift),\n 2312 | i16::shr(a[5usize], shift),\n 2313 | i16::shr(a[6usize], shift),\n 2314 | i16::shr(a[7usize], shift),\n 2315 | ]\n 2316 | .simd_into(self)\n 2317 | }\n 2318 | #[inline(always)]\n 2319 | fn shrv_i16x8(self, a: i16x8<Self>, b: i16x8<Self>) -> i16x8<Self> {\n 2320 | [\n 2321 | i16::shr(a[0usize], &b[0usize]),","lines":17},{"ref":"fearless_simd/src/generated/fallback.rs:2703-2729","code":" 2703 | fn shl_u16x8(self, a: u16x8<Self>, shift: u32) -> u16x8<Self> {\n 2704 | [\n 2705 | u16::shl(a[0usize], shift),\n 2706 | u16::shl(a[1usize], shift),\n 2707 | u16::shl(a[2usize], shift),\n 2708 | u16::shl(a[3usize], shift),\n 2709 | u16::shl(a[4usize], shift),\n 2710 | u16::shl(a[5usize], shift),\n 2711 | u16::shl(a[6usize], shift),\n 2712 | u16::shl(a[7usize], shift),\n 2713 | ]\n 2714 | .simd_into(self)\n 2715 | }\n 2716 | #[inline(always)]\n 2717 | fn shlv_u16x8(self, a: u16x8<Self>, b: u16x8<Self>) -> u16x8<Self> {\n 2718 | [\n 2719 | u16::shl(a[0usize], &b[0usize]),\n 2720 | u16::shl(a[1usize], &b[1usize]),\n 2721 | u16::shl(a[2usize], &b[2usize]),\n 2722 | u16::shl(a[3usize], &b[3usize]),\n 2723 | u16::shl(a[4usize], &b[4usize]),\n 2724 | u16::shl(a[5usize], &b[5usize]),\n 2725 | u16::shl(a[6usize], &b[6usize]),\n 2726 | u16::shl(a[7usize], &b[7usize]),\n 2727 | ]\n 2728 | .simd_into(self)\n 2729 | }","lines":27},{"ref":"fearless_simd/src/generated/fallback.rs:2731-2759","code":" 2731 | fn shr_u16x8(self, a: u16x8<Self>, shift: u32) -> u16x8<Self> {\n 2732 | [\n 2733 | u16::shr(a[0usize], shift),\n 2734 | u16::shr(a[1usize], shift),\n 2735 | u16::shr(a[2usize], shift),\n 2736 | u16::shr(a[3usize], shift),\n 2737 | u16::shr(a[4usize], shift),\n 2738 | u16::shr(a[5usize], shift),\n 2739 | u16::shr(a[6usize], shift),\n 2740 | u16::shr(a[7usize], shift),\n 2741 | ]\n 2742 | .simd_into(self)\n 2743 | }\n 2744 | #[inline(always)]\n 2745 | fn shrv_u16x8(self, a: u16x8<Self>, b: u16x8<Self>) -> u16x8<Self> {\n 2746 | [\n 2747 | u16::shr(a[0usize], &b[0usize]),\n 2748 | u16::shr(a[1usize], &b[1usize]),\n 2749 | u16::shr(a[2usize], &b[2usize]),\n 2750 | u16::shr(a[3usize], &b[3usize]),\n 2751 | u16::shr(a[4usize], &b[4usize]),\n 2752 | u16::shr(a[5usize], &b[5usize]),\n 2753 | u16::shr(a[6usize], &b[6usize]),\n 2754 | u16::shr(a[7usize], &b[7usize]),\n 2755 | ]\n 2756 | .simd_into(self)\n 2757 | }\n 2758 | #[inline(always)]\n 2759 | fn simd_eq_u16x8(self, a: u16x8<Self>, b: u16x8<Self>) -> mask16x8<Self> {","lines":29},{"ref":"fearless_simd/src/generated/fallback.rs:3294-3302","code":" 3294 | fn shl_i32x4(self, a: i32x4<Self>, shift: u32) -> i32x4<Self> {\n 3295 | [\n 3296 | i32::shl(a[0usize], shift),\n 3297 | i32::shl(a[1usize], shift),\n 3298 | i32::shl(a[2usize], shift),\n 3299 | i32::shl(a[3usize], shift),\n 3300 | ]\n 3301 | .simd_into(self)\n 3302 | }","lines":9},{"ref":"fearless_simd/src/generated/fallback.rs:3314-3322","code":" 3314 | fn shr_i32x4(self, a: i32x4<Self>, shift: u32) -> i32x4<Self> {\n 3315 | [\n 3316 | i32::shr(a[0usize], shift),\n 3317 | i32::shr(a[1usize], shift),\n 3318 | i32::shr(a[2usize], shift),\n 3319 | i32::shr(a[3usize], shift),\n 3320 | ]\n 3321 | .simd_into(self)\n 3322 | }","lines":9},{"ref":"fearless_simd/src/generated/fallback.rs:3622-3630","code":" 3622 | fn shl_u32x4(self, a: u32x4<Self>, shift: u32) -> u32x4<Self> {\n 3623 | [\n 3624 | u32::shl(a[0usize], shift),\n 3625 | u32::shl(a[1usize], shift),\n 3626 | u32::shl(a[2usize], shift),\n 3627 | u32::shl(a[3usize], shift),\n 3628 | ]\n 3629 | .simd_into(self)\n 3630 | }","lines":9},{"ref":"fearless_simd/src/generated/fallback.rs:3642-3650","code":" 3642 | fn shr_u32x4(self, a: u32x4<Self>, shift: u32) -> u32x4<Self> {\n 3643 | [\n 3644 | u32::shr(a[0usize], shift),\n 3645 | u32::shr(a[1usize], shift),\n 3646 | u32::shr(a[2usize], shift),\n 3647 | u32::shr(a[3usize], shift),\n 3648 | ]\n 3649 | .simd_into(self)\n 3650 | }","lines":9}]},{"id":"PANIC_2","type":"panic","type_label":"Panic","severity":"high","easy":false,"description":"shl_i8x16 and shr_i8x16 (and their 32-wide variants shl_i8x32, shr_i8x32, etc.) in the fallback\nand AVX2 scalar-fallback paths (shlv/shrv) call Rust's built-in i8 << u32 operator.\nIn Rust, i8 << shift panics in debug mode if shift >= 8 (the bit width of i8).\nIn release mode this is undefined behavior.\nNEON's vshlq_s8 never panics: shift >= 8 gives 0 (all bits shifted out), shift < 0 gives a right shift.\nThe trait documentation does not specify that callers must restrict shift amounts to < bit_width,\ncreating a hidden contract that is enforced differently across platforms.","fix":"Either:\n1. Document in the Simd trait that shift amounts must be in [0, bit_width) for all vector shift\n methods (making the caller responsible), matching Rust scalar semantics; OR\n2. Clamp shift amounts in fallback paths using .min(bit_width - 1) for the scalar fallback, so\n all platforms behave consistently (shift >= bit_width gives 0 for left shift, sign-filled for\n arithmetic right shift).","locations":[{"ref":"fearless_simd/src/generated/fallback.rs:768-788","code":" 768 | fn shl_i8x16(self, a: i8x16<Self>, shift: u32) -> i8x16<Self> {\n 769 | [\n 770 | i8::shl(a[0usize], shift),\n 771 | i8::shl(a[1usize], shift),\n 772 | i8::shl(a[2usize], shift),\n 773 | i8::shl(a[3usize], shift),\n 774 | i8::shl(a[4usize], shift),\n 775 | i8::shl(a[5usize], shift),\n 776 | i8::shl(a[6usize], shift),\n 777 | i8::shl(a[7usize], shift),\n 778 | i8::shl(a[8usize], shift),\n 779 | i8::shl(a[9usize], shift),\n 780 | i8::shl(a[10usize], shift),\n 781 | i8::shl(a[11usize], shift),\n 782 | i8::shl(a[12usize], shift),\n 783 | i8::shl(a[13usize], shift),\n 784 | i8::shl(a[14usize], shift),\n 785 | i8::shl(a[15usize], shift),\n 786 | ]\n 787 | .simd_into(self)\n 788 | }","lines":21},{"ref":"fearless_simd/src/generated/fallback.rs:812-835","code":" 812 | fn shr_i8x16(self, a: i8x16<Self>, shift: u32) -> i8x16<Self> {\n 813 | [\n 814 | i8::shr(a[0usize], shift),\n 815 | i8::shr(a[1usize], shift),\n 816 | i8::shr(a[2usize], shift),\n 817 | i8::shr(a[3usize], shift),\n 818 | i8::shr(a[4usize], shift),\n 819 | i8::shr(a[5usize], shift),\n 820 | i8::shr(a[6usize], shift),\n 821 | i8::shr(a[7usize], shift),\n 822 | i8::shr(a[8usize], shift),\n 823 | i8::shr(a[9usize], shift),\n 824 | i8::shr(a[10usize], shift),\n 825 | i8::shr(a[11usize], shift),\n 826 | i8::shr(a[12usize], shift),\n 827 | i8::shr(a[13usize], shift),\n 828 | i8::shr(a[14usize], shift),\n 829 | i8::shr(a[15usize], shift),\n 830 | ]\n 831 | .simd_into(self)\n 832 | }\n 833 | #[inline(always)]\n 834 | fn shrv_i8x16(self, a: i8x16<Self>, b: i8x16<Self>) -> i8x16<Self> {\n 835 | [","lines":24},{"ref":"fearless_simd/src/generated/avx2.rs:522-524","code":" 522 | fn shlv_i8x16(self, a: i8x16<Self>, b: i8x16<Self>) -> i8x16<Self> {\n 523 | core::array::from_fn(|i| core::ops::Shl::shl(a[i], b[i])).simd_into(self)\n 524 | }","lines":3},{"ref":"fearless_simd/src/generated/sse4_2.rs:551-553","code":" 551 | fn shlv_i8x16(self, a: i8x16<Self>, b: i8x16<Self>) -> i8x16<Self> {\n 552 | core::array::from_fn(|i| core::ops::Shl::shl(a[i], b[i])).simd_into(self)\n 553 | }","lines":3}]},{"id":"PANIC_3","type":"panic","type_label":"Panic","severity":"high","easy":false,"description":"All scalar `shlv_*` and `shrv_*` fallback implementations (variable vector shifts)\ncall `T::shl(a[n], &b[n])` / `T::shr(a[n], &b[n])` where `b` is a vector of the same\nsigned integer type as `a` (e.g. `shlv_i8x16` shifts `i8` by `&i8`). This invokes\n`Shl<&i8> for i8` which panics in debug builds when the shift amount is negative (any\nnegative i8 value) or >= 8. Since `b` is a general-purpose signed integer vector,\nnegative shift amounts are entirely plausible inputs. In release builds the behavior is\na silent bitwise wrap instead of the expected hardware clamp to zero.\n\nAffected functions: shlv_i8x16, shrv_i8x16, shlv_i16x8, shrv_i16x8, shlv_i32x4,\nshrv_i32x4, shlv_u8x16, shrv_u8x16, shlv_u16x8, shrv_u16x8, shlv_u32x4, shrv_u32x4,\nand all wider variants.","fix":"Use `T::wrapping_shl(a, b as u32)` / `T::wrapping_shr(a, b as u32)` (casting the shift amount to u32), which masks the shift count and avoids panics. For signed shift amounts, if the spec requires zero output for negative shifts, add an explicit branch: `if b < 0 { 0 } else { a.wrapping_shl(b as u32) }`.","locations":[{"ref":"fearless_simd/src/generated/fallback.rs:790-810","code":" 790 | fn shlv_i8x16(self, a: i8x16<Self>, b: i8x16<Self>) -> i8x16<Self> {\n 791 | [\n 792 | i8::shl(a[0usize], &b[0usize]),\n 793 | i8::shl(a[1usize], &b[1usize]),\n 794 | i8::shl(a[2usize], &b[2usize]),\n 795 | i8::shl(a[3usize], &b[3usize]),\n 796 | i8::shl(a[4usize], &b[4usize]),\n 797 | i8::shl(a[5usize], &b[5usize]),\n 798 | i8::shl(a[6usize], &b[6usize]),\n 799 | i8::shl(a[7usize], &b[7usize]),\n 800 | i8::shl(a[8usize], &b[8usize]),\n 801 | i8::shl(a[9usize], &b[9usize]),\n 802 | i8::shl(a[10usize], &b[10usize]),\n 803 | i8::shl(a[11usize], &b[11usize]),\n 804 | i8::shl(a[12usize], &b[12usize]),\n 805 | i8::shl(a[13usize], &b[13usize]),\n 806 | i8::shl(a[14usize], &b[14usize]),\n 807 | i8::shl(a[15usize], &b[15usize]),\n 808 | ]\n 809 | .simd_into(self)\n 810 | }","lines":21},{"ref":"fearless_simd/src/generated/fallback.rs:834-854","code":" 834 | fn shrv_i8x16(self, a: i8x16<Self>, b: i8x16<Self>) -> i8x16<Self> {\n 835 | [\n 836 | i8::shr(a[0usize], &b[0usize]),\n 837 | i8::shr(a[1usize], &b[1usize]),\n 838 | i8::shr(a[2usize], &b[2usize]),\n 839 | i8::shr(a[3usize], &b[3usize]),\n 840 | i8::shr(a[4usize], &b[4usize]),\n 841 | i8::shr(a[5usize], &b[5usize]),\n 842 | i8::shr(a[6usize], &b[6usize]),\n 843 | i8::shr(a[7usize], &b[7usize]),\n 844 | i8::shr(a[8usize], &b[8usize]),\n 845 | i8::shr(a[9usize], &b[9usize]),\n 846 | i8::shr(a[10usize], &b[10usize]),\n 847 | i8::shr(a[11usize], &b[11usize]),\n 848 | i8::shr(a[12usize], &b[12usize]),\n 849 | i8::shr(a[13usize], &b[13usize]),\n 850 | i8::shr(a[14usize], &b[14usize]),\n 851 | i8::shr(a[15usize], &b[15usize]),\n 852 | ]\n 853 | .simd_into(self)\n 854 | }","lines":21},{"ref":"fearless_simd/src/generated/fallback.rs:1414-1435","code":" 1414 | #[inline(always)]\n 1415 | fn shlv_u8x16(self, a: u8x16<Self>, b: u8x16<Self>) -> u8x16<Self> {\n 1416 | [\n 1417 | u8::shl(a[0usize], &b[0usize]),\n 1418 | u8::shl(a[1usize], &b[1usize]),\n 1419 | u8::shl(a[2usize], &b[2usize]),\n 1420 | u8::shl(a[3usize], &b[3usize]),\n 1421 | u8::shl(a[4usize], &b[4usize]),\n 1422 | u8::shl(a[5usize], &b[5usize]),\n 1423 | u8::shl(a[6usize], &b[6usize]),\n 1424 | u8::shl(a[7usize], &b[7usize]),\n 1425 | u8::shl(a[8usize], &b[8usize]),\n 1426 | u8::shl(a[9usize], &b[9usize]),\n 1427 | u8::shl(a[10usize], &b[10usize]),\n 1428 | u8::shl(a[11usize], &b[11usize]),\n 1429 | u8::shl(a[12usize], &b[12usize]),\n 1430 | u8::shl(a[13usize], &b[13usize]),\n 1431 | u8::shl(a[14usize], &b[14usize]),\n 1432 | u8::shl(a[15usize], &b[15usize]),\n 1433 | ]\n 1434 | .simd_into(self)\n 1435 | }","lines":22},{"ref":"fearless_simd/src/generated/fallback.rs:1458-1479","code":" 1458 | #[inline(always)]\n 1459 | fn shrv_u8x16(self, a: u8x16<Self>, b: u8x16<Self>) -> u8x16<Self> {\n 1460 | [\n 1461 | u8::shr(a[0usize], &b[0usize]),\n 1462 | u8::shr(a[1usize], &b[1usize]),\n 1463 | u8::shr(a[2usize], &b[2usize]),\n 1464 | u8::shr(a[3usize], &b[3usize]),\n 1465 | u8::shr(a[4usize], &b[4usize]),\n 1466 | u8::shr(a[5usize], &b[5usize]),\n 1467 | u8::shr(a[6usize], &b[6usize]),\n 1468 | u8::shr(a[7usize], &b[7usize]),\n 1469 | u8::shr(a[8usize], &b[8usize]),\n 1470 | u8::shr(a[9usize], &b[9usize]),\n 1471 | u8::shr(a[10usize], &b[10usize]),\n 1472 | u8::shr(a[11usize], &b[11usize]),\n 1473 | u8::shr(a[12usize], &b[12usize]),\n 1474 | u8::shr(a[13usize], &b[13usize]),\n 1475 | u8::shr(a[14usize], &b[14usize]),\n 1476 | u8::shr(a[15usize], &b[15usize]),\n 1477 | ]\n 1478 | .simd_into(self)\n 1479 | }","lines":22},{"ref":"fearless_simd/src/generated/fallback.rs:2291-2303","code":" 2291 | fn shlv_i16x8(self, a: i16x8<Self>, b: i16x8<Self>) -> i16x8<Self> {\n 2292 | [\n 2293 | i16::shl(a[0usize], &b[0usize]),\n 2294 | i16::shl(a[1usize], &b[1usize]),\n 2295 | i16::shl(a[2usize], &b[2usize]),\n 2296 | i16::shl(a[3usize], &b[3usize]),\n 2297 | i16::shl(a[4usize], &b[4usize]),\n 2298 | i16::shl(a[5usize], &b[5usize]),\n 2299 | i16::shl(a[6usize], &b[6usize]),\n 2300 | i16::shl(a[7usize], &b[7usize]),\n 2301 | ]\n 2302 | .simd_into(self)\n 2303 | }","lines":13},{"ref":"fearless_simd/src/generated/fallback.rs:2317-2335","code":" 2317 | }\n 2318 | #[inline(always)]\n 2319 | fn shrv_i16x8(self, a: i16x8<Self>, b: i16x8<Self>) -> i16x8<Self> {\n 2320 | [\n 2321 | i16::shr(a[0usize], &b[0usize]),\n 2322 | i16::shr(a[1usize], &b[1usize]),\n 2323 | i16::shr(a[2usize], &b[2usize]),\n 2324 | i16::shr(a[3usize], &b[3usize]),\n 2325 | i16::shr(a[4usize], &b[4usize]),\n 2326 | i16::shr(a[5usize], &b[5usize]),\n 2327 | i16::shr(a[6usize], &b[6usize]),\n 2328 | i16::shr(a[7usize], &b[7usize]),\n 2329 | ]\n 2330 | .simd_into(self)\n 2331 | }\n 2332 | #[inline(always)]\n 2333 | fn simd_eq_i16x8(self, a: i16x8<Self>, b: i16x8<Self>) -> mask16x8<Self> {\n 2334 | [\n 2335 | -(i16::eq(&a[0usize], &b[0usize]) as i16),","lines":19},{"ref":"fearless_simd/src/generated/fallback.rs:2717-2729","code":" 2717 | fn shlv_u16x8(self, a: u16x8<Self>, b: u16x8<Self>) -> u16x8<Self> {\n 2718 | [\n 2719 | u16::shl(a[0usize], &b[0usize]),\n 2720 | u16::shl(a[1usize], &b[1usize]),\n 2721 | u16::shl(a[2usize], &b[2usize]),\n 2722 | u16::shl(a[3usize], &b[3usize]),\n 2723 | u16::shl(a[4usize], &b[4usize]),\n 2724 | u16::shl(a[5usize], &b[5usize]),\n 2725 | u16::shl(a[6usize], &b[6usize]),\n 2726 | u16::shl(a[7usize], &b[7usize]),\n 2727 | ]\n 2728 | .simd_into(self)\n 2729 | }","lines":13},{"ref":"fearless_simd/src/generated/fallback.rs:2745-2757","code":" 2745 | fn shrv_u16x8(self, a: u16x8<Self>, b: u16x8<Self>) -> u16x8<Self> {\n 2746 | [\n 2747 | u16::shr(a[0usize], &b[0usize]),\n 2748 | u16::shr(a[1usize], &b[1usize]),\n 2749 | u16::shr(a[2usize], &b[2usize]),\n 2750 | u16::shr(a[3usize], &b[3usize]),\n 2751 | u16::shr(a[4usize], &b[4usize]),\n 2752 | u16::shr(a[5usize], &b[5usize]),\n 2753 | u16::shr(a[6usize], &b[6usize]),\n 2754 | u16::shr(a[7usize], &b[7usize]),\n 2755 | ]\n 2756 | .simd_into(self)\n 2757 | }","lines":13},{"ref":"fearless_simd/src/generated/fallback.rs:3304-3312","code":" 3304 | fn shlv_i32x4(self, a: i32x4<Self>, b: i32x4<Self>) -> i32x4<Self> {\n 3305 | [\n 3306 | i32::shl(a[0usize], &b[0usize]),\n 3307 | i32::shl(a[1usize], &b[1usize]),\n 3308 | i32::shl(a[2usize], &b[2usize]),\n 3309 | i32::shl(a[3usize], &b[3usize]),\n 3310 | ]\n 3311 | .simd_into(self)\n 3312 | }","lines":9},{"ref":"fearless_simd/src/generated/fallback.rs:3326-3334","code":" 3326 | i32::shr(a[0usize], &b[0usize]),\n 3327 | i32::shr(a[1usize], &b[1usize]),\n 3328 | i32::shr(a[2usize], &b[2usize]),\n 3329 | i32::shr(a[3usize], &b[3usize]),\n 3330 | ]\n 3331 | .simd_into(self)\n 3332 | }\n 3333 | #[inline(always)]\n 3334 | fn simd_eq_i32x4(self, a: i32x4<Self>, b: i32x4<Self>) -> mask32x4<Self> {","lines":9},{"ref":"fearless_simd/src/generated/fallback.rs:3632-3640","code":" 3632 | fn shlv_u32x4(self, a: u32x4<Self>, b: u32x4<Self>) -> u32x4<Self> {\n 3633 | [\n 3634 | u32::shl(a[0usize], &b[0usize]),\n 3635 | u32::shl(a[1usize], &b[1usize]),\n 3636 | u32::shl(a[2usize], &b[2usize]),\n 3637 | u32::shl(a[3usize], &b[3usize]),\n 3638 | ]\n 3639 | .simd_into(self)\n 3640 | }","lines":9},{"ref":"fearless_simd/src/generated/fallback.rs:3652-3660","code":" 3652 | fn shrv_u32x4(self, a: u32x4<Self>, b: u32x4<Self>) -> u32x4<Self> {\n 3653 | [\n 3654 | u32::shr(a[0usize], &b[0usize]),\n 3655 | u32::shr(a[1usize], &b[1usize]),\n 3656 | u32::shr(a[2usize], &b[2usize]),\n 3657 | u32::shr(a[3usize], &b[3usize]),\n 3658 | ]\n 3659 | .simd_into(self)\n 3660 | }","lines":9}]},{"id":"PANIC_4","type":"panic","type_label":"Panic","severity":"high","easy":false,"description":"All scalar `neg_*` fallback implementations for signed integer vectors use the\nnon-wrapping `i8::neg` / `i16::neg` / `i32::neg` (i.e. the unary `-` operator via the\n`Neg` trait). In Rust, this panics in debug builds when the input is the minimum\nvalue of the type (i8::MIN = -128, i16::MIN = -32768, i32::MIN). In release builds it\nsilently wraps. SIMD `neg` on hardware always wraps for integer types, so the release\nresult is correct but the debug panic diverges from the intended wrapping semantics and\ncan crash any code that exercises the fallback path with extreme values.","fix":"Replace `i8::neg(x)` / `i16::neg(x)` / `i32::neg(x)` with `i8::wrapping_neg(x)` etc., which always wraps without panicking.","locations":[{"ref":"fearless_simd/src/generated/fallback.rs:1146-1165","code":" 1146 | fn neg_i8x16(self, a: i8x16<Self>) -> i8x16<Self> {\n 1147 | [\n 1148 | i8::neg(a[0usize]),\n 1149 | i8::neg(a[1usize]),\n 1150 | i8::neg(a[2usize]),\n 1151 | i8::neg(a[3usize]),\n 1152 | i8::neg(a[4usize]),\n 1153 | i8::neg(a[5usize]),\n 1154 | i8::neg(a[6usize]),\n 1155 | i8::neg(a[7usize]),\n 1156 | i8::neg(a[8usize]),\n 1157 | i8::neg(a[9usize]),\n 1158 | i8::neg(a[10usize]),\n 1159 | i8::neg(a[11usize]),\n 1160 | i8::neg(a[12usize]),\n 1161 | i8::neg(a[13usize]),\n 1162 | i8::neg(a[14usize]),\n 1163 | i8::neg(a[15usize]),\n 1164 | ]\n 1165 | .simd_into(self)","lines":20},{"ref":"fearless_simd/src/generated/fallback.rs:2520-2532","code":" 2520 | fn neg_i16x8(self, a: i16x8<Self>) -> i16x8<Self> {\n 2521 | [\n 2522 | i16::neg(a[0usize]),\n 2523 | i16::neg(a[1usize]),\n 2524 | i16::neg(a[2usize]),\n 2525 | i16::neg(a[3usize]),\n 2526 | i16::neg(a[4usize]),\n 2527 | i16::neg(a[5usize]),\n 2528 | i16::neg(a[6usize]),\n 2529 | i16::neg(a[7usize]),\n 2530 | ]\n 2531 | .simd_into(self)\n 2532 | }","lines":13},{"ref":"fearless_simd/src/generated/fallback.rs:3461-3469","code":" 3461 | fn neg_i32x4(self, a: i32x4<Self>) -> i32x4<Self> {\n 3462 | [\n 3463 | i32::neg(a[0usize]),\n 3464 | i32::neg(a[1usize]),\n 3465 | i32::neg(a[2usize]),\n 3466 | i32::neg(a[3usize]),\n 3467 | ]\n 3468 | .simd_into(self)\n 3469 | }","lines":9},{"ref":"fearless_simd/src/generated/fallback.rs:4934-4937","code":" 4934 | fn neg_i8x32(self, a: i8x32<Self>) -> i8x32<Self> {\n 4935 | let (a0, a1) = self.split_i8x32(a);\n 4936 | self.combine_i8x16(self.neg_i8x16(a0), self.neg_i8x16(a1))\n 4937 | }","lines":4},{"ref":"fearless_simd/src/generated/fallback.rs:5565-5568","code":" 5565 | fn neg_i16x16(self, a: i16x16<Self>) -> i16x16<Self> {\n 5566 | let (a0, a1) = self.split_i16x16(a);\n 5567 | self.combine_i16x8(self.neg_i16x8(a0), self.neg_i16x8(a1))\n 5568 | }","lines":4},{"ref":"fearless_simd/src/generated/fallback.rs:6218-6221","code":" 6218 | fn neg_i32x8(self, a: i32x8<Self>) -> i32x8<Self> {\n 6219 | let (a0, a1) = self.split_i32x8(a);\n 6220 | self.combine_i32x4(self.neg_i32x4(a0), self.neg_i32x4(a1))\n 6221 | }","lines":4},{"ref":"fearless_simd/src/generated/fallback.rs:7642-7646","code":" 7642 | fn neg_i8x64(self, a: i8x64<Self>) -> i8x64<Self> {\n 7643 | let (a0, a1) = self.split_i8x64(a);\n 7644 | self.combine_i8x32(self.neg_i8x32(a0), self.neg_i8x32(a1))\n 7645 | }\n 7646 | #[inline(always)]","lines":5},{"ref":"fearless_simd/src/generated/fallback.rs:8338-8341","code":" 8338 | fn neg_i16x32(self, a: i16x32<Self>) -> i16x32<Self> {\n 8339 | let (a0, a1) = self.split_i16x32(a);\n 8340 | self.combine_i16x16(self.neg_i16x16(a0), self.neg_i16x16(a1))\n 8341 | }","lines":4},{"ref":"fearless_simd/src/generated/fallback.rs:9016-9019","code":" 9016 | fn neg_i32x16(self, a: i32x16<Self>) -> i32x16<Self> {\n 9017 | let (a0, a1) = self.split_i32x16(a);\n 9018 | self.combine_i32x8(self.neg_i32x8(a0), self.neg_i32x8(a1))\n 9019 | }","lines":4}]},{"id":"PANIC_5","type":"panic","type_label":"Panic","severity":"medium","easy":true,"description":"SimdBase::from_slice and store_slice use slice.try_into().unwrap() to convert a user-supplied\n&[T] into a fixed-size array reference. If the slice length does not exactly match the vector\nwidth, this panics with a generic TryFromSliceError message (\"could not convert slice to array\")\nthat provides no indication of the expected size. All vector types exhibit this pattern.","fix":"Use .unwrap_or_else(|_| panic!(\"from_slice requires a slice of exactly {} elements, got {}\", N, slice.len()))\nto provide a helpful error message, or use expect() with a descriptive message.","locations":[{"ref":"fearless_simd/src/generated/simd_types.rs:104","code":" 104 | simd.load_array_ref_f32x4(slice.try_into().unwrap())","lines":1},{"ref":"fearless_simd/src/generated/simd_types.rs:109","code":" 109 | .store_array_f32x4(*self, slice.try_into().unwrap());","lines":1},{"ref":"fearless_simd/src/generated/simd_types.rs:357","code":" 357 | simd.load_array_ref_i8x16(slice.try_into().unwrap())","lines":1},{"ref":"fearless_simd/src/generated/simd_types.rs:362","code":" 362 | .store_array_i8x16(*self, slice.try_into().unwrap());","lines":1},{"ref":"fearless_simd/src/generated/simd_types.rs:542","code":" 542 | simd.load_array_ref_u8x16(slice.try_into().unwrap())","lines":1},{"ref":"fearless_simd/src/generated/simd_types.rs:547","code":" 547 | .store_array_u8x16(*self, slice.try_into().unwrap());","lines":1}]},{"id":"PANIC_6","type":"panic","type_label":"Panic","severity":"medium","easy":true,"description":"SimdMask::from_slice and store_slice use slice.try_into().unwrap() without a meaningful error\nmessage. Also, the assert in SimdMask::set uses a literal integer in the format string rather\nthan the const N, and the second argument redundantly repeats the hardcoded bound:\n assert!(index < 16, \"mask lane index {index} is out of bounds for {} lanes\", 16);\nThe '16' appears twice; a future code generation change that updates the first but not the second\nwould produce misleading messages.","fix":"Use the const N from the trait in the panic message, or at least keep a single source of truth.\nFor set(): replace the literal 16 argument with a reference to the type's lane count constant.","locations":[{"ref":"fearless_simd/src/generated/simd_types.rs:691-695","code":" 691 | assert!(\n 692 | index < 16,\n 693 | \"mask lane index {index} is out of bounds for {} lanes\",\n 694 | 16\n 695 | );","lines":5},{"ref":"fearless_simd/src/generated/simd_types.rs:702","code":" 702 | let slice: &[i8; 16] = slice.try_into().unwrap();","lines":1},{"ref":"fearless_simd/src/generated/simd_types.rs:707","code":" 707 | let slice: &mut [i8; 16] = slice.try_into().unwrap();","lines":1},{"ref":"fearless_simd/src/generated/simd_types.rs:1159-1163","code":" 1159 | assert!(\n 1160 | index < 8,\n 1161 | \"mask lane index {index} is out of bounds for {} lanes\",\n 1162 | 8\n 1163 | );","lines":5}]},{"id":"PANIC_7","type":"panic","type_label":"Panic","severity":"medium","easy":false,"description":"In `generic_op` for `OpSig::StoreInterleaved`, the code unconditionally panics at generator time with the message 'The generic fallback is not implemented for this operation'. However, `StoreInterleaved` is listed as an operation that `should_use_generic_op` might return `true` for (it is NOT in the exclusion list inside `should_use_generic_op`). If a backend with a native width smaller than the vector width ever calls `generic_op` for `StoreInterleaved`, the generator itself will panic during code generation, rather than producing a useful error or a correct implementation.","fix":"Add `OpSig::StoreInterleaved { .. }` to the exclusion list in `should_use_generic_op` alongside `LoadInterleaved`, or implement the generic fallback.","locations":[{"ref":"fearless_simd_gen/src/generic.rs:229-231","code":" 229 | OpSig::StoreInterleaved { .. } => {\n 230 | panic!(\"The generic fallback is not implemented for this operation\")\n 231 | }","lines":3},{"ref":"fearless_simd_gen/src/ops.rs:1504-1519","code":" 1504 | // These operations need to work on the full vector type.\n 1505 | if matches!(\n 1506 | self,\n 1507 | Self::Split { .. }\n 1508 | | Self::Combine { .. }\n 1509 | | Self::LoadInterleaved { .. }\n 1510 | | Self::StoreInterleaved { .. }\n 1511 | | Self::FromArray { .. }\n 1512 | | Self::AsArray { .. }\n 1513 | | Self::StoreArray\n 1514 | | Self::Slide {\n 1515 | granularity: SlideGranularity::AcrossBlocks,\n 1516 | ..\n 1517 | }\n 1518 | ) {\n 1519 | return false;","lines":16}]},{"id":"PANIC_8","type":"panic","type_label":"Panic","severity":"medium","easy":true,"description":"The error message string literal in File::create contains an unformatted placeholder `{path:?}`. Because it is passed to `.expect()` (not to a format macro), the braces are printed literally rather than being interpolated. The actual path that failed to be created is never shown in the panic message.","fix":"Change the string to a plain string and use a chained `.unwrap_or_else` with a format call, e.g. `File::create(&path).unwrap_or_else(|e| panic!(\"error creating {path:?}: {e}\"))`.","locations":[{"ref":"fearless_simd_gen/src/main.rs:138","code":" 138 | let file = File::create(&path).expect(\"error creating {path:?}\");","lines":1}]},{"id":"PANIC_9","type":"panic","type_label":"Panic","severity":"medium","easy":false,"description":"In `mk_neon.rs` `make_method` for `OpSig::Slide`, the `WithinBlocks` branch for 128-bit vectors panics: `panic!(\"This should have been handled by generic_op\")`. This is only safe if `should_use_generic_op` always returns `true` for `Slide { granularity: WithinBlocks }` when `vec_ty.n_bits() == 128`. Looking at `ops.rs:1522-1529`, `should_use_generic_op` does return `true` for this case, so the panic is unreachable in practice. However, should anyone add a new backend that overrides `should_use_generic_op` differently, this would become reachable. The same pattern exists in `mk_wasm.rs:458` and `mk_x86.rs:1592-1593`.","fix":"Replace the `panic!` with `unreachable!()` to make the unreachability explicit, or add a debug assertion verifying the invariant.","locations":[{"ref":"fearless_simd_gen/src/mk_neon.rs:418-422","code":" 418 | let bytes_expr = match (granularity, vec_ty.n_bits()) {\n 419 | (WithinBlocks, 128) => {\n 420 | panic!(\"This should have been handled by generic_op\");\n 421 | }\n 422 | (WithinBlocks, _) | (_, 128) => {","lines":5},{"ref":"fearless_simd_gen/src/mk_wasm.rs:457-459","code":" 457 | (WithinBlocks, 128) => {\n 458 | panic!(\"This should have been handled by generic_op\");\n 459 | }","lines":3},{"ref":"fearless_simd_gen/src/mk_x86.rs:1592-1593","code":" 1592 | (WithinBlocks, 128, _) => {\n 1593 | panic!(\"This should have been handled by generic_op\");","lines":2}]},{"id":"PANIC_10","type":"panic","type_label":"Panic","severity":"low","easy":false,"description":"dyn_alignr_128 (avx2.rs:8280), dyn_alignr_256 (avx2.rs:8307), dyn_vext_128 (neon.rs:8764),\nand dyn_slide_128 (wasm.rs:8159) each handle shifts 0-15 with explicit match arms and end with\n`_ => unreachable!()`. These are internal helpers called with shift = SHIFT*element_bytes % 16,\nso the value should always be in [0, 15]. However, if a bug in a calling function ever produces\na shift outside this range, the code panics with a confusing \"unreachable\" message rather than\na helpful diagnostic. The current callers all guarantee the precondition, but the functions are\nunsafe and the precondition is not checked.","fix":"Replace `_ => unreachable!()` with `_ => unreachable!(\"shift must be in 0..16, got {}\", shift)`\nto produce a helpful diagnostic if the precondition is ever violated. This is a zero-cost change\nat the callsites and makes future debugging significantly easier.","locations":[{"ref":"fearless_simd/src/generated/avx2.rs:8299","code":" 8299 | _ => unreachable!(),","lines":1},{"ref":"fearless_simd/src/generated/avx2.rs:8326","code":" 8326 | _ => unreachable!(),","lines":1},{"ref":"fearless_simd/src/generated/neon.rs:8783","code":" 8783 | _ => unreachable!(),","lines":1},{"ref":"fearless_simd/src/generated/wasm.rs:8194","code":" 8194 | _ => unreachable!(),","lines":1}]},{"id":"PANIC_11","type":"panic","type_label":"Panic","severity":"low","easy":false,"description":"In `generic.rs` `generic_block_split` and `generic_block_combine`, unhandled dimension combinations call `unimplemented!()`. For example, a 512-bit vector split when `max_block_size == 256` would attempt `(256, 256)` for the half type, which is covered, but a `(512, 256)` case in `split` calls the function with half being 256-bit, so `(256, 256)` matches. However, if a new backend is added with unusual block sizes, or if the existing combinations are extended with new type sizes, new arms may be silently needed. The `unimplemented!()` calls will produce generator panics only when those specific combinations are hit.","fix":"Document which combinations are expected and possible, or add exhaustive pattern matches with better error messages identifying which combination is unhandled.","locations":[{"ref":"fearless_simd_gen/src/generic.rs:323-337","code":" 323 | let expr = match (half_ty.n_bits(), max_block_size) {\n 324 | (256, 128) => quote! {\n 325 | (\n 326 | #half_rust { val: #split_arch_ty([a.val.0[0], a.val.0[1]]), simd: self },\n 327 | #half_rust { val: #split_arch_ty([a.val.0[2], a.val.0[3]]), simd: self },\n 328 | )\n 329 | },\n 330 | (128, 128) | (256, 256) => quote! {\n 331 | (\n 332 | #half_rust { val: #split_arch_ty(a.val.0[0]), simd: self },\n 333 | #half_rust { val: #split_arch_ty(a.val.0[1]), simd: self },\n 334 | )\n 335 | },\n 336 | _ => unimplemented!(),\n 337 | };","lines":15},{"ref":"fearless_simd_gen/src/generic.rs:352-364","code":" 352 | let expr = match (combined_ty.n_bits(), max_block_size) {\n 353 | (512, 128) => quote! {\n 354 | #combined_rust {val: #combined_arch_ty([a.val.0[0], a.val.0[1], b.val.0[0], b.val.0[1]]), simd: self }\n 355 | },\n 356 | (256, 128) | (512, 256) => quote! {\n 357 | #combined_rust {val: #combined_arch_ty([a.val.0, b.val.0]), simd: self }\n 358 | },\n 359 | _ => unimplemented!(),\n 360 | };\n 361 | quote! {\n 362 | #method_sig {\n 363 | #expr\n 364 | }","lines":13}]},{"id":"OVF_1","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","easy":true,"description":"slide_f32x4::<SHIFT> in the fallback implementation performs usize arithmetic without guarding\nagainst SHIFT > 4:\n dest[..4usize - SHIFT].copy_from_slice(&a.val.0[SHIFT..]);\n dest[4usize - SHIFT..].copy_from_slice(&b.val.0[..SHIFT]);\nIf SHIFT > 4, the expression `4usize - SHIFT` wraps around (usize underflow), causing a panic in\ndebug mode and undefined behavior in release mode with overflow checks disabled. The same pattern\nexists in every fallback slide_*::<SHIFT> function for all element types and widths.\nWhile callers are expected to pass SHIFT in [0, N] per the trait semantics, the lack of a runtime\nguard means any caller that violates this silently corrupts memory in release builds.","fix":"Add a compile-time or runtime guard: since SHIFT is a const generic, use a const assertion:\n const { assert!(SHIFT <= 4, \"SHIFT must be in [0, N]\") };\nThis fires at compile time if a monomorphization uses an out-of-range SHIFT, and has zero\nruntime cost.","locations":[{"ref":"fearless_simd/src/generated/fallback.rs:203-208","code":" 203 | fn slide_f32x4<const SHIFT: usize>(self, a: f32x4<Self>, b: f32x4<Self>) -> f32x4<Self> {\n 204 | let mut dest = [Default::default(); 4usize];\n 205 | dest[..4usize - SHIFT].copy_from_slice(&a.val.0[SHIFT..]);\n 206 | dest[4usize - SHIFT..].copy_from_slice(&b.val.0[..SHIFT]);\n 207 | dest.simd_into(self)\n 208 | }","lines":6}]},{"id":"OVF_2","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","easy":false,"description":"NEON shr_* functions compute the negation of a shift value that was cast to a signed integer.\nWhen the shift value equals the maximum representable positive value of that type, the wrapping\ncast produces i8::MIN / i16::MIN / i32::MIN, and negating that minimum value overflows.\nIn Rust debug mode this panics. In release mode the negation wraps (e.g. -(-128i8) = -128i8),\nwhich causes vshlq to right-shift by the element size (128 for i8), a saturated-shift result\nthat coincidentally equals the correct arithmetic right-shift behavior for shifts >= element_size.\nSo release mode gives the right answer by accident, but debug mode panics.\n\nAffected functions and thresholds:\n- shr_i8x16 (line 455): shift = 128 causes -(128u32 as i8) = -(-128i8) overflow\n- shr_u8x16 (line 655): same threshold\n- shr_i16x8 (line 954): shift = 32768\n- shr_u16x8 (line 1154): shift = 32768\n- shr_i32x4 (line 1441): shift = 0x80000000\n- shr_u32x4 (line 1645): shift = 0x80000000","fix":"Use wrapping negation: shift.wrapping_neg() as i8 (or i16/i32 as appropriate), or clamp shift to the valid NEON range [0, element_size-1] before negating.","locations":[{"ref":"fearless_simd/src/generated/neon.rs:455","code":" 455 | unsafe { vshlq_s8(a.into(), vdupq_n_s8(-(shift as i8))).simd_into(self) }","lines":1},{"ref":"fearless_simd/src/generated/neon.rs:655","code":" 655 | unsafe { vshlq_u8(a.into(), vdupq_n_s8(-(shift as i8))).simd_into(self) }","lines":1},{"ref":"fearless_simd/src/generated/neon.rs:954","code":" 954 | unsafe { vshlq_s16(a.into(), vdupq_n_s16(-(shift as i16))).simd_into(self) }","lines":1},{"ref":"fearless_simd/src/generated/neon.rs:1154","code":" 1154 | unsafe { vshlq_u16(a.into(), vdupq_n_s16(-(shift as i16))).simd_into(self) }","lines":1},{"ref":"fearless_simd/src/generated/neon.rs:1441","code":" 1441 | unsafe { vshlq_s32(a.into(), vdupq_n_s32(-shift.cast_signed())).simd_into(self) }","lines":1},{"ref":"fearless_simd/src/generated/neon.rs:1645","code":" 1645 | unsafe { vshlq_u32(a.into(), vdupq_n_s32(-shift.cast_signed())).simd_into(self) }","lines":1}]},{"id":"LOGIC_1","type":"logic_error","type_label":"Logic Error","severity":"high","easy":false,"description":"NEON fract_f32x4 uses integer truncation via vcvtq_s32_f32/vcvtq_f32_s32 instead of the\nhardware vrndq_f32 intrinsic. This gives wrong results for values whose magnitude is >=\n2^31 (i32::MAX+1 ~= 2.147e9), because the float-to-int conversion saturates at i32::MIN\nor i32::MAX. For example, fract_f32x4(2.5e9f) computes trunc via i32 saturate giving\n2147483648.0, so the fract result is 2.5e9 - 2147483648.0 = 352516352.0 instead of 0.0.\nNaN input also produces wrong output (0.0 instead of NaN).\n\nAll other backends (SSE4.2, AVX2, WASM) correctly implement fract as a - trunc(a) using\nproper hardware round-to-zero instructions.","fix":"Replace vcvtq_s32_f32/vcvtq_f32_s32 with vrndq_f32 (rounds toward zero), then subtract: vsubq_f32(a, vrndq_f32(a)).","locations":[{"ref":"fearless_simd/src/generated/neon.rs:290-296","code":" 290 | fn fract_f32x4(self, a: f32x4<Self>) -> f32x4<Self> {\n 291 | unsafe {\n 292 | let c1 = vcvtq_s32_f32(a.into());\n 293 | let c2 = vcvtq_f32_s32(c1);\n 294 | vsubq_f32(a.into(), c2).simd_into(self)\n 295 | }\n 296 | }","lines":7}]},{"id":"LOGIC_2","type":"logic_error","type_label":"Logic Error","severity":"high","easy":false,"description":"NEON fract_f64x2 uses integer truncation via vcvtq_s64_f64/vcvtq_f64_s64 instead of the\nhardware vrndq_f64 intrinsic. This gives wrong results for values whose magnitude is >=\n2^63 (i64::MAX+1 ~= 9.22e18) because the float-to-i64 conversion saturates, and also\nfor NaN inputs (which should return NaN but instead produce 0.0).\n\nAll other backends (SSE4.2, AVX2, WASM) implement fract as a - trunc(a) using hardware\nround-to-zero instructions.","fix":"Replace vcvtq_s64_f64/vcvtq_f64_s64 with vrndq_f64, then subtract: vsubq_f64(a, vrndq_f64(a)).","locations":[{"ref":"fearless_simd/src/generated/neon.rs:2020-2026","code":" 2020 | fn fract_f64x2(self, a: f64x2<Self>) -> f64x2<Self> {\n 2021 | unsafe {\n 2022 | let c1 = vcvtq_s64_f64(a.into());\n 2023 | let c2 = vcvtq_f64_s64(c1);\n 2024 | vsubq_f64(a.into(), c2).simd_into(self)\n 2025 | }\n 2026 | }","lines":7}]},{"id":"LOGIC_3","type":"logic_error","type_label":"Logic Error","severity":"high","easy":false,"description":"shl_i8x16 and shl_i8x32 in SSE4.2 and AVX2 use saturating pack (_mm_packs_epi16 /\n_mm256_packs_epi16) to truncate 16-bit shifted values back to 8-bit. The trait contract\nsays 'bits shifted out of the left side are discarded', meaning wrapping behavior.\n_mm_packs_epi16 saturates to the i8 range instead of truncating: shl_i8x16(127i8, 1)\nreturns 127i8 (saturated) instead of -2i8 (wrapping). NEON vshlq_s8 wraps correctly,\nand WASM i8x16_shl also wraps correctly.\n\nNote: shr_i8x16 / shr_i8x32 use _mm_sra_epi16 (arithmetic right shift), so after shifting\nthe i8-ranged i16 values can never exceed [-128, 127] and the saturating pack is harmless.\nOnly the left-shift variants are wrong.","fix":"AND each shifted i16 lane with 0x00FF before packing to discard the high byte, then use _mm_packus_epi16 (which saturates to 0-255, but the masked values are already in 0-255). Reinterpret the result as i8x16.","locations":[{"ref":"fearless_simd/src/generated/sse4_2.rs:539-548","code":" 539 | fn shl_i8x16(self, a: i8x16<Self>, shift: u32) -> i8x16<Self> {\n 540 | unsafe {\n 541 | let val = a.into();\n 542 | let shift_count = _mm_cvtsi32_si128(shift.cast_signed());\n 543 | let lo_16 = _mm_unpacklo_epi8(val, _mm_cmpgt_epi8(_mm_setzero_si128(), val));\n 544 | let hi_16 = _mm_unpackhi_epi8(val, _mm_cmpgt_epi8(_mm_setzero_si128(), val));\n 545 | let lo_shifted = _mm_sll_epi16(lo_16, shift_count);\n 546 | let hi_shifted = _mm_sll_epi16(hi_16, shift_count);\n 547 | _mm_packs_epi16(lo_shifted, hi_shifted).simd_into(self)\n 548 | }","lines":10},{"ref":"fearless_simd/src/generated/avx2.rs:510-519","code":" 510 | fn shl_i8x16(self, a: i8x16<Self>, shift: u32) -> i8x16<Self> {\n 511 | unsafe {\n 512 | let val = a.into();\n 513 | let shift_count = _mm_cvtsi32_si128(shift.cast_signed());\n 514 | let lo_16 = _mm_unpacklo_epi8(val, _mm_cmpgt_epi8(_mm_setzero_si128(), val));\n 515 | let hi_16 = _mm_unpackhi_epi8(val, _mm_cmpgt_epi8(_mm_setzero_si128(), val));\n 516 | let lo_shifted = _mm_sll_epi16(lo_16, shift_count);\n 517 | let hi_shifted = _mm_sll_epi16(hi_16, shift_count);\n 518 | _mm_packs_epi16(lo_shifted, hi_shifted).simd_into(self)\n 519 | }","lines":10},{"ref":"fearless_simd/src/generated/avx2.rs:2727-2736","code":" 2727 | fn shl_i8x32(self, a: i8x32<Self>, shift: u32) -> i8x32<Self> {\n 2728 | unsafe {\n 2729 | let val = a.into();\n 2730 | let shift_count = _mm_cvtsi32_si128(shift.cast_signed());\n 2731 | let lo_16 = _mm256_unpacklo_epi8(val, _mm256_cmpgt_epi8(_mm256_setzero_si256(), val));\n 2732 | let hi_16 = _mm256_unpackhi_epi8(val, _mm256_cmpgt_epi8(_mm256_setzero_si256(), val));\n 2733 | let lo_shifted = _mm256_sll_epi16(lo_16, shift_count);\n 2734 | let hi_shifted = _mm256_sll_epi16(hi_16, shift_count);\n 2735 | _mm256_packs_epi16(lo_shifted, hi_shifted).simd_into(self)\n 2736 | }","lines":10}]},{"id":"LOGIC_4","type":"logic_error","type_label":"Logic Error","severity":"high","easy":false,"description":"shl_i8x16 and shl_u8x16 on x86 (SSE4.2 and AVX2) produce wrong results when the shifted value\noverflows the element type. The implementation widens i8/u8 to i16, shifts the 16-bit lanes, then\npacks back with _mm_packs_epi16 / _mm_packus_epi16, both of which SATURATE rather than wrap.\nExamples of wrong results:\n- shl_u8x16(128, 1) -> 255 (saturated) instead of 0 (wrapping: 256 & 0xFF = 0).\n- shl_i8x16(1, 7) -> 127 (saturated to i8::MAX) instead of -128 (wrapping: 0x80 as i8).\n- shl_i8x16(-65, 1) -> -128 (saturated to i8::MIN) instead of 126 (wrapping: 0xBF*2 = 0x7E).\nNEON (vshlq_u8 / vshlq_s8) and the fallback scalar path produce wrapping results.\nThis is a cross-platform behavioral divergence that silently gives different arithmetic results.","fix":"Before calling the pack instruction, AND the 16-bit shifted results with 0x00FF to isolate the\nlow byte, then use _mm_packus_epi16 (unsigned pack, no saturation occurs since values are 0-255).\nFor i8: cast the input to u8, apply u8 shl, cast result back to i8.\nExample fix for shl_u8x16:\n let mask = _mm_set1_epi16(0x00FF);\n let lo_shifted = _mm_and_si128(_mm_sll_epi16(lo_16, shift_count), mask);\n let hi_shifted = _mm_and_si128(_mm_sll_epi16(hi_16, shift_count), mask);\n _mm_packus_epi16(lo_shifted, hi_shifted)","locations":[{"ref":"fearless_simd/src/generated/sse4_2.rs:539-550","code":" 539 | fn shl_i8x16(self, a: i8x16<Self>, shift: u32) -> i8x16<Self> {\n 540 | unsafe {\n 541 | let val = a.into();\n 542 | let shift_count = _mm_cvtsi32_si128(shift.cast_signed());\n 543 | let lo_16 = _mm_unpacklo_epi8(val, _mm_cmpgt_epi8(_mm_setzero_si128(), val));\n 544 | let hi_16 = _mm_unpackhi_epi8(val, _mm_cmpgt_epi8(_mm_setzero_si128(), val));\n 545 | let lo_shifted = _mm_sll_epi16(lo_16, shift_count);\n 546 | let hi_shifted = _mm_sll_epi16(hi_16, shift_count);\n 547 | _mm_packs_epi16(lo_shifted, hi_shifted).simd_into(self)\n 548 | }\n 549 | }\n 550 | #[inline(always)]","lines":12},{"ref":"fearless_simd/src/generated/sse4_2.rs:766-778","code":" 766 | fn shl_u8x16(self, a: u8x16<Self>, shift: u32) -> u8x16<Self> {\n 767 | unsafe {\n 768 | let val = a.into();\n 769 | let shift_count = _mm_cvtsi32_si128(shift.cast_signed());\n 770 | let lo_16 = _mm_unpacklo_epi8(val, _mm_setzero_si128());\n 771 | let hi_16 = _mm_unpackhi_epi8(val, _mm_setzero_si128());\n 772 | let lo_shifted = _mm_sll_epi16(lo_16, shift_count);\n 773 | let hi_shifted = _mm_sll_epi16(hi_16, shift_count);\n 774 | _mm_packus_epi16(lo_shifted, hi_shifted).simd_into(self)\n 775 | }\n 776 | }\n 777 | #[inline(always)]\n 778 | fn shlv_u8x16(self, a: u8x16<Self>, b: u8x16<Self>) -> u8x16<Self> {","lines":13},{"ref":"fearless_simd/src/generated/avx2.rs:510-520","code":" 510 | fn shl_i8x16(self, a: i8x16<Self>, shift: u32) -> i8x16<Self> {\n 511 | unsafe {\n 512 | let val = a.into();\n 513 | let shift_count = _mm_cvtsi32_si128(shift.cast_signed());\n 514 | let lo_16 = _mm_unpacklo_epi8(val, _mm_cmpgt_epi8(_mm_setzero_si128(), val));\n 515 | let hi_16 = _mm_unpackhi_epi8(val, _mm_cmpgt_epi8(_mm_setzero_si128(), val));\n 516 | let lo_shifted = _mm_sll_epi16(lo_16, shift_count);\n 517 | let hi_shifted = _mm_sll_epi16(hi_16, shift_count);\n 518 | _mm_packs_epi16(lo_shifted, hi_shifted).simd_into(self)\n 519 | }\n 520 | }","lines":11},{"ref":"fearless_simd/src/generated/avx2.rs:734-746","code":" 734 | fn shl_u8x16(self, a: u8x16<Self>, shift: u32) -> u8x16<Self> {\n 735 | unsafe {\n 736 | let val = a.into();\n 737 | let shift_count = _mm_cvtsi32_si128(shift.cast_signed());\n 738 | let lo_16 = _mm_unpacklo_epi8(val, _mm_setzero_si128());\n 739 | let hi_16 = _mm_unpackhi_epi8(val, _mm_setzero_si128());\n 740 | let lo_shifted = _mm_sll_epi16(lo_16, shift_count);\n 741 | let hi_shifted = _mm_sll_epi16(hi_16, shift_count);\n 742 | _mm_packus_epi16(lo_shifted, hi_shifted).simd_into(self)\n 743 | }\n 744 | }\n 745 | #[inline(always)]\n 746 | fn shlv_u8x16(self, a: u8x16<Self>, b: u8x16<Self>) -> u8x16<Self> {","lines":13}]},{"id":"LOGIC_5","type":"logic_error","type_label":"Logic Error","severity":"high","easy":false,"description":"shl_u8x16 and shl_u8x32 in SSE4.2 and AVX2 use saturating pack (_mm_packus_epi16 /\n_mm256_packus_epi16) to truncate 16-bit shifted values back to 8-bit. The trait contract\nsays 'bits shifted out of the left side are discarded', meaning wrapping behavior.\n_mm_packus_epi16 saturates to the u8 range (0-255): shl_u8x16(200u8, 1) returns 255u8\n(saturated) instead of 144u8 (wrapping: 400 & 0xFF = 0x90 = 144). NEON vshlq_u8 wraps\ncorrectly. WASM u8x16_shl also wraps.\n\nNote: shr_u8x16 / shr_u8x32 use _mm_srl_epi16 (logical right shift) on zero-extended u8\nvalues, so the result is always in [0, 127] for shift >= 1, and in [0, 255] for shift = 0.\nThe saturating pack never triggers for right shifts - only left shifts are wrong.","fix":"AND each shifted u16 lane with 0x00FF before packing: _mm_and_si128(lo_shifted, _mm_set1_epi16(0xFF)). The masked values are in 0-255, so _mm_packus_epi16 then gives the correct wrapping result.","locations":[{"ref":"fearless_simd/src/generated/sse4_2.rs:766-775","code":" 766 | fn shl_u8x16(self, a: u8x16<Self>, shift: u32) -> u8x16<Self> {\n 767 | unsafe {\n 768 | let val = a.into();\n 769 | let shift_count = _mm_cvtsi32_si128(shift.cast_signed());\n 770 | let lo_16 = _mm_unpacklo_epi8(val, _mm_setzero_si128());\n 771 | let hi_16 = _mm_unpackhi_epi8(val, _mm_setzero_si128());\n 772 | let lo_shifted = _mm_sll_epi16(lo_16, shift_count);\n 773 | let hi_shifted = _mm_sll_epi16(hi_16, shift_count);\n 774 | _mm_packus_epi16(lo_shifted, hi_shifted).simd_into(self)\n 775 | }","lines":10},{"ref":"fearless_simd/src/generated/avx2.rs:734-743","code":" 734 | fn shl_u8x16(self, a: u8x16<Self>, shift: u32) -> u8x16<Self> {\n 735 | unsafe {\n 736 | let val = a.into();\n 737 | let shift_count = _mm_cvtsi32_si128(shift.cast_signed());\n 738 | let lo_16 = _mm_unpacklo_epi8(val, _mm_setzero_si128());\n 739 | let hi_16 = _mm_unpackhi_epi8(val, _mm_setzero_si128());\n 740 | let lo_shifted = _mm_sll_epi16(lo_16, shift_count);\n 741 | let hi_shifted = _mm_sll_epi16(hi_16, shift_count);\n 742 | _mm_packus_epi16(lo_shifted, hi_shifted).simd_into(self)\n 743 | }","lines":10},{"ref":"fearless_simd/src/generated/avx2.rs:3034-3043","code":" 3034 | fn shl_u8x32(self, a: u8x32<Self>, shift: u32) -> u8x32<Self> {\n 3035 | unsafe {\n 3036 | let val = a.into();\n 3037 | let shift_count = _mm_cvtsi32_si128(shift.cast_signed());\n 3038 | let lo_16 = _mm256_unpacklo_epi8(val, _mm256_setzero_si256());\n 3039 | let hi_16 = _mm256_unpackhi_epi8(val, _mm256_setzero_si256());\n 3040 | let lo_shifted = _mm256_sll_epi16(lo_16, shift_count);\n 3041 | let hi_shifted = _mm256_sll_epi16(hi_16, shift_count);\n 3042 | _mm256_packus_epi16(lo_shifted, hi_shifted).simd_into(self)\n 3043 | }","lines":10}]},{"id":"LOGIC_6","type":"logic_error","type_label":"Logic Error","severity":"high","easy":false,"description":"In `generic.rs` `generic_op` for `OpSig::Unzip`, the code does `self.#do_half(a0, a1)` and `self.#do_half(b0, b1)`. For unzip_low and unzip_high on the half-width type, the semantics are: unzip_low([a0, a1, a2, a3], [b0, b1, b2, b3]) = [a0, a2, b0, b2]. But the generic split gives `(a0, a1)` as the two halves of `a`, so `a0 = [a0, a1]` and `a1 = [a2, a3]` (first and second halves of `a`). Then `do_half(a0, a1)` = unzip_low([a0, a1], [a2, a3]) = [a0, a2]. And `do_half(b0, b1)` = unzip_low([b0, b1], [b2, b3]) = [b0, b2]. Combined: [a0, a2, b0, b2] -- correct! For unzip_high: do_half(a0, a1) = [a1, a3], do_half(b0, b1) = [b1, b3]. Combined: [a1, a3, b1, b3] -- correct. The implementation is correct, but the logic is subtle and deserves a comment.","fix":"Add a comment explaining why splitting each input and applying unzip to the two halves of each input (not cross-inputs) yields the correct result.","locations":[{"ref":"fearless_simd_gen/src/generic.rs:143-150","code":" 143 | OpSig::Unzip { .. } => {\n 144 | quote! {\n 145 | #method_sig {\n 146 | let (a0, a1) = self.#split(a);\n 147 | let (b0, b1) = self.#split(b);\n 148 | self.#combine(self.#do_half(a0, a1), self.#do_half(b0, b1))\n 149 | }\n 150 | }","lines":8}]},{"id":"LOGIC_7","type":"logic_error","type_label":"Logic Error","severity":"high","easy":false,"description":"In `mk_neon.rs` `MaskReduce` handling, the pattern for `(Quantifier::Any, false)` uses `vminv` with check `!= 0xffffffff`. The intent is 'any lane is false (i.e., equals zero)'. The minimum value across all lanes is compared against `0xffffffff`. If the minimum is not `0xffffffff`, at least one lane is not all-ones, so it's false. However, `vminv` returns a `u32`, and the mask lanes are stored as all-zeros (0) or all-ones (-1 as unsigned = 0xffffffff). So 'min != 0xffffffff' correctly detects any zero (false) lane. This appears correct, but the `u32_ty` used is `vec_ty.reinterpret(ScalarType::Unsigned, 32)` -- for an 8-bit mask this would be `u32x4` (reinterpreting 128 bits as 32-bit chunks), which is the right representation for `vminv_u32` / `vmaxv_u32`. But for a 16-bit mask (also 128 bits = 8 lanes), reinterpreting as u32 gives 4 u32 lanes, not 8. The `vminv_u32` on this would only check 4 values, missing the upper 4 mask lanes.","fix":"For 8-bit and 16-bit masks, use the appropriate lane-size reinterpretation for the reduction: for 8-bit lanes use `vmaxv_u8`/`vminv_u8`, and for 16-bit lanes use `vmaxv_u16`/`vminv_u16`, so all mask lanes are checked. The current code reinterprets all masks as u32 regardless of lane width.","locations":[{"ref":"fearless_simd_gen/src/mk_neon.rs:511-531","code":" 511 | OpSig::MaskReduce {\n 512 | quantifier,\n 513 | condition,\n 514 | } => {\n 515 | let (reduction, target) = match (quantifier, condition) {\n 516 | (crate::ops::Quantifier::Any, true) => (\"vmaxv\", quote! { != 0 }),\n 517 | (crate::ops::Quantifier::Any, false) => (\"vminv\", quote! { != 0xffffffff }),\n 518 | (crate::ops::Quantifier::All, true) => (\"vminv\", quote! { == 0xffffffff }),\n 519 | (crate::ops::Quantifier::All, false) => (\"vmaxv\", quote! { == 0 }),\n 520 | };\n 521 | \n 522 | let u32_ty = vec_ty.reinterpret(ScalarType::Unsigned, 32);\n 523 | let min_max = simple_intrinsic(reduction, &u32_ty);\n 524 | let reinterpret = format_ident!(\"vreinterpretq_u32_s{}\", vec_ty.scalar_bits);\n 525 | quote! {\n 526 | #method_sig {\n 527 | unsafe {\n 528 | #min_max(#reinterpret(a.into())) #target\n 529 | }\n 530 | }\n 531 | }","lines":21}]},{"id":"LOGIC_8","type":"logic_error","type_label":"Logic Error","severity":"high","easy":false,"description":"In `mk_neon.rs` `handle_mask_from_bitmask` for 8-bit masks (`scalar_bits == 8`), the shifts array is declared as `[i16; 8]` with values `[15, 14, 13, 12, 11, 10, 9, 8]` and transmitted to `int16x8_t`. This handles 8 lanes. Then a second broadcast with `(bits >> 8)` handles the other 8 lanes. However, the shifts are the same `[15, 14, ...]` for both the low and high halves, but to extract the correct bit for the high 8 lanes (lanes 8-15), the shifts into the u16 should be `[15, 14, 13, 12, 11, 10, 9, 8]` applied to `bits >> 8`. The shift removes 8 from the bit index by shifting the bits, so this is actually correct -- but there's a subtle issue: the shifts `[15, 14, ..., 8]` shift a bit into the sign position. Lane 0 of the hi vector needs bit 8, which after `bits >> 8` becomes bit 0, and shift of 15 puts it at bit 15 (sign). This appears correct. The issue is the `checked_transmute_copy` transmutes `[i16; 8]` as if it were `int16x8_t`, but the `shifts` array contains the shift amounts as signed i16, whereas `vshlq_u16` interprets the shift register as a signed shift -- this should be fine.","fix":"No fix needed if logic is verified correct; add a comment explaining the shift direction convention to prevent future confusion.","locations":[{"ref":"fearless_simd_gen/src/mk_neon.rs:580-597","code":" 580 | 8 => quote! {\n 581 | #method_sig {\n 582 | unsafe {\n 583 | let shifts =\n 584 | crate::transmute::checked_transmute_copy::<[i16; 8], int16x8_t>(\n 585 | &[15, 14, 13, 12, 11, 10, 9, 8],\n 586 | );\n 587 | let lo = vshlq_u16(vdupq_n_u16(bits as u16), shifts);\n 588 | let hi = vshlq_u16(vdupq_n_u16((bits >> 8) as u16), shifts);\n 589 | let lo = vcltq_s16(vreinterpretq_s16_u16(lo), vdupq_n_s16(0));\n 590 | let hi = vcltq_s16(vreinterpretq_s16_u16(hi), vdupq_n_s16(0));\n 591 | vcombine_s8(\n 592 | vmovn_s16(vreinterpretq_s16_u16(lo)),\n 593 | vmovn_s16(vreinterpretq_s16_u16(hi)),\n 594 | ).simd_into(self)\n 595 | }\n 596 | }\n 597 | },","lines":18}]},{"id":"LOGIC_9","type":"logic_error","type_label":"Logic Error","severity":"high","easy":false,"description":"In `mk_x86.rs` for the AVX2 `widen` path from 256-bit to 512-bit (`(Self::Avx2, 512, 256)`), after splitting the input vector into `a0` (low half) and `a1` (high half), the widened result is labeled `high = extend(a0)` and `low = extend(a1)`. This is backwards: `a0` is the low half of the original vector, so widening it should produce the low half of the result, not the high half. The combine call is `self.#combine(high, low)`, which places the widened low-half first and the widened high-half second -- reversing the element order.","fix":"Swap the variable names: `let low = #extend(a0.into()); let high = #extend(a1.into());` then call `self.#combine(low, high)`.","locations":[{"ref":"fearless_simd_gen/src/mk_x86.rs:900-921","code":" 900 | }\n 901 | (Self::Avx2, 512, 256) => {\n 902 | let extend = extend_intrinsic(\n 903 | vec_ty.scalar,\n 904 | vec_ty.scalar_bits,\n 905 | target_ty.scalar_bits,\n 906 | vec_ty.n_bits(),\n 907 | );\n 908 | let combine = generic_op_name(\n 909 | \"combine\",\n 910 | &vec_ty.reinterpret(vec_ty.scalar, vec_ty.scalar_bits * 2),\n 911 | );\n 912 | let split = generic_op_name(\"split\", vec_ty);\n 913 | quote! {\n 914 | unsafe {\n 915 | let (a0, a1) = self.#split(a);\n 916 | let high = #extend(a0.into()).simd_into(self);\n 917 | let low = #extend(a1.into()).simd_into(self);\n 918 | self.#combine(high, low)\n 919 | }\n 920 | }\n 921 | }","lines":22}]},{"id":"LOGIC_10","type":"logic_error","type_label":"Logic Error","severity":"high","easy":false,"description":"In `mk_x86.rs` for the SSE4.2 `widen` path from 128-bit to 256-bit (`(Self::Sse4_2, 256, 128)`), the comment says 'Shift by 8 since we want to get the higher part into the lower position', and `_mm_srli_si128::<8>` is used to shift. But the result of extending the unshifted input is assigned to `high` and the shifted (upper) half to `low`. This means the combine is `combine(high, low)` which places the widened lower 8 bytes first (correct) but the variable naming is inverted, making the code confusing and error-prone. The actual element order may be correct but needs careful verification.","fix":"Rename `high` and `low` to reflect their actual content: `let low_widened = extend(raw); let high_widened = extend(_mm_srli_si128::<8>(raw)); combine(low_widened, high_widened)`.","locations":[{"ref":"fearless_simd_gen/src/mk_x86.rs:922-941","code":" 922 | (Self::Sse4_2, 256, 128) => {\n 923 | let extend = extend_intrinsic(\n 924 | vec_ty.scalar,\n 925 | vec_ty.scalar_bits,\n 926 | target_ty.scalar_bits,\n 927 | vec_ty.n_bits(),\n 928 | );\n 929 | let combine = generic_op_name(\n 930 | \"combine\",\n 931 | &vec_ty.reinterpret(vec_ty.scalar, vec_ty.scalar_bits * 2),\n 932 | );\n 933 | quote! {\n 934 | unsafe {\n 935 | let raw = a.into();\n 936 | let high = #extend(raw).simd_into(self);\n 937 | // Shift by 8 since we want to get the higher part into the\n 938 | // lower position.\n 939 | let low = #extend(_mm_srli_si128::<8>(raw)).simd_into(self);\n 940 | self.#combine(high, low)\n 941 | }","lines":20}]},{"id":"LOGIC_11","type":"logic_error","type_label":"Logic Error","severity":"high","easy":false,"description":"In `mk_x86.rs` `handle_cvt` for float-to-unsigned, imprecise path (line ~1681): the code adjusts values >= 2^31 by computing `excess = a - 2^31` and converting that. The `andnot(in_range, excess)` masks out lanes that ARE in range, keeping only out-of-range lanes. Then `excess_converted` is added to `converted`. But `converted` was already computed as `cvttps_epi32(a)`. For values between 2^31 and 2^32-1, `cvttps_epi32` returns the 'invalid' sentinel `-2^31` (0x80000000). Adding `excess_converted` (which is `cvttps_epi32(a - 2^31)`, i.e., the value minus 2^31) to the sentinel value `0x80000000` gives the expected result via unsigned addition wraparound. For values < 0, `cvttps_epi32` returns `0x80000000` and excess would be negative, so `andnot(in_range, excess)` should zero it out since `in_range` covers the negative case too (0.0 to 2^31-1). However, 'in_range' is the result of cmplt(a, 2^31), which is TRUE for negative values as well. So negative values are considered 'in range' by the cmplt test and converted directly by cvttps. For negative floats, cvttps gives the correct wrapping 2's complement result for the negative integer. But for u32, negative float values should give implementation-defined/saturated results, and the code might produce unexpected values for large negative inputs.","fix":"Document the behavior for negative float inputs in the imprecise u32 conversion, or clamp to 0 for negative values before conversion.","locations":[{"ref":"fearless_simd_gen/src/mk_x86.rs:1681-1699","code":" 1681 | (ScalarType::Unsigned, false) => {\n 1682 | quote! {\n 1683 | unsafe {\n 1684 | let mut converted = #convert(a.into());\n 1685 | \n 1686 | // In the common case where everything is in range of an i32, we don't need to do anything else.\n 1687 | let in_range = #cmplt(a.into(), #set1_float(2147483648.0));\n 1688 | let all_in_range = #movemask(in_range) == #all_ones;\n 1689 | \n 1690 | if !all_in_range {\n 1691 | // Add any excess (beyond the maximum value)\n 1692 | let excess = #sub_float(a.into(), #set1_float(2147483648.0));\n 1693 | let excess_converted = #convert(#andnot(in_range, excess));\n 1694 | converted = #add_int(converted, excess_converted);\n 1695 | }\n 1696 | \n 1697 | converted.simd_into(self)\n 1698 | }\n 1699 | }","lines":19}]},{"id":"LOGIC_12","type":"logic_error","type_label":"Logic Error","severity":"high","easy":false,"description":"In `types.rs`, `VecType::widened` only widens `u8` vectors (it returns `None` unless `scalar_bits == 8`), but the resulting type has the same `len`, not half the `len`. A widened vector should have half as many elements (each element is twice as wide), but the code returns `Self::new(self.scalar, scalar_bits * 2, self.len)` -- keeping the same lane count. This means `u8x16.widened()` returns a `u16x16` (256 bits) rather than `u16x8` (128 bits). The operation description says 'The number of elements in the result is half that of the input', which contradicts the implementation.","fix":"Return `Self::new(self.scalar, scalar_bits, self.len / 2)` with `scalar_bits = self.scalar_bits * 2`. Concretely: `Some(Self::new(self.scalar, self.scalar_bits * 2, self.len / 2))`.","locations":[{"ref":"fearless_simd_gen/src/types.rs:128-138","code":" 128 | pub(crate) fn widened(&self) -> Option<Self> {\n 129 | if matches!(self.scalar, ScalarType::Mask | ScalarType::Float)\n 130 | || self.n_bits() > 256\n 131 | || self.scalar_bits != 8\n 132 | {\n 133 | return None;\n 134 | }\n 135 | \n 136 | let scalar_bits = self.scalar_bits * 2;\n 137 | Some(Self::new(self.scalar, scalar_bits, self.len))\n 138 | }","lines":11}]},{"id":"LOGIC_13","type":"logic_error","type_label":"Logic Error","severity":"high","easy":false,"description":"In `types.rs`, `VecType::narrowed` has the inverse bug: it keeps the same `len` when it should double it. A narrowed vector should have twice as many elements (each element is half as wide). The docstring for the generated `narrow` op says 'The number of elements in the result is twice that of the input', but `narrowed()` returns `Self::new(self.scalar, scalar_bits / 2, self.len)` with the same count.","fix":"Return `Self::new(self.scalar, self.scalar_bits / 2, self.len * 2)`.","locations":[{"ref":"fearless_simd_gen/src/types.rs:140-150","code":" 140 | pub(crate) fn narrowed(&self) -> Option<Self> {\n 141 | if matches!(self.scalar, ScalarType::Mask | ScalarType::Float)\n 142 | || self.n_bits() < 256\n 143 | || self.scalar_bits != 16\n 144 | {\n 145 | return None;\n 146 | }\n 147 | \n 148 | let scalar_bits = self.scalar_bits / 2;\n 149 | Some(Self::new(self.scalar, scalar_bits, self.len))\n 150 | }","lines":11}]},{"id":"LOGIC_14","type":"logic_error","type_label":"Logic Error","severity":"high","easy":false,"description":"The `approximate_recip_f64x2` test (named for f64x2) actually creates a f64x4 vector and only loops over the first 2 elements. The function is tagged #[simd_test] so it receives a generic `S: Simd`, yet the test body hard-codes `f64x4` instead of `f64x2`. This means the f64x2 recip path is never exercised and a correctness bug there would go undetected.","fix":"Replace `f64x4::from_slice(simd, &[1.0, -2.0, 23.0, 9.0])` with `f64x2::from_slice(simd, &[1.0, -2.0])` and update the loop bound and expected slice accordingly.","locations":[{"ref":"fearless_simd_tests/tests/harness/mod.rs:3203-3214","code":" 3203 | fn approximate_recip_f64x2<S: Simd>(simd: S) {\n 3204 | let a = f64x4::from_slice(simd, &[1.0, -2.0, 23.0, 9.0]);\n 3205 | let result = a.approximate_recip();\n 3206 | let expected = [1.0, -0.5, 1. / 23., 1. / 9.];\n 3207 | for i in 0..2 {\n 3208 | let rel_error = ((result[i] - expected[i]) / expected[i]).abs();\n 3209 | assert!(\n 3210 | rel_error < 0.005,\n 3211 | \"approximate_recip({}) rel_error = {rel_error}\",\n 3212 | a[i]\n 3213 | );\n 3214 | }","lines":12}]},{"id":"LOGIC_15","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"Fallback::level() without the `force_support_fallback` feature calls Level::baseline() instead of\nreturning Level::Fallback. This means that when a FnOnce(Level)->R is used as WithSimd and\ndispatched through the Fallback token (e.g., via a future API or if Fallback becomes constructible\nwithout the feature), the closure receives a Level corresponding to the best static target (e.g.,\nLevel::Neon) rather than Level::Fallback. This is subtle: a library storing the returned Level\nwill get a non-Fallback Level even though it was invoked with the Fallback Simd token.\nCurrently the only constructor for Level::Fallback is Level::fallback() which requires\n`force_support_fallback`, so the impact is limited, but the invariant is fragile.","fix":"Consider always returning Level::Fallback(self) from Fallback::level(). If the Fallback variant\nis not available (because the cfg would exclude it), the code already is cfg-guarded. Having\nlevel() return a different level than the simd token represents violates the principle of least\nsurprise for users of the WithSimd trait.","locations":[{"ref":"fearless_simd/src/generated/fallback.rs:141-146","code":" 141 | fn level(self) -> Level {\n 142 | #[cfg(feature = \"force_support_fallback\")]\n 143 | return Level::Fallback(self);\n 144 | #[cfg(not(feature = \"force_support_fallback\"))]\n 145 | Level::baseline()\n 146 | }","lines":6},{"ref":"fearless_simd/src/traits.rs:28-35","code":" 28 | impl<R, F: FnOnce(Level) -> R> WithSimd for F {\n 29 | type Output = R;\n 30 | \n 31 | #[inline(always)]\n 32 | fn with_simd<S: Simd>(self, simd: S) -> Self::Output {\n 33 | self(simd.level())\n 34 | }\n 35 | }","lines":8}]},{"id":"LOGIC_16","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"The `slide_f32x4` (and all analogous `slide_T_Nx` functions) implement a\n`dest[..N - SHIFT]` / `dest[N - SHIFT..]` split using usize subtraction. When\n`SHIFT > N` the subtraction underflows and panics in debug builds (or wraps to a huge\nusize in release builds, causing an out-of-bounds slice panic). Because `SHIFT` is a\nconst generic there is no runtime guard; the generator does not emit any static assertion\nthat `SHIFT <= N`. A caller that instantiates e.g. `slide_f32x4::<5>` will compile but\ncrash at runtime in debug mode and produce a wrong-length slice panic in release mode.\n\nThe same issue exists for every `slide_T_Nx` variant (slide_i8x16, slide_u8x16,\nslide_i16x8, slide_u16x8, slide_i32x4, slide_u32x4, slide_f64x2, and all 256/512-bit\nvariants).","fix":"Add a compile-time assertion `const { assert!(SHIFT <= N, \"SHIFT must be <= vector length\"); }` at the top of each slide function body. This converts the latent runtime panic into a clear compile error.","locations":[{"ref":"fearless_simd/src/generated/fallback.rs:203-208","code":" 203 | fn slide_f32x4<const SHIFT: usize>(self, a: f32x4<Self>, b: f32x4<Self>) -> f32x4<Self> {\n 204 | let mut dest = [Default::default(); 4usize];\n 205 | dest[..4usize - SHIFT].copy_from_slice(&a.val.0[SHIFT..]);\n 206 | dest[4usize - SHIFT..].copy_from_slice(&b.val.0[..SHIFT]);\n 207 | dest.simd_into(self)\n 208 | }","lines":6},{"ref":"fearless_simd/src/generated/fallback.rs:599-603","code":" 599 | fn slide_i8x16<const SHIFT: usize>(self, a: i8x16<Self>, b: i8x16<Self>) -> i8x16<Self> {\n 600 | let mut dest = [Default::default(); 16usize];\n 601 | dest[..16usize - SHIFT].copy_from_slice(&a.val.0[SHIFT..]);\n 602 | dest[16usize - SHIFT..].copy_from_slice(&b.val.0[..SHIFT]);\n 603 | dest.simd_into(self)","lines":5},{"ref":"fearless_simd/src/generated/fallback.rs:1224-1228","code":" 1224 | fn slide_u8x16<const SHIFT: usize>(self, a: u8x16<Self>, b: u8x16<Self>) -> u8x16<Self> {\n 1225 | let mut dest = [Default::default(); 16usize];\n 1226 | dest[..16usize - SHIFT].copy_from_slice(&a.val.0[SHIFT..]);\n 1227 | dest[16usize - SHIFT..].copy_from_slice(&b.val.0[..SHIFT]);\n 1228 | dest.simd_into(self)","lines":5},{"ref":"fearless_simd/src/generated/fallback.rs:2164-2168","code":" 2164 | fn slide_i16x8<const SHIFT: usize>(self, a: i16x8<Self>, b: i16x8<Self>) -> i16x8<Self> {\n 2165 | let mut dest = [Default::default(); 8usize];\n 2166 | dest[..8usize - SHIFT].copy_from_slice(&a.val.0[SHIFT..]);\n 2167 | dest[8usize - SHIFT..].copy_from_slice(&b.val.0[..SHIFT]);\n 2168 | dest.simd_into(self)","lines":5},{"ref":"fearless_simd/src/generated/fallback.rs:2590-2594","code":" 2590 | fn slide_u16x8<const SHIFT: usize>(self, a: u16x8<Self>, b: u16x8<Self>) -> u16x8<Self> {\n 2591 | let mut dest = [Default::default(); 8usize];\n 2592 | dest[..8usize - SHIFT].copy_from_slice(&a.val.0[SHIFT..]);\n 2593 | dest[8usize - SHIFT..].copy_from_slice(&b.val.0[..SHIFT]);\n 2594 | dest.simd_into(self)","lines":5},{"ref":"fearless_simd/src/generated/fallback.rs:3209-3213","code":" 3209 | fn slide_i32x4<const SHIFT: usize>(self, a: i32x4<Self>, b: i32x4<Self>) -> i32x4<Self> {\n 3210 | let mut dest = [Default::default(); 4usize];\n 3211 | dest[..4usize - SHIFT].copy_from_slice(&a.val.0[SHIFT..]);\n 3212 | dest[4usize - SHIFT..].copy_from_slice(&b.val.0[..SHIFT]);\n 3213 | dest.simd_into(self)","lines":5},{"ref":"fearless_simd/src/generated/fallback.rs:3537-3541","code":" 3537 | fn slide_u32x4<const SHIFT: usize>(self, a: u32x4<Self>, b: u32x4<Self>) -> u32x4<Self> {\n 3538 | let mut dest = [Default::default(); 4usize];\n 3539 | dest[..4usize - SHIFT].copy_from_slice(&a.val.0[SHIFT..]);\n 3540 | dest[4usize - SHIFT..].copy_from_slice(&b.val.0[..SHIFT]);\n 3541 | dest.simd_into(self)","lines":5},{"ref":"fearless_simd/src/generated/fallback.rs:3990-3994","code":" 3990 | fn slide_f64x2<const SHIFT: usize>(self, a: f64x2<Self>, b: f64x2<Self>) -> f64x2<Self> {\n 3991 | let mut dest = [Default::default(); 2usize];\n 3992 | dest[..2usize - SHIFT].copy_from_slice(&a.val.0[SHIFT..]);\n 3993 | dest[2usize - SHIFT..].copy_from_slice(&b.val.0[..SHIFT]);\n 3994 | dest.simd_into(self)","lines":5}]},{"id":"LOGIC_17","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"shlv_i8x16 (variable left shift where the shift amounts are i8 elements) exhibits fundamentally\ndifferent behavior across platforms for negative shift amounts:\n- NEON (vshlq_s8): a negative shift amount means RIGHT shift by the absolute value. So passing\n b[i] = -3 shifts a[i] RIGHT by 3. This is intentional NEON behavior.\n- Fallback and AVX2 scalar paths: use Rust's i8 << i8 operator, which panics in debug mode\n for any negative shift amount (and is UB in release).\nThis means code that relies on NEON's bidirectional shift semantics will crash on other platforms.\nThe trait documentation does not mention this at all.","fix":"Document clearly in the shlv trait method that negative shift amounts are implementation-defined\n(and produce panics on non-NEON platforms in debug mode). If portable behavior is desired, add a\nprecondition that shift amounts must be non-negative, or provide a distinct bidirectional-shift method\nthat makes the NEON semantics explicit.","locations":[{"ref":"fearless_simd/src/generated/neon.rs:211-213","code":" 211 | unsafe { vreinterpretq_s32_u32(vcleq_f32(a.into(), b.into())).simd_into(self) }\n 212 | }\n 213 | #[inline(always)]","lines":3},{"ref":"fearless_simd/src/generated/fallback.rs:790-810","code":" 790 | fn shlv_i8x16(self, a: i8x16<Self>, b: i8x16<Self>) -> i8x16<Self> {\n 791 | [\n 792 | i8::shl(a[0usize], &b[0usize]),\n 793 | i8::shl(a[1usize], &b[1usize]),\n 794 | i8::shl(a[2usize], &b[2usize]),\n 795 | i8::shl(a[3usize], &b[3usize]),\n 796 | i8::shl(a[4usize], &b[4usize]),\n 797 | i8::shl(a[5usize], &b[5usize]),\n 798 | i8::shl(a[6usize], &b[6usize]),\n 799 | i8::shl(a[7usize], &b[7usize]),\n 800 | i8::shl(a[8usize], &b[8usize]),\n 801 | i8::shl(a[9usize], &b[9usize]),\n 802 | i8::shl(a[10usize], &b[10usize]),\n 803 | i8::shl(a[11usize], &b[11usize]),\n 804 | i8::shl(a[12usize], &b[12usize]),\n 805 | i8::shl(a[13usize], &b[13usize]),\n 806 | i8::shl(a[14usize], &b[14usize]),\n 807 | i8::shl(a[15usize], &b[15usize]),\n 808 | ]\n 809 | .simd_into(self)\n 810 | }","lines":21},{"ref":"fearless_simd/src/generated/avx2.rs:522-524","code":" 522 | fn shlv_i8x16(self, a: i8x16<Self>, b: i8x16<Self>) -> i8x16<Self> {\n 523 | core::array::from_fn(|i| core::ops::Shl::shl(a[i], b[i])).simd_into(self)\n 524 | }","lines":3},{"ref":"fearless_simd/src/generated/sse4_2.rs:551-553","code":" 551 | fn shlv_i8x16(self, a: i8x16<Self>, b: i8x16<Self>) -> i8x16<Self> {\n 552 | core::array::from_fn(|i| core::ops::Shl::shl(a[i], b[i])).simd_into(self)\n 553 | }","lines":3}]},{"id":"LOGIC_18","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"NEON shl_i8x16 and shl_u8x16 cast 'shift as i8' to pass to vshlq_s8/vshlq_u8. For\nshift values >= 128, the wrapping cast produces a negative i8, telling NEON to shift\nRIGHT instead of left. For shift in range [8, 127] the ARM intrinsic returns 0 for both\nleft shifts >= element_size and right shifts >= element_size, so the wrong direction\naccidentally gives the right result. But for shift = 128: cast gives -128i8, and\nvshlq_s8(a, -128) arithmetic-right-shifts by 128 (gives 0 or -1 based on sign), while\nthe expected result for shl by 128 is 0 for all inputs.\n\nThe same wrapping-cast-as-signed issue affects shl_i16x8 (shift as i16, threshold 32768)\nand shl_u16x8.","fix":"Clamp shift to element_size before passing: if shift >= 8 { return zeros; } else { vshlq_s8(a, vdupq_n_s8(shift as i8)) }.","locations":[{"ref":"fearless_simd/src/generated/neon.rs:447","code":" 447 | unsafe { vshlq_s8(a.into(), vdupq_n_s8(shift as i8)).simd_into(self) }","lines":1},{"ref":"fearless_simd/src/generated/neon.rs:647","code":" 647 | unsafe { vshlq_u8(a.into(), vdupq_n_s8(shift as i8)).simd_into(self) }","lines":1},{"ref":"fearless_simd/src/generated/neon.rs:946","code":" 946 | unsafe { vshlq_s16(a.into(), vdupq_n_s16(shift as i16)).simd_into(self) }","lines":1},{"ref":"fearless_simd/src/generated/neon.rs:1146","code":" 1146 | unsafe { vshlq_u16(a.into(), vdupq_n_s16(shift as i16)).simd_into(self) }","lines":1}]},{"id":"LOGIC_19","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"cvt_u32_f32x4 in SSE4.2 and AVX2 has a data-dependent branch (if !all_in_range) that\nchanges lane-level behavior: when only some lanes have values >= 2^31, the _mm_andnot_ps\nmask selects between original and excess values per-lane, but the branch is entered based\non the collective movemask of all four lanes. This is correct but non-obvious. More\nimportantly, for negative float inputs in cvt_u32_f32x4 (non-precise): _mm_cvttps_epi32\nreturns 0x80000000 for any negative float, and the 'excess' path is only entered for\nvalues >= 2^31. So negative values silently return 0x80000000 = 2147483648u32 instead\nof 0. NEON vcvtq_u32_f32 clamps negative inputs to 0. WASM u32x4_trunc_sat_f32x4 also\nclamps to 0. The SSE4.2/AVX2 cvt_u32_f32x4 (non-precise) thus differs from NEON and WASM\nfor negative inputs.","fix":"Either document that cvt_u32_f32x4 is undefined for negative inputs, or add a clamp: let a = _mm_max_ps(a.into(), _mm_setzero_ps()); before the conversion.","locations":[{"ref":"fearless_simd/src/generated/sse4_2.rs:370-382","code":" 370 | fn cvt_u32_f32x4(self, a: f32x4<Self>) -> u32x4<Self> {\n 371 | unsafe {\n 372 | let mut converted = _mm_cvttps_epi32(a.into());\n 373 | let in_range = _mm_cmplt_ps(a.into(), _mm_set1_ps(2147483648.0));\n 374 | let all_in_range = _mm_movemask_ps(in_range) == 0b1111;\n 375 | if !all_in_range {\n 376 | let excess = _mm_sub_ps(a.into(), _mm_set1_ps(2147483648.0));\n 377 | let excess_converted = _mm_cvttps_epi32(_mm_andnot_ps(in_range, excess));\n 378 | converted = _mm_add_epi32(converted, excess_converted);\n 379 | }\n 380 | converted.simd_into(self)\n 381 | }\n 382 | }","lines":13},{"ref":"fearless_simd/src/generated/avx2.rs:341-353","code":" 341 | fn cvt_u32_f32x4(self, a: f32x4<Self>) -> u32x4<Self> {\n 342 | unsafe {\n 343 | let mut converted = _mm_cvttps_epi32(a.into());\n 344 | let in_range = _mm_cmplt_ps(a.into(), _mm_set1_ps(2147483648.0));\n 345 | let all_in_range = _mm_movemask_ps(in_range) == 0b1111;\n 346 | if !all_in_range {\n 347 | let excess = _mm_sub_ps(a.into(), _mm_set1_ps(2147483648.0));\n 348 | let excess_converted = _mm_cvttps_epi32(_mm_andnot_ps(in_range, excess));\n 349 | converted = _mm_add_epi32(converted, excess_converted);\n 350 | }\n 351 | converted.simd_into(self)\n 352 | }\n 353 | }","lines":13}]},{"id":"LOGIC_20","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"The `#[simd_test]` macro-generated x86 SSE4.2 and AVX2 test stubs silently pass when the required CPU features are absent (they return early without running the test body). The comment at line 59 acknowledges this is \"not ideal\" but no mechanism informs the developer or CI that the test was not actually exercised. A broken SSE4.2 implementation would produce a false-positive green build on machines that happen not to support it.","fix":"Emit a `#[cfg_attr(not(target_feature = \"...\"), ignore)]` where possible, or at minimum record a skip via `eprintln!` so the skip is visible in test output. Alternatively track skipped tests and fail with a summary at the end.","locations":[{"ref":"fearless_simd_dev_macros/src/lib.rs:58-116","code":" 58 | // There is currently no way to conditionally ignore a test at runtime (see\n 59 | // https://internals.rust-lang.org/t/pre-rfc-skippable-tests/14611). Instead, we'll just pass the tests if the\n 60 | // target features aren't supported. This is not ideal, since it may mislead you into thinking tests have passed\n 61 | // when they haven't even been run, but some CI runners don't support all target features and we don't want failures\n 62 | // as a result of that.\n 63 | \n 64 | let neon_snippet = quote! {\n 65 | #[cfg(target_arch = \"aarch64\")]\n 66 | #[test]\n 67 | #ignore_neon\n 68 | fn #neon_name() {\n 69 | if std::arch::is_aarch64_feature_detected!(\"neon\") {\n 70 | let neon = unsafe { fearless_simd::aarch64::Neon::new_unchecked() };\n 71 | #input_fn_name(neon);\n 72 | }\n 73 | }\n 74 | };\n 75 | \n 76 | let sse4_snippet = quote! {\n 77 | #[cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))]\n 78 | #[test]\n 79 | #ignore_sse4\n 80 | fn #sse4_name() {\n 81 | if std::arch::is_x86_feature_detected!(\"sse4.2\")\n 82 | && std::arch::is_x86_feature_detected!(\"cmpxchg16b\")\n 83 | && std::arch::is_x86_feature_detected!(\"popcnt\")\n 84 | {\n 85 | let sse4 = unsafe { fearless_simd::x86::Sse4_2::new_unchecked() };\n 86 | sse4.vectorize(\n 87 | #[inline(always)]\n 88 | || #input_fn_name(sse4)\n 89 | );\n 90 | }\n 91 | }\n 92 | };\n 93 | \n 94 | let avx2_snippet = quote! {\n 95 | #[cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))]\n 96 | #[test]\n 97 | #ignore_avx2\n 98 | fn #avx2_name() {\n 99 | if std::arch::is_x86_feature_detected!(\"avx2\")\n 100 | && std::arch::is_x86_feature_detected!(\"bmi1\")\n 101 | && std::arch::is_x86_feature_detected!(\"bmi2\")\n 102 | && std::arch::is_x86_feature_detected!(\"cmpxchg16b\")\n 103 | && std::arch::is_x86_feature_detected!(\"f16c\")\n 104 | && std::arch::is_x86_feature_detected!(\"fma\")\n 105 | && std::arch::is_x86_feature_detected!(\"lzcnt\")\n 106 | && std::arch::is_x86_feature_detected!(\"movbe\")\n 107 | && std::arch::is_x86_feature_detected!(\"popcnt\")\n 108 | && std::arch::is_x86_feature_detected!(\"xsave\")\n 109 | {\n 110 | let avx2 = unsafe { fearless_simd::x86::Avx2::new_unchecked() };\n 111 | avx2.vectorize(\n 112 | #[inline(always)]\n 113 | || #input_fn_name(avx2)\n 114 | );\n 115 | }\n 116 | }","lines":59}]},{"id":"LOGIC_21","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"In `generic.rs` `generic_op` for `OpSig::Cvt` and `OpSig::Reinterpret`, the variable `combine` is computed but never used in the generated code body. The `combine` ident is shadowed and then only `self.#combine(...)` references from the outer function `combine` variable are used. Specifically at line 163, `let combine = Ident::new(...)` is created but the `quote!` block at line 165 still uses the outer `combine` variable (which refers to `generic_op_name('combine', &half)` computed at line 35). This is dead code in the generator (the new `combine` variable is unused).","fix":"Remove the unused `let combine = ...` binding at line 163, or verify whether the correct combine method name should be used and fix accordingly.","locations":[{"ref":"fearless_simd_gen/src/generic.rs:161-169","code":" 161 | let mut half = ty.reinterpret(target_ty, scalar_bits);\n 162 | half.len /= 2;\n 163 | let combine = Ident::new(&format!(\"combine_{}\", half.rust_name()), Span::call_site());\n 164 | quote! {\n 165 | #method_sig {\n 166 | let (a0, a1) = self.#split(a);\n 167 | self.#combine(self.#do_half(a0), self.#do_half(a1))\n 168 | }\n 169 | }","lines":9}]},{"id":"LOGIC_22","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"In `generic.rs` `generic_op` for `OpSig::WidenNarrow`, `target_ty.len` is mutated in-place by `target_ty.len /= 2` (line 172), which modifies the local copy. The `combine` ident then uses this halved `target_ty`. However the generated code body uses `self.#do_half(a0)` and `self.#do_half(a1)` where each call returns a vector of the full `target_ty` size (not the halved size). Halving the len just to compute the combine method name is confusing and appears incorrect -- the combine method for the full `target_ty` (not half) is what's actually needed to combine the two half results.","fix":"Clarify the intent: if `do_half` returns a half-size target type, the combine target should indeed be the full target type. Rename and compute the combine name from the unmodified `target_ty`, not from a mutated copy.","locations":[{"ref":"fearless_simd_gen/src/generic.rs:171-178","code":" 171 | OpSig::WidenNarrow { mut target_ty } => {\n 172 | target_ty.len /= 2;\n 173 | let combine = generic_op_name(\"combine\", &target_ty);\n 174 | quote! {\n 175 | #method_sig {\n 176 | let (a0, a1) = self.#split(a);\n 177 | self.#combine(self.#do_half(a0), self.#do_half(a1))\n 178 | }","lines":8}]},{"id":"LOGIC_23","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"In `level.rs` `make_type_impl`, the filter condition is `if n_bits > max_block_size || n_bits < native_width`. For SSE4.2 where both `native_width` and `max_block_size` are 128, this skips types with `n_bits < 128`. But there are no types smaller than 128 bits in `SIMD_TYPES`, so the `< native_width` part is always false and the condition reduces to just `n_bits > max_block_size`. For NEON and Fallback where `native_width == 128` and `max_block_size == 512`, the condition `n_bits < 128` would skip no types, and `n_bits > 512` would also skip no types (no 512+ bit types exist beyond those already in `SIMD_TYPES`). The condition is effectively always just the first part for all current backends, making the second disjunct dead code.","fix":"Remove the dead `|| n_bits < native_width` clause, or document when it would ever be non-false with future backends.","locations":[{"ref":"fearless_simd_gen/src/level.rs:190","code":" 190 | if n_bits > max_block_size || n_bits < native_width {","lines":1}]},{"id":"LOGIC_24","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"In `mk_fallback.rs` `make_method` for `OpSig::Ternary`, when the method is not `mul_add` or `mul_sub`, the fallback uses `fallback::expr(method, vec_ty, &args)` with args as `[a.into(), b.into(), c.into()]` and then calls `.simd_into(self)` on the result. However, `fallback::expr` dispatches through `translate_op` in `arch/fallback.rs`, which does not handle any ternary operations (it only handles unary and binary ops). So any ternary operation other than `mul_add`/`mul_sub` would call `fallback::expr` with an unknown op and hit the `unimplemented!` branch at the top level, causing a generator panic.","fix":"Remove the `else` branch that calls `fallback::expr` for unknown ternary ops, or replace it with `unreachable!()` if no other ternary ops exist, documenting that `mul_add` and `mul_sub` are the only ternary ops.","locations":[{"ref":"fearless_simd_gen/src/mk_fallback.rs:253-265","code":" 253 | let args = [\n 254 | quote! { a.into() },\n 255 | quote! { b.into() },\n 256 | quote! { c.into() },\n 257 | ];\n 258 | \n 259 | let expr = fallback::expr(method, vec_ty, &args);\n 260 | quote! {\n 261 | #method_sig {\n 262 | #expr.simd_into(self)\n 263 | }\n 264 | }\n 265 | }","lines":13}]},{"id":"LOGIC_25","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"In `mk_fallback.rs` `interleave_indices`, the `stride` parameter passed for `StoreInterleaved` is `len / block_count as usize` (the number of elements per block/channel). But `interleave` transposes a `height x width` matrix where `height = len / width`. Calling `interleave(indices, stride)` with `stride = len / block_count` and `height = block_count` transposes the indices correctly. For `LoadInterleaved`, stride is `block_count`. The inverse operation (store) needs the transpose of load. These are matrix transposes of each other. The asymmetry in stride choice (`block_count` for load vs `len/block_count` for store) correctly implements the inverse. However, the function name `interleave_indices` is used for both operations, making the semantic difference invisible.","fix":"Add comments documenting the stride semantics for load vs store, to prevent future readers from incorrectly 'simplifying' the different strides.","locations":[{"ref":"fearless_simd_gen/src/mk_fallback.rs:469-495","code":" 469 | OpSig::LoadInterleaved {\n 470 | block_size,\n 471 | block_count,\n 472 | } => {\n 473 | let len = (block_size * block_count) as usize / vec_ty.scalar_bits;\n 474 | let items =\n 475 | interleave_indices(len, block_count as usize, |idx| quote! { src[#idx] });\n 476 | \n 477 | quote! {\n 478 | #method_sig {\n 479 | #items.simd_into(self)\n 480 | }\n 481 | }\n 482 | }\n 483 | OpSig::StoreInterleaved {\n 484 | block_size,\n 485 | block_count,\n 486 | } => {\n 487 | let len = (block_size * block_count) as usize / vec_ty.scalar_bits;\n 488 | let items =\n 489 | interleave_indices(len, len / block_count as usize, |idx| quote! { a[#idx] });\n 490 | \n 491 | quote! {\n 492 | #method_sig {\n 493 | *dest = #items;\n 494 | }\n 495 | }","lines":27},{"ref":"fearless_simd_gen/src/mk_fallback.rs:553-565","code":" 553 | fn interleave_indices(\n 554 | len: usize,\n 555 | stride: usize,\n 556 | func: impl FnMut(usize) -> TokenStream,\n 557 | ) -> TokenStream {\n 558 | let indices = {\n 559 | let indices = (0..len).collect::<Vec<_>>();\n 560 | interleave(&indices, stride)\n 561 | };\n 562 | \n 563 | make_list(indices.into_iter().map(func).collect::<Vec<_>>())\n 564 | }\n 565 | ","lines":13}]},{"id":"LOGIC_26","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"In `mk_neon.rs` `make_method` for `OpSig::Binary` with `method == 'shlv' | 'shrv'` for unsigned types, the code reinterprets `b` (the shift amounts) as signed via `vreinterpretq_s{bits}_u{bits}`. This is needed because ARM's `vshlq` takes signed shift amounts. For `shrv` (right shift), the shift amount is then negated via `vneg`. However, the `vneg` negation can overflow for a lane value of `INT_MIN` (e.g., `-128` for 8-bit, `-32768` for 16-bit). On ARM, `vneg` of `INT_MIN` is implementation-defined. For a typical right shift amount of 0-63, this would never be `INT_MIN`, but the code has no guard against it.","fix":"Add a comment noting that shift amounts are expected to be in range [0, scalar_bits), making INT_MIN impossible as a shift amount, or add a range assertion for debug builds.","locations":[{"ref":"fearless_simd_gen/src/mk_neon.rs:221-241","code":" 221 | let expr = match method {\n 222 | \"shlv\" | \"shrv\" => {\n 223 | let mut args = if vec_ty.scalar == ScalarType::Int {\n 224 | // Signed case\n 225 | [quote! { a.into() }, quote! { b.into() }]\n 226 | } else {\n 227 | // Unsigned case\n 228 | let bits = vec_ty.scalar_bits;\n 229 | let reinterpret = format_ident!(\"vreinterpretq_s{bits}_u{bits}\");\n 230 | [quote! { a.into() }, quote! { #reinterpret(b.into()) }]\n 231 | };\n 232 | \n 233 | // For a right shift, we need to negate the shift amount\n 234 | if method == \"shrv\" {\n 235 | let neg = simple_intrinsic(\"vneg\", &vec_ty.cast(ScalarType::Int));\n 236 | let arg1 = &args[1];\n 237 | args[1] = quote! { #neg(#arg1) };\n 238 | }\n 239 | \n 240 | let expr = neon::expr(method, vec_ty, &args);\n 241 | quote! {","lines":21}]},{"id":"LOGIC_27","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"In `mk_wasm.rs` `mask_from_bitmask` for 8-bit masks, the expression uses `i8x16_splat(bits as i8)` for the low byte and `i8x16_splat((bits >> 8) as i8)` for the high byte, then shuffles them into a 16-byte vector. The `bits >> 8` shifts the u64 right by 8, dropping the low 8 bits. The cast `as i8` truncates to 8 bits. This is correct for lanes 8-15. However, `bits as i8` truncates the u64 to 8 bits, keeping only the low 8 bits, which is the correct bits for lanes 0-7. The logic appears correct but relies on silent truncation via `as i8`. If `bits` is ever expected to carry lane bits beyond lane 15 (for a wider mask), this silently truncates them.","fix":"Add explicit masking before casting to make the intent clear: `(bits & 0xFF) as i8` and `((bits >> 8) & 0xFF) as i8`.","locations":[{"ref":"fearless_simd_gen/src/mk_wasm.rs:36-45","code":" 36 | let expr = match vec_ty.scalar_bits {\n 37 | 8 => quote! {\n 38 | let lo = i8x16_splat(bits as i8);\n 39 | let hi = i8x16_splat((bits >> 8) as i8);\n 40 | let bytes =\n 41 | u8x16_shuffle::<0, 0, 0, 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16, 16>(lo, hi);\n 42 | let powers = u8x16(1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128);\n 43 | let selected = v128_and(bytes, powers);\n 44 | i8x16_ne(selected, i8x16_splat(0)).simd_into(self)\n 45 | },","lines":10}]},{"id":"LOGIC_28","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"In `mk_wasm.rs` `make_method` for `OpSig::Ternary`, when method is not `mul_add` or `mul_sub`, the code calls `unimplemented!()`. This is fine since those are the only ternary ops currently. However, if a new ternary op is added to the ops list (for example for WASM-specific ops), the generator will panic at code-generation time for WASM rather than producing a graceful error. The same issue exists in `mk_wasm.rs:305`.","fix":"Add a comment listing which ternary ops are expected, or use `unreachable!()` with a message indicating which ops are unhandled.","locations":[{"ref":"fearless_simd_gen/src/mk_wasm.rs:304-306","code":" 304 | } else {\n 305 | unimplemented!()\n 306 | }","lines":3}]},{"id":"LOGIC_29","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"In `mk_x86.rs` `should_use_generic_op`, the specialization check for `MaskToBitmask` and `MaskFromBitmask` for wide masks only handles the cases defined in `has_specialized_mask_from_bitmask` / `has_specialized_mask_to_bitmask`. However, the logic is inverted: the function returns `!self.has_specialized_mask_from_bitmask(vec_ty)` when `should_use_generic` is true. This means: if there IS a specialization, don't use generic (return false). But if there is NO specialization and the generic path is requested, return true (use generic). This is correct in intent. However, `has_specialized_mask_to_bitmask` only returns true for `scalar_bits == 16`. For scalar_bits 32 and 64 wide masks, the non-wide path in `handle_mask_to_bitmask` uses `movemask`, which works on native-width vectors only. The wide (512-bit) case for scalar_bits 32/64 falls through the `should_use_generic_op` check with `true`, using generic split/combine. The generic split/combine for `MaskToBitmask` in `generic.rs` does work correctly, so this is fine -- but the AVX2-specific `mask_from_bitmask_wide_avx2` path for 512-bit 32/64 masks is intentionally bypassing generic to avoid the split shape. This interaction is correct but undocumented.","fix":"Add a comment explaining why `MaskToBitmask` for wide 32/64-bit masks uses the generic split path, while `MaskFromBitmask` has a specialized path.","locations":[{"ref":"fearless_simd_gen/src/mk_x86.rs:171-182","code":" 171 | fn should_use_generic_op(&self, op: &Op, vec_ty: &VecType) -> bool {\n 172 | let should_use_generic = op.sig.should_use_generic_op(vec_ty, self.native_width());\n 173 | if !should_use_generic {\n 174 | return false;\n 175 | }\n 176 | \n 177 | match op.sig {\n 178 | OpSig::MaskFromBitmask => !self.has_specialized_mask_from_bitmask(vec_ty),\n 179 | OpSig::MaskToBitmask => !self.has_specialized_mask_to_bitmask(vec_ty),\n 180 | _ => true,\n 181 | }\n 182 | }","lines":12}]},{"id":"LOGIC_30","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"In `mk_x86.rs` `mask_to_bitmask_words` for the `(256, 512)` case, the code uses `_pext_u32(lo, 0x5555_5555u32)` and `_pext_u32(hi, 0x5555_5555u32)`. The mask `0x5555_5555` selects every other bit (bits 0, 2, 4, ..., 30), extracting 16 bits total. For a 256-bit vector holding 16-bit masks, `_mm256_movemask_epi8` produces a 32-bit result where each pair of bits represents one 16-bit lane. `_pext` with `0x5555_5555` extracts only the even bits, dropping the redundant odd bits. The result is then shifted by 16 for the high half. This should yield a 32-bit bitmask correctly. However, `_pext_u32` is a BMI2 instruction, which is only available when BMI2 is enabled. The `Sse4_2` target features do not include `bmi2`, but this function can be called with `native_width = 128` (SSE4.2). Only the `256, 512` case with `native_width=128` uses `_pext_u32`, so this path should only be reached from SSE4.2 with a 512-bit mask16x32 type. If so, `_pext_u32` is UB/illegal on non-BMI2 hardware.","fix":"Either guard this case so it is only reachable on AVX2 (which includes BMI2), or replace `_pext_u32` with a portable bit-extraction sequence.","locations":[{"ref":"fearless_simd_gen/src/mk_x86.rs:524-535","code":" 524 | },\n 525 | (256, 512) => quote! {\n 526 | {\n 527 | let lo = _mm256_movemask_epi8(a.val.0[0]) as u32;\n 528 | let hi = _mm256_movemask_epi8(a.val.0[1]) as u32;\n 529 | let lo = _pext_u32(lo, 0x5555_5555u32) as u64;\n 530 | let hi = _pext_u32(hi, 0x5555_5555u32) as u64;\n 531 | lo | (hi << 16usize)\n 532 | }\n 533 | },\n 534 | _ => unimplemented!(),\n 535 | }","lines":12}]},{"id":"LOGIC_31","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"In `mk_x86.rs` `handle_mask_reduce` for 8-bit and 16-bit masks, the `movemask` is applied to a reinterpretation of the mask as `Int` with 8-bit lanes (`bits_ty = vec_ty.reinterpret(ScalarType::Int, 8)`). For an 8-bit mask, this is `i8xN` which is correct. For a 16-bit mask, this reinterprets the 16-bit lane values as bytes, meaning each 16-bit lane (which is either 0x0000 or 0xFFFF) becomes two bytes. `_mm_movemask_epi8` then produces 2 bits per logical 16-bit lane. The `all_ones` constant for a 128-bit 16-bit mask is `0xffff` (16 bits), but `movemask_epi8` on 16 bytes produces a 16-bit result where each pair of bits represents one 16-bit lane. So `all_ones == 0xffff` checks all 16 byte-bits, which requires all bytes (including both bytes of each 16-bit lane) to have their MSB set. For canonical masks (0x0000 or 0xFFFF), both bytes have the same MSB, so this should be correct. But it relies on canonical mask values.","fix":"Add a comment documenting the assumption that masks are canonical (0x0000 or 0xFFFF) for this logic to be correct.","locations":[{"ref":"fearless_simd_gen/src/mk_x86.rs:1889-1899","code":" 1889 | 8 | 16 => {\n 1890 | let bits_ty = vec_ty.reinterpret(ScalarType::Int, 8);\n 1891 | let movemask = simple_intrinsic(\"movemask\", &bits_ty);\n 1892 | let movemask = quote! { #movemask(a.into()) };\n 1893 | let all_ones = match vec_ty.n_bits() {\n 1894 | 128 => quote! { 0xffff },\n 1895 | 256 => quote! { 0xffffffff },\n 1896 | _ => unimplemented!(),\n 1897 | };\n 1898 | \n 1899 | (movemask, all_ones)","lines":11}]},{"id":"LOGIC_32","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"In `ops.rs` `ops_for_type`, when `ty.scalar == ScalarType::Float && ty.scalar_bits == 64`, the function returns early with `return ops;` before the `load_interleaved_128`/`store_interleaved_128`, `widen`/`narrow`, `reinterpret_u8`/`reinterpret_u32` and conversion ops are pushed. This means f64 vectors skip those additions correctly, but the early `return` also makes it impossible to later add operations specifically for f64 without restructuring the code. The early return is a footgun for maintenance.","fix":"Instead of early return, use a guard condition or restructure so that f64-specific logic is handled inline without an early return.","locations":[{"ref":"fearless_simd_gen/src/ops.rs:1308-1311","code":" 1308 | if ty.scalar_bits == 64 {\n 1309 | return ops;\n 1310 | }\n 1311 | }","lines":4}]},{"id":"LOGIC_33","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"In `ops.rs`, `valid_reinterpret` only checks if the source and destination type are the same (returns false) and if the source is a mask type (returns false). It does NOT check whether the total bit width would be preserved, or whether the destination scalar bit size evenly divides the source bit width. For example, reinterpreting a 128-bit vector with 3-bit scalars (hypothetical) would produce a fractional number of lanes, but the function would return `true`. This is a correctness gap in the validator that could produce incorrect code if new scalar sizes are added.","fix":"Add a check that `src.n_bits() % dst_bits == 0` to ensure the reinterpret produces a whole number of lanes.","locations":[{"ref":"fearless_simd_gen/src/ops.rs:1670-1680","code":" 1670 | pub(crate) fn valid_reinterpret(src: &VecType, dst_scalar: ScalarType, dst_bits: usize) -> bool {\n 1671 | if src.scalar == dst_scalar && src.scalar_bits == dst_bits {\n 1672 | return false;\n 1673 | }\n 1674 | \n 1675 | if matches!(src.scalar, ScalarType::Mask) {\n 1676 | return false;\n 1677 | }\n 1678 | \n 1679 | true\n 1680 | }","lines":11}]},{"id":"LOGIC_34","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":true,"description":"The `cvt_u32_precise_f32x4_inf` test at line 301-305 asserts that `f32::NEG_INFINITY` converts to `u32::MIN` (which is 0). The more natural saturating result for a negative infinity to unsigned conversion is 0 (i.e. `u32::MIN`), so this is likely correct. However, the test name says `_inf` and the assertion for `f32::INFINITY` correctly expects `u32::MAX`, but `u32::MIN` is a magic constant (0) not spelled out in the assertion. A reader may not immediately know this is correct without checking the constant.","fix":"Replace `u32::MIN` with the explicit literal `0` in the assertion to make the expected value self-evident, or add a comment clarifying the saturation semantics.","locations":[{"ref":"fearless_simd_tests/tests/harness/mod.rs:301-305","code":" 301 | fn cvt_u32_precise_f32x4_inf<S: Simd>(simd: S) {\n 302 | let a = f32x4::from_slice(simd, &[-10.3, f32::NAN, f32::INFINITY, f32::NEG_INFINITY]);\n 303 | \n 304 | assert_eq!(*a.to_int_precise::<u32x4<_>>(), [0, 0, u32::MAX, u32::MIN]);\n 305 | }","lines":5}]},{"id":"LOGIC_35","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":true,"description":"The `slide_u32x4` test tests only shifts 0, 2, and 4, entirely skipping shifts 1 and 3 for a 4-element vector. The corresponding `slide_i32x4` test does test shifts 0, 1, 2, 4 (also missing 3). Symmetrical types should have symmetric coverage.","fix":"Add assertions for shifts 1 and 3 in `slide_u32x4` and shift 3 in `slide_i32x4`.","locations":[{"ref":"fearless_simd_tests/tests/harness/mod.rs:3385-3391","code":" 3385 | let b = f32x4::from_slice(simd, &[-8.1, 7.9, -9.8, 3243.6]);\n 3386 | \n 3387 | assert_eq!(*(a * b), [83.43001, 0.0, -130.73201, 759761400.0]);\n 3388 | }\n 3389 | \n 3390 | #[simd_test]\n 3391 | fn simd_eq_u8x16<S: Simd>(simd: S) {","lines":7}]},{"id":"LOGIC_36","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":true,"description":"The `slide_f32x4` test at line 3799 skips the shift value 3: it tests shifts 0, 1, 2, and 4 but omits 3. This is an incomplete coverage gap for a 4-element vector where shift=3 is a valid and distinct case.","fix":"Add `assert_eq!(*a.slide::<3>(b), [4.0, 5.0, 6.0, 7.0]);` between the shift-2 and shift-4 assertions.","locations":[{"ref":"fearless_simd_tests/tests/harness/mod.rs:3793-3800","code":" 3793 | fn slide_f32x4<S: Simd>(simd: S) {\n 3794 | let a = f32x4::from_slice(simd, &[1.0, 2.0, 3.0, 4.0]);\n 3795 | let b = f32x4::from_slice(simd, &[5.0, 6.0, 7.0, 8.0]);\n 3796 | assert_eq!(*a.slide::<0>(b), [1.0, 2.0, 3.0, 4.0]);\n 3797 | assert_eq!(*a.slide::<1>(b), [2.0, 3.0, 4.0, 5.0]);\n 3798 | assert_eq!(*a.slide::<2>(b), [3.0, 4.0, 5.0, 6.0]);\n 3799 | assert_eq!(*a.slide::<4>(b), [5.0, 6.0, 7.0, 8.0]);\n 3800 | }","lines":8}]},{"id":"LOGIC_37","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"The slide_exhaustive harness uses only two fixed constant test vectors (values 1..N for `a` and N+1..2N for `b`). This is a narrow coverage set: many shift amounts will always read from specific, predictable positions. A buggy implementation that only handles the \"all distinct ascending\" case would pass. The comment `core::hint::black_box` inside the macro is placed on the array construction, not on the slide call itself, so a smart optimiser could still constant-fold the slide result.","fix":"Generate at least one additional pair of test vectors (e.g. alternating or random values) to reduce the chance that a misimplemented slide passes due to the uniform structure of the input.","locations":[{"ref":"fearless_simd_tests/tests/harness/slide_exhaustive.rs:203-223","code":" 203 | macro_rules! test_slide_exhaustive {\n 204 | ($test_name:ident, $vec_ty:ident, $elem_ty:ty, $n_elems:literal, $vec_n:ident, $block_n:ident) => {\n 205 | #[simd_test]\n 206 | fn $test_name<S: Simd>(simd: S) {\n 207 | let fallback = fearless_simd::Fallback::new();\n 208 | \n 209 | let vals_a: [$elem_ty; $n_elems] = core::hint::black_box(core::array::from_fn(|i| (i + 1) as $elem_ty));\n 210 | let vals_b: [$elem_ty; $n_elems] = core::hint::black_box(core::array::from_fn(|i| (i + 1 + $n_elems) as $elem_ty));\n 211 | \n 212 | let test_vec_a = $vec_ty::from_slice(simd, &vals_a);\n 213 | let test_vec_b = $vec_ty::from_slice(simd, &vals_b);\n 214 | let fallback_vec_a = <$vec_ty::<fearless_simd::Fallback>>::from_slice(fallback, &vals_a);\n 215 | let fallback_vec_b = <$vec_ty::<fearless_simd::Fallback>>::from_slice(fallback, &vals_b);\n 216 | \n 217 | // Test vector-wide operations\n 218 | test_slide_impl!(@$vec_n test_vec_a, test_vec_b, fallback_vec_a, fallback_vec_b, $vec_ty);\n 219 | // Test within-block operations\n 220 | test_slide_impl!(@$block_n test_vec_a, test_vec_b, fallback_vec_a, fallback_vec_b, $vec_ty);\n 221 | }\n 222 | };\n 223 | }","lines":21}]},{"id":"LOGIC_38","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"The exhaustive f32 conversion tests (`test_f32_to_i32_precise_exhaustive` etc.) use `step_by(4)` to advance by 4 bit-pattern values per iteration and construct 4-element SIMD vectors starting at that base. This means the tested patterns are `i`, `i+1`, `i+2`, `i+3` for each step. However, because `step_by(4)` starts at 0 and the range is `0..u32::MAX`, the last value in each group can wrap or be skipped at the end of the range, potentially missing the last few bit patterns before `u32::MAX`.","fix":"Verify that the final few bit patterns are not skipped. Consider using `(0..=u32::MAX).step_by(4)` or adding an explicit test for the last 4 values near `u32::MAX`.","locations":[{"ref":"fearless_simd_tests/tests/mod.rs:76-87","code":" 76 | for i in (0..u32::MAX).step_by(4) {\n 77 | let floats = f32x4::from_fn(simd, |n| f32::from_bits(n as u32 + i));\n 78 | let ints = floats.to_int_precise::<i32x4<_>>();\n 79 | let ints_ref = (*floats).map(|f| f as i32);\n 80 | assert_eq!(\n 81 | *ints, ints_ref,\n 82 | \"f32x4::to_int_precise::<i32x4<_>>() returns the same results as Rust's `as i32`\"\n 83 | );\n 84 | }\n 85 | },\n 86 | );\n 87 | }","lines":12},{"ref":"fearless_simd_tests/tests/mod.rs:98-111","code":" 98 | #[inline(always)]\n 99 | || {\n 100 | for i in (0..u32::MAX).step_by(4) {\n 101 | let floats = f32x4::from_fn(simd, |n| f32::from_bits(n as u32 + i));\n 102 | let ints = floats.to_int_precise::<u32x4<_>>();\n 103 | let ints_ref = (*floats).map(|f| f as u32);\n 104 | assert_eq!(\n 105 | *ints, ints_ref,\n 106 | \"f32x4::to_int_precise::<u32x4<_>>() returns the same results as Rust's `as u32`\"\n 107 | );\n 108 | }\n 109 | },\n 110 | );\n 111 | }","lines":14}]},{"id":"LOGIC_39","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":true,"description":"In the exhaustive f32->u32 and f32->i32 tests (`test_f32_to_u32_exhaustive`, `test_f32_to_i32_exhaustive`), the in-range guard checks `*val > T::MIN as f32` (strictly greater) for both integer types. For `u32`, `u32::MIN as f32 == 0.0` and for `i32`, `i32::MIN as f32 == -2147483648.0`. Floats that round exactly to the boundary value (e.g. 0.0 for u32, or -2147483648.0 for i32) are excluded from testing even though `as` cast is well-defined for those values. The guard should use `>=` for the lower bound.","fix":"Change `*val > u32::MIN as f32` to `*val >= u32::MIN as f32` (and similarly for i32) so that exact boundary floats are also tested.","locations":[{"ref":"fearless_simd_tests/tests/mod.rs:128","code":" 128 | if ! (*floats).iter().all(|val| !val.is_nan() && *val > u32::MIN as f32 && *val < u32::MAX as f32) {","lines":1},{"ref":"fearless_simd_tests/tests/mod.rs:157-159","code":" 157 | if !(*floats)\n 158 | .iter()\n 159 | .all(|val| !val.is_nan() && *val > i32::MIN as f32 && *val < i32::MAX as f32)","lines":3}]},{"id":"LOGIC_40","type":"logic_error","type_label":"Logic Error","severity":"medium","easy":false,"description":"The `assert_panics` helper in `soundness.rs` is a no-op when compiled with `panic = \"abort\"` (line 20-23). On targets where abort-on-panic is the default (e.g. wasm32-wasip1 as noted in the comment), all soundness checks that use `assert_panics` silently succeed without actually verifying that out-of-bounds accesses are rejected. The test names (`from_slice_rejects_short_slice`, etc.) imply the rejection is being verified but it is not.","fix":"Add a `#[cfg(panic = \"abort\")]` `compile_error!` or mark the soundness tests with `#[cfg(panic = \"unwind\")]` so they are excluded rather than silently skipped on abort targets. Alternatively, add a `todo!(\"soundness tests require panic = unwind\")` in the no-op path so the tests at least fail informatively.","locations":[{"ref":"fearless_simd_tests/tests/soundness.rs:19-23","code":" 19 | #[cfg(not(panic = \"unwind\"))]\n 20 | fn assert_panics(_label: &str, _f: impl FnOnce()) {\n 21 | // These tests need panic unwinding to observe rejected operations. Some targets, such as\n 22 | // wasm32-wasip1, abort on panic instead.\n 23 | }","lines":5},{"ref":"fearless_simd_tests/tests/soundness.rs:122-139","code":" 122 | #[simd_test]\n 123 | fn from_slice_rejects_short_slice<S: Simd>(simd: S) {\n 124 | for_each_simd_type!(check_from_slice_short, simd);\n 125 | }\n 126 | \n 127 | #[simd_test]\n 128 | fn store_slice_rejects_short_slice<S: Simd>(simd: S) {\n 129 | for_each_simd_type!(check_store_slice_short, simd);\n 130 | }\n 131 | \n 132 | #[simd_test]\n 133 | fn mask_test_rejects_out_of_bounds<S: Simd>(simd: S) {\n 134 | for_each_mask_type!(check_mask_test_oob, simd);\n 135 | }\n 136 | \n 137 | #[simd_test]\n 138 | fn mask_set_rejects_out_of_bounds<S: Simd>(simd: S) {\n 139 | for_each_mask_type!(check_mask_set_oob, simd);","lines":18}]},{"id":"LOGIC_41","type":"logic_error","type_label":"Logic Error","severity":"low","easy":false,"description":"The `srgb.rs` example's `copy_alpha` function has an unreachable fallback path on platforms where both the `#[cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))]` and `#[cfg(target_arch = \"aarch64\")]` branches are active. On wasm32 or other platforms, only the scalar fallback runs. However, the `kernel!` macro definitions for `copy_alpha_neon` and `copy_alpha_sse4_2` are still compiled unconditionally (they are declared outside any `cfg`). The `#[cfg]` guards on the kernel functions themselves will silently drop the body on non-matching platforms, so the `kernel!` macro must handle this correctly. This is a latent confusion point if the macro does not gate compilation of the body.","fix":"Wrap the `kernel!` definitions in the appropriate `#[cfg(target_arch = ...)]` gates to make it clear they are arch-specific, matching the call-site guards in `copy_alpha`.","locations":[{"ref":"fearless_simd/examples/srgb.rs:28-42","code":" 28 | fearless_simd::kernel!(\n 29 | /// Copy the alpha lane on AArch64 using a NEON lane-copy intrinsic.\n 30 | #[inline]\n 31 | fn copy_alpha_neon(neon: Neon, a: float32x4_t, b: float32x4_t) -> float32x4_t {\n 32 | vcopyq_laneq_f32::<3, 3>(a, b)\n 33 | }\n 34 | );\n 35 | \n 36 | fearless_simd::kernel!(\n 37 | /// Copy the alpha lane on x86 using the SSE4.2 token to enable SSE4.1 blend instructions.\n 38 | #[inline]\n 39 | fn copy_alpha_sse4_2(sse4_2: Sse4_2, a: __m128, b: __m128) -> __m128 {\n 40 | _mm_blend_ps::<8>(a, b)\n 41 | }\n 42 | );","lines":15},{"ref":"fearless_simd/examples/srgb.rs:50-62","code":" 50 | #[cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))]\n 51 | if let Some(sse4_2) = a.simd.level().as_sse4_2() {\n 52 | return copy_alpha_sse4_2(sse4_2, a.into(), b.into()).simd_into(a.simd);\n 53 | }\n 54 | \n 55 | #[cfg(target_arch = \"aarch64\")]\n 56 | if let Some(neon) = a.simd.level().as_neon() {\n 57 | return copy_alpha_neon(neon, a.into(), b.into()).simd_into(a.simd);\n 58 | }\n 59 | \n 60 | let mut result = a;\n 61 | result[3] = b[3];\n 62 | result","lines":13}]},{"id":"LOGIC_42","type":"logic_error","type_label":"Logic Error","severity":"low","easy":false,"description":"`shlv_i8x16`, `shlv_i16x8`, `shlv_i32x4`, and their unsigned/wider counterparts all\naccept the shift amount as a vector of the same element type (e.g. `i8x16` for shifts\nin `shlv_i8x16`). For the signed types this means the shift amount can be negative.\nEven ignoring the debug panic described in the shlv/shrv finding above, the behavior\nwith in-range but logically-wrong values (e.g. a shift amount that is semantically\nunsigned but stored as a signed i8 with value > 127 interpreted as negative) is\nundefined. Most hardware ISAs define variable shifts in terms of an unsigned count\nmasked to the bit width. The fallback does not perform any masking or sign check, so\nthere is no defined contract for what happens with shift amounts >= bit_width or negative\nin the Simd trait.","fix":"Document in the Simd trait that variable shift amounts for signed integer vector types must be in [0, bit_width), or change the API to accept unsigned shift vectors. The fallback implementation should at minimum cast to u32 before shifting.","locations":[{"ref":"fearless_simd/src/generated/fallback.rs:790-810","code":" 790 | fn shlv_i8x16(self, a: i8x16<Self>, b: i8x16<Self>) -> i8x16<Self> {\n 791 | [\n 792 | i8::shl(a[0usize], &b[0usize]),\n 793 | i8::shl(a[1usize], &b[1usize]),\n 794 | i8::shl(a[2usize], &b[2usize]),\n 795 | i8::shl(a[3usize], &b[3usize]),\n 796 | i8::shl(a[4usize], &b[4usize]),\n 797 | i8::shl(a[5usize], &b[5usize]),\n 798 | i8::shl(a[6usize], &b[6usize]),\n 799 | i8::shl(a[7usize], &b[7usize]),\n 800 | i8::shl(a[8usize], &b[8usize]),\n 801 | i8::shl(a[9usize], &b[9usize]),\n 802 | i8::shl(a[10usize], &b[10usize]),\n 803 | i8::shl(a[11usize], &b[11usize]),\n 804 | i8::shl(a[12usize], &b[12usize]),\n 805 | i8::shl(a[13usize], &b[13usize]),\n 806 | i8::shl(a[14usize], &b[14usize]),\n 807 | i8::shl(a[15usize], &b[15usize]),\n 808 | ]\n 809 | .simd_into(self)\n 810 | }","lines":21},{"ref":"fearless_simd/src/generated/fallback.rs:834-854","code":" 834 | fn shrv_i8x16(self, a: i8x16<Self>, b: i8x16<Self>) -> i8x16<Self> {\n 835 | [\n 836 | i8::shr(a[0usize], &b[0usize]),\n 837 | i8::shr(a[1usize], &b[1usize]),\n 838 | i8::shr(a[2usize], &b[2usize]),\n 839 | i8::shr(a[3usize], &b[3usize]),\n 840 | i8::shr(a[4usize], &b[4usize]),\n 841 | i8::shr(a[5usize], &b[5usize]),\n 842 | i8::shr(a[6usize], &b[6usize]),\n 843 | i8::shr(a[7usize], &b[7usize]),\n 844 | i8::shr(a[8usize], &b[8usize]),\n 845 | i8::shr(a[9usize], &b[9usize]),\n 846 | i8::shr(a[10usize], &b[10usize]),\n 847 | i8::shr(a[11usize], &b[11usize]),\n 848 | i8::shr(a[12usize], &b[12usize]),\n 849 | i8::shr(a[13usize], &b[13usize]),\n 850 | i8::shr(a[14usize], &b[14usize]),\n 851 | i8::shr(a[15usize], &b[15usize]),\n 852 | ]\n 853 | .simd_into(self)\n 854 | }","lines":21},{"ref":"fearless_simd/src/generated/fallback.rs:2291-2303","code":" 2291 | fn shlv_i16x8(self, a: i16x8<Self>, b: i16x8<Self>) -> i16x8<Self> {\n 2292 | [\n 2293 | i16::shl(a[0usize], &b[0usize]),\n 2294 | i16::shl(a[1usize], &b[1usize]),\n 2295 | i16::shl(a[2usize], &b[2usize]),\n 2296 | i16::shl(a[3usize], &b[3usize]),\n 2297 | i16::shl(a[4usize], &b[4usize]),\n 2298 | i16::shl(a[5usize], &b[5usize]),\n 2299 | i16::shl(a[6usize], &b[6usize]),\n 2300 | i16::shl(a[7usize], &b[7usize]),\n 2301 | ]\n 2302 | .simd_into(self)\n 2303 | }","lines":13},{"ref":"fearless_simd/src/generated/fallback.rs:2317-2335","code":" 2317 | }\n 2318 | #[inline(always)]\n 2319 | fn shrv_i16x8(self, a: i16x8<Self>, b: i16x8<Self>) -> i16x8<Self> {\n 2320 | [\n 2321 | i16::shr(a[0usize], &b[0usize]),\n 2322 | i16::shr(a[1usize], &b[1usize]),\n 2323 | i16::shr(a[2usize], &b[2usize]),\n 2324 | i16::shr(a[3usize], &b[3usize]),\n 2325 | i16::shr(a[4usize], &b[4usize]),\n 2326 | i16::shr(a[5usize], &b[5usize]),\n 2327 | i16::shr(a[6usize], &b[6usize]),\n 2328 | i16::shr(a[7usize], &b[7usize]),\n 2329 | ]\n 2330 | .simd_into(self)\n 2331 | }\n 2332 | #[inline(always)]\n 2333 | fn simd_eq_i16x8(self, a: i16x8<Self>, b: i16x8<Self>) -> mask16x8<Self> {\n 2334 | [\n 2335 | -(i16::eq(&a[0usize], &b[0usize]) as i16),","lines":19},{"ref":"fearless_simd/src/generated/fallback.rs:3304-3312","code":" 3304 | fn shlv_i32x4(self, a: i32x4<Self>, b: i32x4<Self>) -> i32x4<Self> {\n 3305 | [\n 3306 | i32::shl(a[0usize], &b[0usize]),\n 3307 | i32::shl(a[1usize], &b[1usize]),\n 3308 | i32::shl(a[2usize], &b[2usize]),\n 3309 | i32::shl(a[3usize], &b[3usize]),\n 3310 | ]\n 3311 | .simd_into(self)\n 3312 | }","lines":9},{"ref":"fearless_simd/src/generated/fallback.rs:3326-3334","code":" 3326 | i32::shr(a[0usize], &b[0usize]),\n 3327 | i32::shr(a[1usize], &b[1usize]),\n 3328 | i32::shr(a[2usize], &b[2usize]),\n 3329 | i32::shr(a[3usize], &b[3usize]),\n 3330 | ]\n 3331 | .simd_into(self)\n 3332 | }\n 3333 | #[inline(always)]\n 3334 | fn simd_eq_i32x4(self, a: i32x4<Self>, b: i32x4<Self>) -> mask32x4<Self> {","lines":9}]},{"id":"LOGIC_43","type":"logic_error","type_label":"Logic Error","severity":"low","easy":false,"description":"The shift-count semantics for out-of-range shifts differ across backends for 8-bit element\ntypes, creating an observable inconsistency:\n\n- NEON shl_i8x16/shl_u8x16: shift counts >= element_size give 0 (NEON saturates shifts).\n- SSE4.2/AVX2 shl_i8x16/shl_u8x16: shift >= 16 gives 0 via _mm_sll_epi16, but shifts\n in [8,15] produce a non-zero shifted 16-bit value that saturates in the pack step.\n- WASM i8x16_shl/u8x16_shl: shift is taken modulo 8, so shift=8 is treated as shift=0.\n\nFor example, shl_i8x16(any_non_zero, 8):\n- NEON returns 0.\n- SSE4.2 returns a saturated result (non-zero for values with high bit or abs value).\n- WASM returns the original value (shift 0).\n\nWhile the API may not guarantee behavior for shift >= element_size, the inconsistency\ncan silently produce different results across platforms for borderline shift amounts.","fix":"Document the shift-amount contract explicitly (e.g., shift must be in [0, element_bits-1]). Alternatively, add clamp/mask logic to match one behavior across all backends.","locations":[{"ref":"fearless_simd/src/generated/neon.rs:447","code":" 447 | unsafe { vshlq_s8(a.into(), vdupq_n_s8(shift as i8)).simd_into(self) }","lines":1},{"ref":"fearless_simd/src/generated/sse4_2.rs:539-548","code":" 539 | fn shl_i8x16(self, a: i8x16<Self>, shift: u32) -> i8x16<Self> {\n 540 | unsafe {\n 541 | let val = a.into();\n 542 | let shift_count = _mm_cvtsi32_si128(shift.cast_signed());\n 543 | let lo_16 = _mm_unpacklo_epi8(val, _mm_cmpgt_epi8(_mm_setzero_si128(), val));\n 544 | let hi_16 = _mm_unpackhi_epi8(val, _mm_cmpgt_epi8(_mm_setzero_si128(), val));\n 545 | let lo_shifted = _mm_sll_epi16(lo_16, shift_count);\n 546 | let hi_shifted = _mm_sll_epi16(hi_16, shift_count);\n 547 | _mm_packs_epi16(lo_shifted, hi_shifted).simd_into(self)\n 548 | }","lines":10},{"ref":"fearless_simd/src/generated/wasm.rs:489-491","code":" 489 | fn shl_i8x16(self, a: i8x16<Self>, shift: u32) -> i8x16<Self> {\n 490 | i8x16_shl(a.into(), shift).simd_into(self)\n 491 | }","lines":3}]},{"id":"LOGIC_44","type":"logic_error","type_label":"Logic Error","severity":"low","easy":false,"description":"The Sse4_2 level() method contains dead code. In the cfg block at lines 88-115, the\nfunction has two paths: a #[cfg(not(avx2+features))] path returning Level::Sse4_2(self),\nand a #[cfg(all(avx2+features))] block returning Level::baseline(). When the full AVX2\nfeature set is present at compile time, an Sse4_2 token that was runtime-detected returns\nLevel::baseline() from level(). This may confuse callers who hold a bona fide Sse4_2 token\nbut see Level::baseline() returned. The Level::baseline() return value changes the dispatch\nbehavior, potentially eliding the SSE4.2 path. Whether this is intentional or not, the\ninteraction is non-obvious.","fix":"Clarify in a comment whether this cfg-dispatch in level() is intentional, or always return Level::Sse4_2(self) to reflect the actual token type.","locations":[{"ref":"fearless_simd/src/generated/sse4_2.rs:88-115","code":" 88 | #[cfg(not(all(\n 89 | target_feature = \"avx2\",\n 90 | target_feature = \"bmi1\",\n 91 | target_feature = \"bmi2\",\n 92 | target_feature = \"cmpxchg16b\",\n 93 | target_feature = \"f16c\",\n 94 | target_feature = \"fma\",\n 95 | target_feature = \"lzcnt\",\n 96 | target_feature = \"movbe\",\n 97 | target_feature = \"popcnt\",\n 98 | target_feature = \"xsave\"\n 99 | )))]\n 100 | return Level::Sse4_2(self);\n 101 | #[cfg(all(\n 102 | target_feature = \"avx2\",\n 103 | target_feature = \"bmi1\",\n 104 | target_feature = \"bmi2\",\n 105 | target_feature = \"cmpxchg16b\",\n 106 | target_feature = \"f16c\",\n 107 | target_feature = \"fma\",\n 108 | target_feature = \"lzcnt\",\n 109 | target_feature = \"movbe\",\n 110 | target_feature = \"popcnt\",\n 111 | target_feature = \"xsave\"\n 112 | ))]\n 113 | {\n 114 | Level::baseline()\n 115 | }","lines":28}]},{"id":"LOGIC_45","type":"logic_error","type_label":"Logic Error","severity":"low","easy":false,"description":"In `mk_fallback.rs` `interleave` and `deinterleave` for the fallback, the implementation delegates to `zip_low`/`zip_high` and `unzip_low`/`unzip_high` respectively. For 128-bit vectors this is efficient. But the fallback is also used for 256-bit and 512-bit vectors where `should_use_generic_op` would return `true`, so those larger vectors go through the generic split/combine path instead. This means for 128-bit vectors, the interleave/deinterleave is: zip_low + zip_high = 2*N^2 operations for N-element vectors, while the SIMD-style scalar fallback could be done with a single N^2 pass. This is only a performance issue in the generator itself (the generator runs once), not in the generated code.","fix":"No correctness issue; consider whether generating a more direct scalar interleave would be worth the complexity.","locations":[{"ref":"fearless_simd_gen/src/mk_fallback.rs:527-543","code":" 527 | OpSig::Interleave => {\n 528 | let zip_low = generic_op_name(\"zip_low\", vec_ty);\n 529 | let zip_high = generic_op_name(\"zip_high\", vec_ty);\n 530 | quote! {\n 531 | #method_sig {\n 532 | (self.#zip_low(a, b), self.#zip_high(a, b))\n 533 | }\n 534 | }\n 535 | }\n 536 | OpSig::Deinterleave => {\n 537 | let unzip_low = generic_op_name(\"unzip_low\", vec_ty);\n 538 | let unzip_high = generic_op_name(\"unzip_high\", vec_ty);\n 539 | quote! {\n 540 | #method_sig {\n 541 | (self.#unzip_low(a, b), self.#unzip_high(a, b))\n 542 | }\n 543 | }","lines":17}]},{"id":"LOGIC_46","type":"logic_error","type_label":"Logic Error","severity":"low","easy":true,"description":"In `mk_simd_types.rs`, the `simd_mask_impl` function calls `op.vec_trait_method_sig()` and then calls `.expect(...)` on the forwarding_call_args, asserting that the method can be forwarded. However, for `OpSig::Compare` the mask branch overrides the sig to return `Some(quote!{fn simd_eq...})`, but then calls `sig.forwarding_call_args()` for the original sig which might be `OpSig::Compare`. Looking at `forwarding_call_args` for `OpSig::Compare`, it returns `Some(args)`, so this is fine. But the assertion message 'this method can be forwarded to a specific Simd function' would be confusing if it fires for an unrelated op. This is a latent issue if a new non-forwardable op is added to the mask ops list.","fix":"Change the `expect` message to name the specific operation that failed, or check explicitly and emit a better error.","locations":[{"ref":"fearless_simd_gen/src/mk_simd_types.rs:313-316","code":" 313 | let call_args = sig\n 314 | .forwarding_call_args()\n 315 | .expect(\"this method can be forwarded to a specific Simd function\");\n 316 | methods.push(quote! {","lines":4}]},{"id":"LOGIC_47","type":"logic_error","type_label":"Logic Error","severity":"low","easy":true,"description":"In `mk_x86.rs` `Sse4_2::make_level_body`, the generated code has an `#[cfg(all(feature = 'avx2', ...))]` block that returns `Level::baseline()` when AVX2 features are detected at compile time. This contradicts the typical use case: if AVX2 is statically available, the user should get an Avx2 token, not a baseline. The intent appears to be 'if AVX2 is statically compiled in, the Sse4_2 token is not the highest level, and the dispatch machinery should have picked Avx2 already, so return baseline as a fallback indicator'. However, this is surprising behavior -- returning `baseline()` from a Sse4_2 token when AVX2 is statically available seems wrong. If AVX2 is statically compiled, the Sse4_2 level is still valid and should report itself.","fix":"Review whether `Level::baseline()` is the correct return in the static-AVX2 case. If the intent is that Sse4_2::level() always returns the highest available level, then the baseline return should probably be an Avx2 return. Document the intent clearly.","locations":[{"ref":"fearless_simd_gen/src/mk_x86.rs:104-138","code":" 104 | match self {\n 105 | Self::Sse4_2 => quote! {\n 106 | #[cfg(not(all(\n 107 | target_feature = \"avx2\",\n 108 | target_feature = \"bmi1\",\n 109 | target_feature = \"bmi2\",\n 110 | target_feature = \"cmpxchg16b\",\n 111 | target_feature = \"f16c\",\n 112 | target_feature = \"fma\",\n 113 | target_feature = \"lzcnt\",\n 114 | target_feature = \"movbe\",\n 115 | target_feature = \"popcnt\",\n 116 | target_feature = \"xsave\"\n 117 | )))]\n 118 | return Level::#level_tok(self);\n 119 | #[cfg(all(\n 120 | target_feature = \"avx2\",\n 121 | target_feature = \"bmi1\",\n 122 | target_feature = \"bmi2\",\n 123 | target_feature = \"cmpxchg16b\",\n 124 | target_feature = \"f16c\",\n 125 | target_feature = \"fma\",\n 126 | target_feature = \"lzcnt\",\n 127 | target_feature = \"movbe\",\n 128 | target_feature = \"popcnt\",\n 129 | target_feature = \"xsave\"\n 130 | ))]\n 131 | {\n 132 | Level::baseline()\n 133 | }\n 134 | },\n 135 | Self::Avx2 => quote! {\n 136 | Level::#level_tok(self)\n 137 | },\n 138 | }","lines":35}]},{"id":"LOGIC_48","type":"logic_error","type_label":"Logic Error","severity":"low","easy":false,"description":"The mask bitmask roundtrip tests in `mask_methods.rs` iterate over `0..=0xffff` (65536 iterations each). For mask types with fewer than 16 elements, the upper bits are masked off before comparison, so many iterations are redundant duplicates. For example `mask64x2` has only 2 relevant bits, so 65536 iterations test only 4 distinct inputs repeatedly. The test wastes time without adding coverage.","fix":"Iterate only over the relevant bit range for each mask type (e.g. `0..=(1u64 << N)` where N is the number of lanes) or use a power-of-two stride to reduce redundancy.","locations":[{"ref":"fearless_simd_tests/tests/harness/lm_generated/mask_methods.rs:9-76","code":" 9 | for bits in 0..=0xffff_u64 {\n 10 | let mask = mask8x16::from_bitmask(simd, bits);\n 11 | assert_eq!(mask.to_bitmask(), bits);\n 12 | }\n 13 | }\n 14 | \n 15 | #[simd_test]\n 16 | fn mask16x8_bitmask_roundtrip<S: Simd>(simd: S) {\n 17 | for bits in 0..=0xffff_u64 {\n 18 | let mask = mask16x8::from_bitmask(simd, bits);\n 19 | assert_eq!(mask.to_bitmask(), bits & 0xff);\n 20 | }\n 21 | }\n 22 | \n 23 | #[simd_test]\n 24 | fn mask32x4_bitmask_roundtrip<S: Simd>(simd: S) {\n 25 | for bits in 0..=0xffff_u64 {\n 26 | let mask = mask32x4::from_bitmask(simd, bits);\n 27 | assert_eq!(mask.to_bitmask(), bits & 0xf);\n 28 | }\n 29 | }\n 30 | \n 31 | #[simd_test]\n 32 | fn mask64x2_bitmask_roundtrip<S: Simd>(simd: S) {\n 33 | for bits in 0..=0xffff_u64 {\n 34 | let mask = mask64x2::from_bitmask(simd, bits);\n 35 | assert_eq!(mask.to_bitmask(), bits & 0x3);\n 36 | }\n 37 | }\n 38 | \n 39 | #[simd_test]\n 40 | fn mask16x16_bitmask_roundtrip<S: Simd>(simd: S) {\n 41 | for bits in 0..=0xffff_u64 {\n 42 | let mask = mask16x16::from_bitmask(simd, bits);\n 43 | assert_eq!(mask.to_bitmask(), bits);\n 44 | }\n 45 | }\n 46 | \n 47 | #[simd_test]\n 48 | fn mask32x8_bitmask_roundtrip<S: Simd>(simd: S) {\n 49 | for bits in 0..=0xffff_u64 {\n 50 | let mask = mask32x8::from_bitmask(simd, bits);\n 51 | assert_eq!(mask.to_bitmask(), bits & 0xff);\n 52 | }\n 53 | }\n 54 | \n 55 | #[simd_test]\n 56 | fn mask64x4_bitmask_roundtrip<S: Simd>(simd: S) {\n 57 | for bits in 0..=0xffff_u64 {\n 58 | let mask = mask64x4::from_bitmask(simd, bits);\n 59 | assert_eq!(mask.to_bitmask(), bits & 0xf);\n 60 | }\n 61 | }\n 62 | \n 63 | #[simd_test]\n 64 | fn mask32x16_bitmask_roundtrip<S: Simd>(simd: S) {\n 65 | for bits in 0..=0xffff_u64 {\n 66 | let mask = mask32x16::from_bitmask(simd, bits);\n 67 | assert_eq!(mask.to_bitmask(), bits);\n 68 | }\n 69 | }\n 70 | \n 71 | #[simd_test]\n 72 | fn mask64x8_bitmask_roundtrip<S: Simd>(simd: S) {\n 73 | for bits in 0..=0xffff_u64 {\n 74 | let mask = mask64x8::from_bitmask(simd, bits);\n 75 | assert_eq!(mask.to_bitmask(), bits & 0xff);\n 76 | }","lines":68}]},{"id":"LOGIC_49","type":"logic_error","type_label":"Logic Error","severity":"low","easy":true,"description":"The `fract_f32x4` test at line 231 calls `simd.fract_f32x4(a)` while all other scalar operations use method syntax on the vector (e.g. `a.floor()`, `a.ceil()`). This is a stylistic inconsistency that will confuse readers about the actual public API being tested.","fix":"Use `a.fract()` if that is the public API, or add a note clarifying why the free function form is required here.","locations":[{"ref":"fearless_simd_tests/tests/harness/mod.rs:228-234","code":" 228 | fn fract_f32x4<S: Simd>(simd: S) {\n 229 | let a = f32x4::from_slice(simd, &[1.7, -2.3, 3.9, -4.1]);\n 230 | assert_eq!(\n 231 | *simd.fract_f32x4(a),\n 232 | [0.70000005, -0.29999995, 0.9000001, -0.099999905]\n 233 | );\n 234 | }","lines":7}]},{"id":"LOGIC_50","type":"logic_error","type_label":"Logic Error","severity":"low","easy":false,"description":"The `slide_exhaustive` module is gated behind `#[cfg(false)]` at line 4454, meaning it is permanently disabled at compile time and will never run. The comment says the tests are disabled because each slide amount requires a separate monomorphisation and the resulting binary is too large. However, the disabled module still exists in the repository with no CI path that would ever execute it, making it effectively dead test code.","fix":"Either remove the dead module entirely and rely on the slide unit tests that are already present, or move the exhaustive tests behind a feature flag and run them only in a dedicated slow-test CI job.","locations":[{"ref":"fearless_simd_tests/tests/harness/mod.rs:4452-4455","code":" 4452 | // Because the slide amount is a const generic, the exhaustive tests have to *compile* one slide per amount per vector\n 4453 | // type. Disable them entirely.`\n 4454 | #[cfg(false)]\n 4455 | mod slide_exhaustive;","lines":4}]},{"id":"LOGIC_51","type":"logic_error","type_label":"Logic Error","severity":"low","easy":false,"description":"The `test_vector_slide!` macro in `slide_exhaustive.rs` wraps only the SIMD-backend call in `vectorize()` but not the fallback call. Both paths should be wrapped equivalently to ensure the compiler generates comparable code paths. Currently the fallback runs outside any vectorize context while the test vector runs inside one, introducing an asymmetry that could hide differences caused by the vectorize wrapper itself.","fix":"Wrap both the test and fallback slide calls inside their respective `vectorize` closures, or document why the asymmetry is intentional.","locations":[{"ref":"fearless_simd_tests/tests/harness/slide_exhaustive.rs:19-33","code":" 19 | assert_eq!(\n 20 | core::hint::black_box(\n 21 | test_vec_a\n 22 | .witness()\n 23 | .vectorize(|| test_vec_a.slide::<$shift>(test_vec_b))\n 24 | .as_slice()\n 25 | ),\n 26 | core::hint::black_box(fallback_vec_a.slide::<$shift>(fallback_vec_b).as_slice()),\n 27 | \"slide::<{}> mismatch\",\n 28 | $shift\n 29 | );\n 30 | }\n 31 | \n 32 | do_test($test_vec_a, $test_vec_b, $fallback_vec_a, $fallback_vec_b)\n 33 | }};","lines":15}]},{"id":"LOGIC_52","type":"logic_error","type_label":"Logic Error","severity":"negligible","easy":false,"description":"The `sigmoid.rs` example computes `a / (a * a + 1.0).sqrt()`, which is `x / sqrt(x^2 + 1)`. Mathematically this equals `tanh(asinh(x))`, which is numerically close to `x / sqrt(x^2+1)`, but for large absolute values of `x` the result saturates toward +-1. The formula is correct for a smooth approximation but the example has no comment explaining what function is being approximated (it is sometimes called the \"algebraic\" or \"Softsign\" sigmoid, not the logistic sigmoid). A user reading this as a reference for how to write SIMD sigmoid code may implement the wrong function.","fix":"Add a comment clarifying that the formula `x / sqrt(x^2 + 1)` is the algebraic (Softsign) approximation, not the standard logistic sigmoid `1 / (1 + e^(-x))`.","locations":[{"ref":"fearless_simd/examples/sigmoid.rs:16","code":" 16 | let b = a / (a * a + 1.0).sqrt();","lines":1}]},{"id":"PERF_1","type":"performance","type_label":"Performance","severity":"low","easy":false,"description":"Fallback::vectorize wraps the user closure in a nested non-inline function vectorize_inner:\n fn vectorize_inner<F: FnOnce() -> R, R>(f: F) -> R { f() }\nThis inner function has no inline annotation (not #[inline] or #[inline(always)]), which may\nprevent the closure from being inlined into the call site. Fallback code that uses vectorize()\n(e.g., via the dispatch! macro) may miss auto-vectorization opportunities if the closure body\nis not inlined by the compiler.","fix":"Add #[inline(always)] to vectorize_inner, or inline it directly: replace the inner function with\na plain f() call. The inner function exists to ensure a non-vectorized calling convention, but\nsince Fallback is the scalar fallback and already has no target-feature attributes, there is no\nbenefit to the indirection.","locations":[{"ref":"fearless_simd/src/generated/fallback.rs:148-153","code":" 148 | fn vectorize<F: FnOnce() -> R, R>(self, f: F) -> R {\n 149 | fn vectorize_inner<F: FnOnce() -> R, R>(f: F) -> R {\n 150 | f()\n 151 | }\n 152 | vectorize_inner(f)\n 153 | }","lines":6}]},{"id":"PERF_2","type":"performance","type_label":"Performance","severity":"low","easy":false,"description":"mul_sub_f32x4 on NEON uses two instructions instead of one:\n vnegq_f32(vfmsq_f32(c, b, a))\nvfmsq_f32(c, b, a) computes c - (b*a), then vnegq_f32 negates to get a*b - c. The equivalent\nresult can be achieved in a single instruction using vfmaq_f32 with negated c:\n vfmaq_f32(vnegq_f32(c), b, a) = vneg(c) + b*a = a*b - c\nor equivalently using the fnmsub/vfnmaq_f32 pattern. However, the most direct single-instruction\napproach depends on the available NEON instructions. The current code is correct but suboptimal.","fix":"Replace with: vfmaq_f32(vnegq_f32(c.into()), b.into(), a.into())\nThis uses a negate of c as the addend to vfmaq, keeping the fused multiply in one FMA unit\noperation. Note that on most ARM cores both approaches are equivalent latency, so this is a\nminor optimization (eliminates one register and one instruction from the dependency chain).","locations":[{"ref":"fearless_simd/src/generated/neon.rs:274-276","code":" 274 | fn mul_sub_f32x4(self, a: f32x4<Self>, b: f32x4<Self>, c: f32x4<Self>) -> f32x4<Self> {\n 275 | unsafe { vnegq_f32(vfmsq_f32(c.into(), b.into(), a.into())).simd_into(self) }\n 276 | }","lines":3}]},{"id":"PERF_3","type":"performance","type_label":"Performance","severity":"low","easy":false,"description":"The `select_native_width_vectors` test allocates multiple `Vec`s (via `vec![]` macros) on every invocation for every SIMD backend. Since these are fixed-size slices known at compile time, using stack arrays (`[T; N]` or `&[T]` literals) would avoid heap allocation in a tight test loop and be more consistent with the rest of the test suite.","fix":"Replace `vec![1.0_f32; S::f32s::N]` etc. with a compile-time approach, e.g. constructing the vector via `S::f32s::splat(simd, 1.0)` and calling `as_slice()` for comparison, which avoids heap allocation entirely.","locations":[{"ref":"fearless_simd_tests/tests/harness/mod.rs:3487-3529","code":" 3487 | let a_f32 = S::f32s::from_slice(simd, &vec![1.0_f32; S::f32s::N]);\n 3488 | let b_f32 = S::f32s::from_slice(simd, &vec![2.0_f32; S::f32s::N]);\n 3489 | let mask_f32 = S::mask32s::from_slice(simd, &vec![-1_i32; S::mask32s::N]);\n 3490 | let result_f32 = mask_f32.select(a_f32, b_f32);\n 3491 | assert_eq!(result_f32.as_slice(), vec![1.0_f32; S::f32s::N]);\n 3492 | \n 3493 | // Test with native u32 vectors\n 3494 | let a_u32 = S::u32s::from_slice(simd, &vec![10_u32; S::u32s::N]);\n 3495 | let b_u32 = S::u32s::from_slice(simd, &vec![20_u32; S::u32s::N]);\n 3496 | let result_u32 = mask_f32.select(a_u32, b_u32);\n 3497 | assert_eq!(result_u32.as_slice(), vec![10_u32; S::u32s::N]);\n 3498 | \n 3499 | // Test with native i32 vectors\n 3500 | let a_i32 = S::i32s::from_slice(simd, &vec![100_i32; S::i32s::N]);\n 3501 | let b_i32 = S::i32s::from_slice(simd, &vec![-100_i32; S::i32s::N]);\n 3502 | let result_i32 = mask_f32.select(a_i32, b_i32);\n 3503 | assert_eq!(result_i32.as_slice(), vec![100_i32; S::i32s::N]);\n 3504 | \n 3505 | // Test with native u8 vectors\n 3506 | let a_u8 = S::u8s::from_slice(simd, &vec![1_u8; S::u8s::N]);\n 3507 | let b_u8 = S::u8s::from_slice(simd, &vec![2_u8; S::u8s::N]);\n 3508 | let mask_u8 = S::mask8s::from_slice(simd, &vec![0_i8; S::u8s::N]);\n 3509 | let result_u8 = mask_u8.select(a_u8, b_u8);\n 3510 | assert_eq!(result_u8.as_slice(), vec![2_u8; S::u8s::N]);\n 3511 | \n 3512 | // Test with native i8 vectors\n 3513 | let a_i8 = S::i8s::from_slice(simd, &vec![10_i8; S::i8s::N]);\n 3514 | let b_i8 = S::i8s::from_slice(simd, &vec![-10_i8; S::i8s::N]);\n 3515 | let result_i8 = mask_u8.select(a_i8, b_i8);\n 3516 | assert_eq!(result_i8.as_slice(), vec![-10_i8; S::i8s::N]);\n 3517 | \n 3518 | // Test with native u16 vectors\n 3519 | let a_u16 = S::u16s::from_slice(simd, &vec![100_u16; S::u16s::N]);\n 3520 | let b_u16 = S::u16s::from_slice(simd, &vec![200_u16; S::u16s::N]);\n 3521 | let mask_u16 = S::mask16s::from_slice(simd, &vec![-1_i16; S::mask16s::N]);\n 3522 | let result_u16 = mask_u16.select(a_u16, b_u16);\n 3523 | assert_eq!(result_u16.as_slice(), vec![100_u16; S::u16s::N]);\n 3524 | \n 3525 | // Test with native i16 vectors\n 3526 | let a_i16 = S::i16s::from_slice(simd, &vec![50_i16; S::i16s::N]);\n 3527 | let b_i16 = S::i16s::from_slice(simd, &vec![-50_i16; S::i16s::N]);\n 3528 | let result_i16 = mask_u16.select(a_i16, b_i16);\n 3529 | assert_eq!(result_i16.as_slice(), vec![50_i16; S::i16s::N]);","lines":43}]},{"id":"PERF_4","type":"performance","type_label":"Performance","severity":"low","easy":false,"description":"The `slide_exhaustive` macro at `slide_exhaustive.rs:203-223` calls `from_slice` for both the test and fallback vectors inside the test function body, which runs for each `#[simd_test]` backend invocation. Creating the fallback vectors should happen only once (not once per backend), but currently each backend call re-creates both the test and fallback vectors from the same arrays. This is minor, but repeated allocation and initialisation of 64-element arrays for every backend across many test invocations adds unnecessary cost.","fix":"Separate the fallback vector construction so it is created once outside the per-backend function, and pass it in as a reference.","locations":[{"ref":"fearless_simd_tests/tests/harness/slide_exhaustive.rs:206-221","code":" 206 | fn $test_name<S: Simd>(simd: S) {\n 207 | let fallback = fearless_simd::Fallback::new();\n 208 | \n 209 | let vals_a: [$elem_ty; $n_elems] = core::hint::black_box(core::array::from_fn(|i| (i + 1) as $elem_ty));\n 210 | let vals_b: [$elem_ty; $n_elems] = core::hint::black_box(core::array::from_fn(|i| (i + 1 + $n_elems) as $elem_ty));\n 211 | \n 212 | let test_vec_a = $vec_ty::from_slice(simd, &vals_a);\n 213 | let test_vec_b = $vec_ty::from_slice(simd, &vals_b);\n 214 | let fallback_vec_a = <$vec_ty::<fearless_simd::Fallback>>::from_slice(fallback, &vals_a);\n 215 | let fallback_vec_b = <$vec_ty::<fearless_simd::Fallback>>::from_slice(fallback, &vals_b);\n 216 | \n 217 | // Test vector-wide operations\n 218 | test_slide_impl!(@$vec_n test_vec_a, test_vec_b, fallback_vec_a, fallback_vec_b, $vec_ty);\n 219 | // Test within-block operations\n 220 | test_slide_impl!(@$block_n test_vec_a, test_vec_b, fallback_vec_a, fallback_vec_b, $vec_ty);\n 221 | }","lines":16}]},{"id":"DEAD_1","type":"dead_code","type_label":"Dead Code","severity":"low","easy":true,"description":"The five `exclude_*` functions in the proc-macro crate (`exclude_neon`, `exclude_fallback`, `exclude_sse4`, `exclude_avx2`, `exclude_wasm`) always return `false` and carry a comment saying developers can edit them. However, the comment (`// You can update below functions if you want to exclude certain tests from different architectures`) is the only mechanism for doing so; there is no attribute or per-test API. These stubs add dead code weight and the comment implies a maintenance burden that invites manual edits to a proc-macro rather than a declarative solution.","fix":"Either remove the functions and inline `false` directly, or add a proper declarative attribute (e.g. `#[simd_test(ignore_neon)]`) so per-test exclusions can be applied without editing the macro crate.","locations":[{"ref":"fearless_simd_dev_macros/src/lib.rs:145-163","code":" 145 | fn exclude_neon(_test_name: &str) -> bool {\n 146 | false\n 147 | }\n 148 | \n 149 | fn exclude_fallback(_test_name: &str) -> bool {\n 150 | false\n 151 | }\n 152 | \n 153 | fn exclude_sse4(_test_name: &str) -> bool {\n 154 | false\n 155 | }\n 156 | \n 157 | fn exclude_avx2(_test_name: &str) -> bool {\n 158 | false\n 159 | }\n 160 | \n 161 | fn exclude_wasm(_test_name: &str) -> bool {\n 162 | false\n 163 | }","lines":19}]},{"id":"DEAD_2","type":"dead_code","type_label":"Dead Code","severity":"negligible","easy":true,"description":"The `assert_simd_pod` function in transmute.rs is annotated with #[allow(dead_code)] because it\nis used only as a const fn pointer in the impl_aligned_simd_pod! macro for compile-time\nverification (const _: fn() = assert_simd_pod::<$inner>;). This pattern works but is unusual:\nthe result is assigned to a const of type fn(), not actually called, which means the assertion\nonly fires at compile time during monomorphization. This is intentional but the explanation in\nthe comment (\"just to only use a single cfg annotation, nothing to do with const evaluation\")\nis on a different const block and slightly misleading in context.","fix":"Add a comment directly on assert_simd_pod explaining the compile-time-only usage pattern:\n // Used as a const fn pointer value to trigger a compile error if T is not SimdPod.\nThis avoids confusion about why a dead_code allow is needed on a function used in a macro.","locations":[{"ref":"fearless_simd/src/transmute.rs:50-51","code":" 50 | #[allow(dead_code, reason = \"Not all platforms use safe transmute machinery\")]\n 51 | fn assert_simd_pod<T: SimdPod>() {}","lines":2}]},{"id":"DEAD_3","type":"dead_code","type_label":"Dead Code","severity":"negligible","easy":true,"description":"In `arch/fallback.rs`, the function `rhs_reference` (at the module level in `mk_fallback.rs`) returns whether the binary op's second argument needs to be passed by reference. The list of methods that do NOT need a reference is `'copysign' | 'min' | 'max' | 'wrapping_sub' | 'wrapping_mul' | 'wrapping_add'`. However, the generator method names are `'add'`, `'sub'`, `'mul'` (not `'wrapping_add'` etc.), so `'wrapping_sub'`, `'wrapping_mul'`, `'wrapping_add'` will never match any op method. These three patterns are dead.","fix":"Remove `'wrapping_sub' | 'wrapping_mul' | 'wrapping_add'` from the match arm, since no op method has those names.","locations":[{"ref":"fearless_simd_gen/src/mk_fallback.rs:575-580","code":" 575 | fn rhs_reference(method: &str) -> bool {\n 576 | !matches!(\n 577 | method,\n 578 | \"copysign\" | \"min\" | \"max\" | \"wrapping_sub\" | \"wrapping_mul\" | \"wrapping_add\"\n 579 | )\n 580 | }","lines":6}]},{"id":"CMT_1","type":"invalid_comment","type_label":"Invalid Comment","severity":"low","easy":true,"description":"The comment on the kernel_macros.rs inner function says:\n // can't use `#[inline(always)]` with target features\n(line 188). This is inaccurate: #[inline(always)] IS compatible with #[target_feature(...)],\nand many functions in the codebase combine both. The restriction described by this comment applies\nonly to #[target_feature] on outer functions that call unsafe inner functions, not to inline(always)\nitself. A more accurate statement would be: \"Using #[inline(always)] here would defeat the purpose\nof the inner function boundary that enables target features.\"","fix":"Correct the comment to explain why #[inline] (not #[inline(always)]) is used here. The reason\nis that the inner __fearless_simd_kernel function carries the #[target_feature] attribute, and\ninlining it would expose those features to the outer context. The goal is to have a separate\nfunction body with the feature enabled, not to inline it everywhere.","locations":[{"ref":"fearless_simd/src/kernel_macros.rs:188","code":" 188 | #[inline] // can't use `#[inline(always)]` with target features","lines":1}]},{"id":"CMT_2","type":"invalid_comment","type_label":"Invalid Comment","severity":"negligible","easy":true,"description":"The copyright header in transmute.rs reads \"Copyright 2026\" (line 1). All other files in the\ncodebase use 2024 or 2025. The year 2026 is in the future as of the codebase's development timeline\nand is almost certainly a typo or copy-paste error from a future-dated template.","fix":"Change \"Copyright 2026\" to \"Copyright 2025\" to match the other source files in the codebase.","locations":[{"ref":"fearless_simd/src/transmute.rs:1","code":" 1 | // Copyright 2026 the Fearless_SIMD Authors","lines":1}]},{"id":"CMT_3","type":"invalid_comment","type_label":"Invalid Comment","severity":"negligible","easy":true,"description":"The copyright headers in `soundness.rs` and `lm_generated.rs` (and its subdirectory files) read `// Copyright 2026 the Fearless_SIMD Authors`. The year 2026 is in the future relative to the project's other files (which use 2024 or 2025). This is almost certainly a typo; the files were likely created in 2025.","fix":"Change `2026` to `2025` in the copyright headers of the affected files.","locations":[{"ref":"fearless_simd_tests/tests/soundness.rs:1","code":" 1 | // Copyright 2026 the Fearless_SIMD Authors","lines":1},{"ref":"fearless_simd_tests/tests/harness/lm_generated.rs:1","code":" 1 | // Copyright 2026 the Fearless_SIMD Authors","lines":1},{"ref":"fearless_simd_tests/tests/harness/lm_generated/mask_methods.rs:1","code":" 1 | // Copyright 2026 the Fearless_SIMD Authors","lines":1},{"ref":"fearless_simd_tests/tests/harness/lm_generated/mod_256.rs:1","code":" 1 | // Copyright 2026 the Fearless_SIMD Authors","lines":1}]},{"id":"DUP_1","type":"duplicate_code","type_label":"Duplicate Code","severity":"low","easy":true,"description":"The duplicate block for `load_interleaved_128` and `store_interleaved_128` ops in `ops_for_type` checks the same condition `matches!(ty.scalar, ScalarType::Unsigned | ScalarType::Float) && ty.n_bits() == 512` twice in a row. The two `if` blocks are identical in their condition but push different ops. They can be merged into a single `if` block.","fix":"Merge the two `if` blocks: `if matches!(ty.scalar, ScalarType::Unsigned | ScalarType::Float) && ty.n_bits() == 512 { ops.push(load_interleaved_128); ops.push(store_interleaved_128); }`.","locations":[{"ref":"fearless_simd_gen/src/ops.rs:1313-1335","code":" 1313 | if matches!(ty.scalar, ScalarType::Unsigned | ScalarType::Float) && ty.n_bits() == 512 {\n 1314 | ops.push(Op::new(\n 1315 | \"load_interleaved_128\",\n 1316 | OpKind::AssociatedOnly,\n 1317 | OpSig::LoadInterleaved {\n 1318 | block_size: 128,\n 1319 | block_count: 4,\n 1320 | },\n 1321 | \"Load elements from an array with 4-way interleaving.\\n\\nReads consecutive elements and deinterleaves them into a single vector.\",\n 1322 | ));\n 1323 | }\n 1324 | \n 1325 | if matches!(ty.scalar, ScalarType::Unsigned | ScalarType::Float) && ty.n_bits() == 512 {\n 1326 | ops.push(Op::new(\n 1327 | \"store_interleaved_128\",\n 1328 | OpKind::AssociatedOnly,\n 1329 | OpSig::StoreInterleaved {\n 1330 | block_size: 128,\n 1331 | block_count: 4,\n 1332 | },\n 1333 | \"Store elements to an array with 4-way interleaving.\\n\\nInterleaves the vector elements and writes them consecutively to memory.\",\n 1334 | ));\n 1335 | }","lines":23}]},{"id":"ARCH_1","type":"architecture","type_label":"Architecture","severity":"low","easy":false,"description":"approximate_recip_f32x4 has significantly different precision across backends:\n- Fallback (fallback.rs:248): 1.0 / a - exact IEEE 754 division, full precision.\n- WASM (wasm.rs:174): 1.0 / a - exact IEEE 754 division, full precision.\n- NEON (neon.rs:175): vrecpeq_f32 - hardware reciprocal estimate, ~8-bit precision.\n- AVX2 (avx2.rs:185): _mm_rcp_ps - hardware reciprocal estimate, ~12-bit precision.\nCode that calls approximate_recip and then compares results across platforms (e.g., in tests)\nwill see differences far larger than floating-point rounding. Code relying on the result being\n\"approximate\" (and thus fast) is also misled on fallback and WASM, where it is actually exact\nand as slow as a true division. The function name implies consistency in approximation quality\nbut delivers anything from 8-bit to exact precision.","fix":"Document in the Simd trait that approximate_recip precision is implementation-defined and may\nrange from ~8-bit to full IEEE 754 precision. If consistent low-precision behavior is needed,\ncallers should apply a Newton-Raphson refinement step on top. Alternatively, the fallback and\nWASM implementations could apply a deliberate rounding to match hardware approximation quality,\nthough exact division is preferable for correctness.","locations":[{"ref":"fearless_simd/src/generated/fallback.rs:248-250","code":" 248 | fn approximate_recip_f32x4(self, a: f32x4<Self>) -> f32x4<Self> {\n 249 | 1.0 / a\n 250 | }","lines":3},{"ref":"fearless_simd/src/generated/wasm.rs:174-176","code":" 174 | fn approximate_recip_f32x4(self, a: f32x4<Self>) -> f32x4<Self> {\n 175 | 1.0 / a\n 176 | }","lines":3},{"ref":"fearless_simd/src/generated/neon.rs:175-177","code":" 175 | fn approximate_recip_f32x4(self, a: f32x4<Self>) -> f32x4<Self> {\n 176 | unsafe { vrecpeq_f32(a.into()).simd_into(self) }\n 177 | }","lines":3},{"ref":"fearless_simd/src/generated/avx2.rs:185-187","code":" 185 | fn approximate_recip_f32x4(self, a: f32x4<Self>) -> f32x4<Self> {\n 186 | unsafe { _mm_rcp_ps(a.into()).simd_into(self) }\n 187 | }","lines":3}]},{"id":"ARCH_2","type":"architecture","type_label":"Architecture","severity":"low","easy":false,"description":"The WithSimd implementation for FnOnce(Level)->R passes simd.level() to the closure. When used\nwith Level::dispatch (which calls f.with_simd(token)), the closure receives the Level re-derived\nfrom the token rather than the original Level that was dispatched. For all currently implemented\nSimd tokens this is equivalent, but the indirection adds conceptual overhead and means the Level\nreturned is the \"natural\" level of the token, not necessarily the dispatched level. If a token\never has a level() that differs from how it was obtained (as Fallback can when force_support_fallback\nis off and the target statically supports SIMD), users of this FnOnce pattern receive unexpected\nLevel values.","fix":"Document clearly that the FnOnce(Level) form of WithSimd receives the level of the Simd token,\nnot necessarily the Level that was used to select this token. Consider whether a separate\nFnOnce(S: Simd) variant would be less surprising, or add documentation examples showing the\npattern.","locations":[{"ref":"fearless_simd/src/traits.rs:28-35","code":" 28 | impl<R, F: FnOnce(Level) -> R> WithSimd for F {\n 29 | type Output = R;\n 30 | \n 31 | #[inline(always)]\n 32 | fn with_simd<S: Simd>(self, simd: S) -> Self::Output {\n 33 | self(simd.level())\n 34 | }\n 35 | }","lines":8},{"ref":"fearless_simd/src/lib.rs:648-650","code":" 648 | pub fn dispatch<W: WithSimd>(self, f: W) -> W::Output {\n 649 | dispatch!(self, simd => f.with_simd(simd))\n 650 | }","lines":3}]},{"id":"ARCH_3","type":"architecture","type_label":"Architecture","severity":"low","easy":false,"description":"In `mk_simd_trait.rs`, `mk_simd_int` uses `overloaded_ops_for(ScalarType::Unsigned)` to generate the trait bounds for `SimdInt`. This means the bounds listed in `SimdInt` are based on unsigned ops, including `ShlVectored` and `ShrVectored`. However, `SimdInt` is also implemented by signed integer vectors. For signed vectors, `NEGATE_INT` should appear in the trait bounds as well (i.e., `core::ops::Neg<Output=Self>`). Looking at the generated trait, signed int vectors must implement `Neg` via a separate bound in the associated type declarations in `mk_simd_trait.rs:74-84`, but the `SimdInt` trait itself does not require `Neg`. This means the trait is not fully self-describing: you cannot call `.neg()` on an arbitrary `impl SimdInt` value.","fix":"Either split `SimdInt` into `SimdSignedInt` and `SimdUnsignedInt`, or document that `Neg` is intentionally not a trait requirement of `SimdInt`.","locations":[{"ref":"fearless_simd_gen/src/mk_simd_trait.rs:240-264","code":" 240 | fn mk_simd_int() -> TokenStream {\n 241 | let methods = methods_for_vec_trait(ScalarType::Unsigned);\n 242 | let overloaded_ops = overloaded_ops_for(ScalarType::Unsigned);\n 243 | let op_traits = overloaded_ops\n 244 | .iter()\n 245 | .filter_map(|op| match &op.kind {\n 246 | OpKind::Overloaded(core_op) => Some(core_op),\n 247 | _ => None,\n 248 | })\n 249 | .flat_map(|core_op| core_op.trait_bounds());\n 250 | quote! {\n 251 | /// Functionality implemented by (signed and unsigned) integer SIMD vectors.\n 252 | pub trait SimdInt<S: Simd>: SimdBase<S> + Seal\n 253 | #(+ #op_traits)*\n 254 | {\n 255 | /// Convert this integer type to a floating-point type. This is a convenience method\n 256 | /// that delegates to [`SimdCvtFloat::float_from`], and can only be called if there\n 257 | /// actually exists a target type of the same bit width (currently, only `f32`).\n 258 | #[inline(always)]\n 259 | fn to_float<T: SimdCvtFloat<Self>>(self) -> T { T::float_from(self) }\n 260 | \n 261 | #( #methods )*\n 262 | }\n 263 | }\n 264 | }","lines":25}]},{"id":"ARCH_4","type":"architecture","type_label":"Architecture","severity":"low","easy":false,"description":"In `mk_wasm.rs`, the `WasmSimd128::make_impl_body` generates a `new_unchecked` constructor, but unlike NEON and x86 variants, the WASM implementation marks this as safe (no `unsafe` keyword). The function comment says nothing about safety. However, for NEON and x86, `new_unchecked` is marked `unsafe` because runtime CPU features may not be available. For WASM SIMD128, the feature is either always-on or detected at compile time (it cannot be checked at runtime in the same way), so a safe constructor is arguably correct. But the inconsistency could confuse users comparing WASM and NEON/x86 usage.","fix":"Add a doc comment to `WasmSimd128::new_unchecked` explaining why it is safe unlike the other backends, or rename it to `new` to signal it is always safe.","locations":[{"ref":"fearless_simd_gen/src/mk_wasm.rs:130-135","code":" 130 | fn make_impl_body(&self) -> TokenStream {\n 131 | quote! {\n 132 | #[inline]\n 133 | pub const fn new_unchecked() -> Self {\n 134 | Self { _private: () }\n 135 | }","lines":6}]},{"id":"ARCH_5","type":"architecture","type_label":"Architecture","severity":"low","easy":false,"description":"The `lm_generated` module comment in `mod_256.rs` states the files were \"generated by Claude Code\" and suggests a future goal of \"more rigorous automatic generation\". However, the files are committed as static source without any generation script or template. If the underlying primitive test in `mod.rs` is changed or new vector sizes are added, the generated tests will silently drift out of sync. There is no mechanism to detect or regenerate them.","fix":"Either add a code-generation script (build.rs or a separate tool) that can regenerate the expanded tests from the canonical 128-bit tests, or add a CI check that fails when the generated files differ from what the script would produce.","locations":[{"ref":"fearless_simd_tests/tests/harness/lm_generated/mod_256.rs:1-12","code":" 1 | // Copyright 2026 the Fearless_SIMD Authors\n 2 | // SPDX-License-Identifier: Apache-2.0 OR MIT\n 3 | \n 4 | // Expanded test suite generated by Claude Code based on mod.rs\n 5 | //\n 6 | // This file contains tests for 256-bit and 512-bit vector operations that correspond to\n 7 | // existing 128-bit vector tests in mod.rs.\n 8 | //\n 9 | // In the future we might want to add a more rigorous automatic generation\n 10 | // of test cases for various vector sizes, but this is a simple first step\n 11 | // towards increased test coverage.\n 12 | ","lines":12},{"ref":"fearless_simd_tests/tests/harness/lm_generated.rs:1-8","code":" 1 | // Copyright 2026 the Fearless_SIMD Authors\n 2 | // SPDX-License-Identifier: Apache-2.0 OR MIT\n 3 | \n 4 | mod extended_512;\n 5 | mod mask_methods;\n 6 | mod mod_256;\n 7 | mod mod_512;","lines":8}]},{"id":"OTHER_1","type":"other","type_label":"Other","severity":"medium","easy":false,"description":"The dispatch! macro ends with `_ => unreachable!()` (macros.rs line 142). If a new Level variant\nis ever added and dispatch! is not updated, this arm will be reached at runtime, causing a panic.\nThe comment in lib.rs says to update dispatch, but there is no compile-time enforcement of this.\nThe non_exhaustive attribute on Level prevents external code from being exhaustive, which is fine,\nbut the internal dispatch macro is inside the crate and must stay in sync manually.","fix":"Add a compile-time test (e.g. a const assertion or a compile-fail doctest) that exhaustively\nmatches all Level variants. Alternatively, make the dispatch macro explicitly list every cfg-guarded\nvariant without a catch-all arm, so a missing arm produces a compile error rather than a runtime\npanic.","locations":[{"ref":"fearless_simd/src/macros.rs:142","code":" 142 | _ => unreachable!(),","lines":1},{"ref":"fearless_simd/src/lib.rs:254-255","code":" 254 | // If new variants are added, make sure to handle them in `Level::dispatch`\n 255 | // and `dispatch!()`","lines":2}]},{"id":"OTHER_2","type":"other","type_label":"Other","severity":"low","easy":false,"description":"The cross_block_slide_blocks_at function in support.rs has no bounds assertion or documentation\nstating that shift_bytes must be in [0, N * BLOCK_BYTES). If shift_bytes >= N * BLOCK_BYTES,\nthen lo_idx could be >= 2*N and b[lo_idx - N] would panic with an out-of-bounds index.\nWhile all current call sites pass values derived from SHIFT * element_size where SHIFT < N (the\nearly-return guard ensures this), the function itself gives no indication of this precondition\nto future maintainers.","fix":"Add a debug_assert!(shift_bytes < N * BLOCK_BYTES, \"shift_bytes must be < N * 16\") at the top\nof the function, and document the precondition in the function's doc comment. This makes the\ninvariant explicit and catches any future callers that violate it.","locations":[{"ref":"fearless_simd/src/support.rs:51-65","code":" 51 | pub(crate) fn cross_block_slide_blocks_at<const N: usize, Block: Copy>(\n 52 | a: &[Block; N],\n 53 | b: &[Block; N],\n 54 | out_idx: usize,\n 55 | shift_bytes: usize,\n 56 | ) -> [Block; 2] {\n 57 | const BLOCK_BYTES: usize = 16;\n 58 | let out_byte_start = out_idx * BLOCK_BYTES + shift_bytes;\n 59 | let lo_idx = out_byte_start.div_euclid(BLOCK_BYTES);\n 60 | let hi_idx = lo_idx + 1;\n 61 | // Concatenation is [a : b], so indices 0..N are from a, indices N..2N are from b\n 62 | let lo_block = if lo_idx < N { a[lo_idx] } else { b[lo_idx - N] };\n 63 | let hi_block = if hi_idx < N { a[hi_idx] } else { b[hi_idx - N] };\n 64 | [lo_block, hi_block]\n 65 | }","lines":15}]},{"id":"OTHER_3","type":"other","type_label":"Other","severity":"low","easy":false,"description":"The `supports_highest_level` test skips the highest-level check when the `CI` environment variable is set, meaning CI can silently skip the check that all SIMD levels are tested. Since CI is precisely where correctness matters most, skipping the level check on CI defeats the purpose of the test. The false-positive comment in the test body acknowledges this but no compensating mechanism exists.","fix":"Instead of skipping the level assertion on CI, configure CI runners to use emulation (QEMU or Intel SDE) that supports all required SIMD levels, or remove the CI exemption and let unsupported-feature tests be correctly marked ignored rather than silently passing.","locations":[{"ref":"fearless_simd_tests/tests/mod.rs:44-56","code":" 44 | if std::env::var_os(\"CI\").is_none() {\n 45 | #[cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))]\n 46 | assert!(\n 47 | level.as_avx2().is_some(),\n 48 | \"This machine does not support every `Level` supported by Fearless SIMD (currently AVX2 and below).\\n{UNSUPPORTED_LEVEL_MESSAGE}\",\n 49 | );\n 50 | \n 51 | #[cfg(target_arch = \"aarch64\")]\n 52 | assert!(\n 53 | level.as_neon().is_some(),\n 54 | \"This machine does not support every `Level` supported by Fearless SIMD (currently NEON and below).\\n{UNSUPPORTED_LEVEL_MESSAGE}\",\n 55 | );\n 56 | }","lines":13}]}]</script>
<script type="application/json" id="PI">{"name":"fearless_simd","description":"","git_url":"https://github.com/linebender/fearless_simd.git","repo_url":"https://github.com/linebender/fearless_simd","commit":"d818fe43d9e02f6c9abcfd18ad5e30d8b5dc643e","commit_short":"d818fe43d9","commit_url":"https://github.com/linebender/fearless_simd/commit/d818fe43d9e02f6c9abcfd18ad5e30d8b5dc643e","branch":"main","generated":"2026-06-15"}</script>
<div class="app">
<div class="top">
<div>
<h1>Code Audit Report — fearless_simd</h1>
<div class="meta">Generated 2026-06-15 — <span id="stats"></span></div>
</div>
<div class="top-right">
<input type="search" id="search" placeholder="Search findings…" autocomplete="off">
<label class="group-label">Group by
<select id="groupby">
<option value="type">Type</option>
<option value="severity">Severity</option>
<option value="file">File</option>
<option value="none">None (flat)</option>
</select>
</label>
</div>
</div>
<div class="project-info"><div class="pi-name">fearless_simd</div><div class="pi-row"><a class="pi-repo-btn" href="https://github.com/linebender/fearless_simd" target="_blank" rel="noopener">🔗 https://github.com/linebender/fearless_simd</a><span><span class="pi-k">branch</span> <code>main</code></span><span><span class="pi-k">commit</span> <a href="https://github.com/linebender/fearless_simd/commit/d818fe43d9e02f6c9abcfd18ad5e30d8b5dc643e" target="_blank" rel="noopener"><code>d818fe43d9</code></a></span></div></div>
<div class="filterbar" id="filterbar"></div>
<div id="progress-wrap">
<div id="progress-bar"><div id="progress-fill"></div></div>
<span id="progress-text"></span>
<button class="action-btn" data-action="collapse-all">⊟ Collapse all</button>
<button class="action-btn" data-action="expand-all">⊞ Expand all</button>
<button class="action-btn" data-action="download-md">⬇ MD</button>
<button class="action-btn" data-action="download-md-short">⬇ MD Short</button>
<button class="action-btn" data-action="download-toml">⬇ TOML</button>
</div>
<div id="hidden-bar"></div>
<div id="content"></div>
</div>
<script>
(function(){
'use strict';
// Data
const FINDINGS = JSON.parse(document.getElementById('D').textContent);
const PROJECT_INFO = JSON.parse(document.getElementById('PI').textContent);
const SEVS = ['critical','high','medium','low','negligible'];
const STATUSES = ['open','fixed','wont-fix','false-positive'];
const ALL_TYPES = [...new Set(FINDINGS.map(f => f.type))].sort();
// Build lookup map for O(1) access
const FINDING_BY_ID = new Map(FINDINGS.map(f => [f.id, f]));
// Only severities actually present in the report — the filter bar hides the rest.
const PRESENT_SEVS = SEVS.filter(s => FINDINGS.some(f => (f.severity || 'medium') === s));
// Whether any finding is flagged easy — the "Quick wins" filter row is hidden otherwise.
const HAS_EASY = FINDINGS.some(f => f.easy);
const STORE_KEY = 'audit-status:' + location.href;
const STATUS_CLS = {open:'st-open', fixed:'st-fixed', 'wont-fix':'st-wont', 'false-positive':'st-fp'};
const STATUS_LBL = {open:'● open', fixed:'✓ fixed', 'wont-fix':"— won't fix", 'false-positive':'✗ false pos.'};
const STATUS_NEXT = {open:'fixed', fixed:'wont-fix', 'wont-fix':'false-positive', 'false-positive':'open'};
// State
const S = {
search: '', groupBy: 'type',
hiddenSev: new Set(), hiddenType: new Set(),
hiddenStatus: new Set(), hiddenGroup: new Set(),
collapsed: new Set(), easyOnly: false,
};
const statuses = {};
// Persistence
function loadStatuses() {
try { Object.assign(statuses, JSON.parse(localStorage.getItem(STORE_KEY) || '{}')); } catch {}
}
function saveStatuses() {
try { localStorage.setItem(STORE_KEY, JSON.stringify(statuses)); } catch {}
}
function statusOf(id) { return statuses[id] || 'open'; }
// URL hash — debounced so rapid filter changes don't thrash history
let _hashTimer = 0;
function syncHash() {
clearTimeout(_hashTimer);
_hashTimer = setTimeout(() => {
const p = new URLSearchParams();
if (S.groupBy !== 'type') p.set('g', S.groupBy);
if (S.hiddenSev.size) p.set('hs', [...S.hiddenSev].join(','));
if (S.hiddenType.size) p.set('ht', [...S.hiddenType].join(','));
if (S.hiddenStatus.size) p.set('hst', [...S.hiddenStatus].join(','));
if (S.easyOnly) p.set('e', '1');
if (S.search) p.set('q', S.search);
const str = p.toString();
history.replaceState(null, '', str ? '#' + str : location.pathname + location.search);
}, 300);
}
function loadHash() {
const p = new URLSearchParams(location.hash.slice(1));
if (p.has('g')) { S.groupBy = p.get('g'); document.getElementById('groupby').value = S.groupBy; }
if (p.has('q')) { S.search = p.get('q'); document.getElementById('search').value = S.search; }
if (p.has('hs')) p.get('hs') .split(',').filter(Boolean).forEach(v => S.hiddenSev.add(v));
if (p.has('ht')) p.get('ht') .split(',').filter(Boolean).forEach(v => S.hiddenType.add(v));
if (p.has('hst')) p.get('hst').split(',').filter(Boolean).forEach(v => S.hiddenStatus.add(v));
if (p.has('e')) S.easyOnly = p.get('e') === '1';
}
// Helpers
function esc(s) {
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
}
function highlight(text, q) {
if (!q) return esc(text);
const re = new RegExp('(' + q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&') + ')', 'gi');
return String(text).split(re).map((p, i) => i % 2 ? '<mark>' + esc(p) + '</mark>' : esc(p)).join('');
}
// Render text with ```fenced``` code blocks and `inline` backticks,
// also paragraph-splitting on blank lines. Output is HTML-safe.
function renderMd(text, q) {
const src = String(text == null ? '' : text);
const FENCE = /```[ \t]*([A-Za-z0-9_+-]*)[ \t]*\r?\n([\s\S]*?)```/g;
const parts = [];
let last = 0, m;
while ((m = FENCE.exec(src)) !== null) {
parts.push({kind: 'text', value: src.slice(last, m.index)});
parts.push({kind: 'code', value: m[2].replace(/\r?\n$/, '')});
last = m.index + m[0].length;
}
parts.push({kind: 'text', value: src.slice(last)});
function renderInline(s) {
const re = /`([^`\n]+)`/g;
let out = '', i = 0, mm;
while ((mm = re.exec(s)) !== null) {
out += highlight(s.slice(i, mm.index), q);
out += '<code class="md-inline">' + highlight(mm[1], q) + '</code>';
i = mm.index + mm[0].length;
}
out += highlight(s.slice(i), q);
return out;
}
function renderTextChunk(s) {
const paragraphs = s.split(/\r?\n[ \t]*\r?\n/);
const out = [];
for (const p of paragraphs) {
const trimmed = p.replace(/^\s+|\s+$/g, '');
if (!trimmed) continue;
out.push('<p>' + renderInline(trimmed).replace(/\r?\n/g, '<br>') + '</p>');
}
return out.join('');
}
let html = '';
for (const p of parts) {
if (p.kind === 'text') html += renderTextChunk(p.value);
else html += '<pre class="md-code">' + highlight(p.value, q) + '</pre>';
}
return html;
}
function sevCls(s) { return SEVS.includes(s) ? 'sev-'+s : 'sev-negligible'; }
function sevLbl(s) { return s ? s[0].toUpperCase()+s.slice(1) : 'Medium'; }
function typeLabel(t) { return t.replace(/_/g,' ').replace(/\b\w/g, c => c.toUpperCase()); }
// First sentence of text (up to and including the first period).
function firstSentence(text) {
const m = String(text).match(/\.(\s|$)/);
return m ? text.slice(0, text.indexOf(m[0]) + 1) : text;
}
// Build full MD report (mirrors to_markdown in Python).
function buildMdFull() {
const SORD = ['critical','high','medium','low','negligible'];
const today = new Date().toISOString().slice(0, 10);
let out = '# Code Audit Report\n\n_Generated ' + today + '_\n\n';
const typeMap = new Map();
for (const f of FINDINGS) {
if (!typeMap.has(f.type)) typeMap.set(f.type, []);
typeMap.get(f.type).push(f);
}
const sortedTypes = [...typeMap.keys()].sort();
out += '## Summary\n\n| Type | Severity | Count |\n|------|----------|-------|\n';
for (const t of sortedTypes) {
const items = typeMap.get(t);
const topSev = items.map(f => f.severity || 'medium').reduce((best, s) => {
const bi = SORD.indexOf(best), si = SORD.indexOf(s);
return (si >= 0 && (bi < 0 || si < bi)) ? s : best;
}, 'negligible');
out += '| ' + typeLabel(t) + ' | ' + topSev + ' | ' + items.length + ' |\n';
}
for (const t of sortedTypes) {
out += '\n## ' + typeLabel(t) + '\n\n';
for (const f of typeMap.get(t)) {
const sevStr = f.severity ? ' `' + f.severity.toUpperCase() + '`' : '';
const easyStr = f.easy ? ' ⚡ easy' : '';
out += '### ' + f.id + sevStr + easyStr + '\n\n';
out += '**Description:** ' + (f.description || '') + '\n\n';
if (f.locations.length) {
out += '**Locations:**\n\n';
for (const l of f.locations) {
if (l.code != null) out += '```\n' + l.ref + '\n' + l.code + '\n```\n\n';
else out += '- `' + l.ref + '` _(file not found)_\n\n';
}
}
out += '**Fix:** ' + (f.fix || '') + '\n\n---\n\n';
}
}
return out;
}
// Build MD short checklist (mirrors to_md_short in Python).
function buildMdShort() {
const lines = [];
const ids = [];
const sorted = [...FINDINGS].sort((a, b) => a.type.localeCompare(b.type));
for (const f of sorted) {
ids.push(f.id);
const desc = firstSentence(f.description || '');
const locs = f.locations.map(l => l.ref);
const locStr = locs.length ? ' - [' + locs.join(', ') + ']' : '';
const sevStr = f.severity ? ' [' + f.severity.toUpperCase() + ']' : '';
const easyStr = f.easy ? ' ⚡' : '';
lines.push('- [ ] - ' + f.id + sevStr + easyStr + ' - ' + desc + locStr);
}
lines.push('', '## IDs only', '');
ids.forEach(id => lines.push('- [ ] - ' + id));
return lines.join('\n') + '\n';
}
function downloadText(text, filename, mime) {
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([text], {type: mime || 'text/plain'}));
a.download = filename;
a.click();
setTimeout(() => URL.revokeObjectURL(a.href), 1000);
}
function tomlStr(s) {
return '"' + String(s == null ? '' : s)
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t') + '"';
}
// Build TOML matching the original findings.toml format (re-parseable by toml_to_report.py).
function buildFindingsToml() {
function pad(key) { return (key + ' ').slice(0, 12); }
const chunks = [];
for (const f of FINDINGS) {
const lns = ['[[finding]]'];
lns.push(pad('type') + '= ' + tomlStr(f.type));
lns.push(pad('severity') + '= ' + tomlStr(f.severity || 'medium'));
if (f.easy) lns.push(pad('easy') + '= true');
lns.push(pad('description') + '= ' + tomlStr(f.description || ''));
const refs = f.locations.map(l => tomlStr(l.ref));
lns.push(pad('locations') + '= [' + refs.join(', ') + ']');
lns.push(pad('fix') + '= ' + tomlStr(f.fix || ''));
chunks.push(lns.join('\n'));
}
return chunks.join('\n\n') + '\n';
}
// Build project-info.toml from embedded PROJECT_INFO (same format as gen_project_info.py).
function buildProjectInfoToml() {
const ORDER = ['name','description','git_url','repo_url','commit','commit_short','commit_url','branch','generated'];
const pi = Object.assign({}, PROJECT_INFO);
if (!('description' in pi)) pi.description = '';
const lines = [
'# Project metadata for the code audit report (all fields optional).',
'# Hand-edit freely (e.g. fill in description); re-run the audit without --force to keep edits.',
'', '[project]'
];
for (const k of ORDER) {
if (k in pi) lines.push(k + ' = ' + tomlStr(pi[k]));
}
return lines.join('\n') + '\n';
}
// Build the GitHub-ready Markdown for one finding (mirrors findings.md).
function findingToMd(f) {
let out = '### ' + f.id + (f.severity ? ' `' + f.severity.toUpperCase() + '`' : '') + (f.easy ? ' ⚡ easy' : '') + '\n\n';
out += '**Description:** ' + (f.description || '') + '\n\n';
if (f.locations.length) {
out += '**Locations:**\n\n';
for (const l of f.locations) {
if (l.code != null) out += '```\n' + l.ref + '\n' + l.code + '\n```\n\n';
else out += '- `' + l.ref + '` _(file not found)_\n\n';
}
}
out += '**Fix:** ' + (f.fix || '') + '\n';
return out;
}
function flashBtn(btn, msg) {
btn.textContent = msg;
setTimeout(() => { btn.textContent = '⧉ Copy'; }, 1200);
}
function copyText(text, btn) {
const ok = () => flashBtn(btn, '✓ Copied');
const no = () => flashBtn(btn, '✗ Failed');
const fallback = () => {
try {
const ta = document.createElement('textarea');
ta.value = text; ta.style.position = 'fixed'; ta.style.top = '-9999px';
document.body.appendChild(ta); ta.select();
const r = document.execCommand('copy');
document.body.removeChild(ta);
r ? ok() : no();
} catch { no(); }
};
if (navigator.clipboard && navigator.clipboard.writeText)
navigator.clipboard.writeText(text).then(ok, fallback);
else fallback();
}
// Filter logic
function getKey(f) {
if (S.groupBy === 'severity') return f.severity || 'medium';
if (S.groupBy === 'file') return f.locations.length ? f.locations[0].ref.replace(/:.*/,'') : '(no location)';
if (S.groupBy === 'none') return '';
return f.type_label;
}
// Pre-compute search haystack per finding (cleared on full re-render)
const _hay = new Map();
function hayOf(f) {
if (!_hay.has(f.id)) {
_hay.set(f.id, [f.id, f.type_label, f.severity, f.description, f.fix,
f.easy ? 'easy quick win' : '',
...f.locations.map(l => l.ref)].join(' ').toLowerCase());
}
return _hay.get(f.id);
}
function matches(f) {
if (S.easyOnly && !f.easy) return false;
if (S.hiddenSev.has(f.severity || 'medium')) return false;
if (S.hiddenType.has(f.type)) return false;
const st = statusOf(f.id);
if (S.hiddenStatus.has(st)) return false;
if (S.search) {
const q = S.search.toLowerCase();
if (!hayOf(f).includes(q) && !st.includes(q)) return false;
}
return true;
}
function sortKeys(keys) {
if (S.groupBy === 'severity') {
return [...keys].sort((a, b) => {
const ai = SEVS.indexOf(a), bi = SEVS.indexOf(b);
return (ai < 0 ? 99 : ai) - (bi < 0 ? 99 : bi);
});
}
return [...keys].sort();
}
// Build body HTML for an expanded card (called lazily on first expand).
function renderCardBody(f, q) {
let locs = '';
for (const l of f.locations) {
const lineInfo = l.lines != null
? ' <span class="loc-lines">(' + l.lines + ' line' + (l.lines === 1 ? '' : 's') + ')</span>'
: '';
locs += '<div class="loc-block"><div class="loc-ref">' + highlight(l.ref, q) + lineInfo + '</div>';
locs += l.code != null ? '<pre>' + esc(l.code) + '</pre>' : '<div class="not-found">(file not found)</div>';
locs += '</div>';
}
return (
'<div class="field-lbl">Description</div>' +
'<div class="desc">' + renderMd(f.description, q) + '</div>' +
(locs ? '<div class="field-lbl">Locations</div>' + locs : '') +
'<div class="field-lbl">Fix</div>' +
'<div class="fix-txt">' + renderMd(f.fix, q) + '</div>'
);
}
// Render card shell only — header + empty or populated body.
// When collapsed the body is empty (display:none) — avoids renderMd for 1800+ cards.
function renderCard(f, q) {
const sc = sevCls(f.severity || 'medium');
const sl = sevLbl(f.severity || 'medium');
const st = statusOf(f.id);
const sc2 = STATUS_CLS[st] || 'st-open';
const sl2 = STATUS_LBL[st] || '● open';
const isCollapsed = S.collapsed.has(f.id);
const isResolved = st !== 'open';
const locSummary = f.locations.length
? f.locations[0].ref + (f.locations.length > 1 ? ' \u2026' : '')
: '';
const bodyInner = isCollapsed ? '' : renderCardBody(f, q);
return (
'<div class="card' + (isResolved ? ' resolved' : '') + '" data-card-id="' + esc(f.id) + '">' +
'<div class="card-header" data-action="toggle-card" data-id="' + esc(f.id) + '">' +
'<span class="card-arrow">' + (isCollapsed ? '\u25b6' : '\u25bc') + '</span>' +
'<span class="card-id">' + esc(f.id) + '</span>' +
'<span class="badge ' + sc + '" style="cursor:default">' + esc(sl) + '</span>' +
'<span class="badge type-badge" style="cursor:default">' + esc(f.type_label) + '</span>' +
(f.easy ? '<span class="badge easy-badge" style="cursor:default" title="Easy to verify & fix - no deep knowledge of the code, SDK or technology required">\u26a1 Easy</span>' : '') +
(locSummary ? '<span class="card-loc" title="' + esc(locSummary) + '">' + esc(locSummary) + '</span>' : '') +
'<button class="status-btn ' + sc2 + '" data-action="cycle-status" data-id="' + esc(f.id) + '">' + sl2 + '</button>' +
'<button class="copy-btn" data-action="copy-md" data-id="' + esc(f.id) + '" title="Copy as Markdown for GitHub">\u29c9 Copy</button>' +
'</div>' +
'<div class="card-body" data-body-id="' + esc(f.id) + '"' + (isCollapsed ? ' style="display:none"' : '') + '>' +
bodyInner +
'</div></div>'
);
}
function initFilterBar() {
const fb = document.getElementById('filterbar');
let h = '<div class="filter-row"><span class="filter-lbl">Severity:</span>';
for (const s of PRESENT_SEVS)
h += '<button class="badge ' + sevCls(s) + '" data-sev="' + s + '">' + sevLbl(s) + '</button>';
h += '<button class="filter-toggle" data-toggle-all="sev"></button></div>';
h += '<div class="filter-row"><span class="filter-lbl">Type:</span>';
for (const t of ALL_TYPES)
h += '<button class="badge type-badge" data-type="' + esc(t) + '">' + esc(typeLabel(t)) + '</button>';
h += '<button class="filter-toggle" data-toggle-all="type"></button></div>';
h += '<div class="filter-row"><span class="filter-lbl">Status:</span>';
for (const st of STATUSES)
h += '<button class="badge ' + (STATUS_CLS[st]||'st-open') + '" data-status="' + st + '">' + (STATUS_LBL[st]||st) + '</button>';
h += '<button class="filter-toggle" data-toggle-all="status"></button></div>';
if (HAS_EASY)
h += '<div class="filter-row"><span class="filter-lbl">Quick wins:</span>'
+ '<button class="badge easy-badge" data-easy="1">\u26a1 Easy only</button></div>';
fb.innerHTML = h;
}
function updateToggles() {
for (const b of document.querySelectorAll('[data-sev]')) b.classList.toggle('off', S.hiddenSev.has(b.dataset.sev));
for (const b of document.querySelectorAll('[data-type]')) b.classList.toggle('off', S.hiddenType.has(b.dataset.type));
for (const b of document.querySelectorAll('[data-status]')) b.classList.toggle('off', S.hiddenStatus.has(b.dataset.status));
for (const b of document.querySelectorAll('[data-easy]')) b.classList.toggle('off', !S.easyOnly);
for (const b of document.querySelectorAll('[data-toggle-all]')) {
const k = b.dataset.toggleAll;
const set = k === 'sev' ? S.hiddenSev : k === 'type' ? S.hiddenType : S.hiddenStatus;
b.textContent = set.size === 0 ? 'Hide all' : 'Show all';
}
}
function updateProgress() {
const total = FINDINGS.length;
const done = FINDINGS.filter(f => statusOf(f.id) !== 'open').length;
const pct = total ? Math.round(done / total * 100) : 0;
document.getElementById('progress-fill').style.width = pct + '%';
document.getElementById('progress-text').textContent = done + ' / ' + total + ' resolved (' + pct + '%)';
}
// Surgical toggle: flip one card without touching the rest of the DOM.
function toggleCard(id) {
const wasCollapsed = S.collapsed.has(id);
if (wasCollapsed) S.collapsed.delete(id); else S.collapsed.add(id);
const body = document.querySelector('[data-body-id="' + id + '"]');
const arrow = document.querySelector('[data-card-id="' + id + '"] .card-arrow');
if (!body) { render(); return; }
if (wasCollapsed) {
if (!body.firstChild) {
const f = FINDING_BY_ID.get(id);
if (f) body.innerHTML = renderCardBody(f, S.search);
}
body.style.display = '';
if (arrow) arrow.textContent = '\u25bc';
} else {
body.style.display = 'none';
if (arrow) arrow.textContent = '\u25b6';
}
}
// Surgical status update: update one card's button + resolved class.
function cycleStatus(id) {
statuses[id] = STATUS_NEXT[statusOf(id)] || 'open';
saveStatuses();
const st = statusOf(id);
const btn = document.querySelector('.status-btn[data-id="' + id + '"]');
if (!btn) { render(); return; }
btn.className = 'status-btn ' + (STATUS_CLS[st] || 'st-open');
btn.textContent = STATUS_LBL[st] || '\u25cf open';
const card = document.querySelector('[data-card-id="' + id + '"]');
if (card) card.classList.toggle('resolved', st !== 'open');
updateProgress();
}
// Surgical collapse/expand for a group: toggle all bodies in the group without full re-render.
function collapseGroup(key, collapse) {
FINDINGS.forEach(f => {
if (getKey(f) !== key) return;
if (collapse) S.collapsed.add(f.id); else S.collapsed.delete(f.id);
const body = document.querySelector('[data-body-id="' + f.id + '"]');
const arrow = document.querySelector('[data-card-id="' + f.id + '"] .card-arrow');
if (!body) return;
if (collapse) {
body.style.display = 'none';
if (arrow) arrow.textContent = '\u25b6';
} else {
if (!body.firstChild) {
const ff = FINDING_BY_ID.get(f.id);
if (ff) body.innerHTML = renderCardBody(ff, S.search);
}
body.style.display = '';
if (arrow) arrow.textContent = '\u25bc';
}
});
}
// Full re-render: used for filter/group-by/search changes.
// Batched via requestAnimationFrame.
let _rafPending = false;
function scheduleRender() {
if (_rafPending) return;
_rafPending = true;
requestAnimationFrame(() => { _rafPending = false; render(); });
}
function render() {
_hay.clear();
updateToggles();
updateProgress();
const q = S.search;
const visible = FINDINGS.filter(matches);
document.getElementById('stats').textContent = visible.length + ' / ' + FINDINGS.length + ' findings';
// Build groups
const map = new Map();
for (const f of visible) {
const k = getKey(f);
if (!map.has(k)) map.set(k, []);
map.get(k).push(f);
}
// Hidden-groups bar
const hbar = document.getElementById('hidden-bar');
if (S.hiddenGroup.size > 0) {
let hh = '<div class="hidden-inner"><span class="hidden-lbl">Hidden:</span>';
for (const g of [...S.hiddenGroup].sort())
hh += '<button class="show-btn" data-action="show-group" data-key="' + esc(g) + '">' + esc(g || '(all findings)') + '</button>';
hh += '<button class="show-all-btn" data-action="show-all">Show all</button></div>';
hbar.innerHTML = hh;
} else {
hbar.innerHTML = '';
}
// Content
const content = document.getElementById('content');
if (!visible.length) {
content.innerHTML = '<div class="no-results">No findings match the current filters.</div>';
syncHash();
return;
}
let hh = '';
for (const k of sortKeys([...map.keys()])) {
if (S.hiddenGroup.has(k)) continue;
const items = map.get(k);
const title = k || 'All Findings';
const done = items.filter(f => statusOf(f.id) !== 'open').length;
const allDone = done === items.length;
hh += '<div class="group-section">' +
'<div class="group-header">' +
'<span class="group-title' + (allDone ? ' all-done' : '') + '">' + esc(title) + '</span>' +
'<span class="group-count">' + done + '/' + items.length + '</span>' +
'<button class="icon-btn" data-action="collapse-group" data-key="' + esc(k) + '" title="Collapse all">\u229f</button>' +
'<button class="icon-btn" data-action="expand-group" data-key="' + esc(k) + '" title="Expand all">\u229e</button>' +
'<button class="hide-btn" data-action="hide-group" data-key="' + esc(k) + '">Hide</button>' +
'</div>';
for (const f of items) hh += renderCard(f, q);
hh += '</div>';
}
content.innerHTML = hh;
syncHash();
}
// Event delegation
document.getElementById('filterbar').addEventListener('click', e => {
const b = e.target.closest('[data-sev],[data-type],[data-status],[data-toggle-all],[data-easy]');
if (!b) return;
if (b.dataset.easy) {
S.easyOnly = !S.easyOnly;
}
else if (b.dataset.toggleAll) {
const k = b.dataset.toggleAll;
const set = k === 'sev' ? S.hiddenSev : k === 'type' ? S.hiddenType : S.hiddenStatus;
const full = k === 'sev' ? PRESENT_SEVS : k === 'type' ? ALL_TYPES : STATUSES;
if (set.size === 0) full.forEach(v => set.add(v));
else set.clear();
}
else if (b.dataset.sev) { const s = S.hiddenSev; s.has(b.dataset.sev) ? s.delete(b.dataset.sev) : s.add(b.dataset.sev); }
else if (b.dataset.type) { const s = S.hiddenType; s.has(b.dataset.type) ? s.delete(b.dataset.type) : s.add(b.dataset.type); }
else if (b.dataset.status) { const s = S.hiddenStatus; s.has(b.dataset.status) ? s.delete(b.dataset.status) : s.add(b.dataset.status); }
scheduleRender();
});
document.addEventListener('click', e => {
const b = e.target.closest('[data-action]');
if (!b) return;
const { action, key, id } = b.dataset;
if (action === 'hide-group') { S.hiddenGroup.add(key); scheduleRender(); }
else if (action === 'show-group') { S.hiddenGroup.delete(key); scheduleRender(); }
else if (action === 'show-all') { S.hiddenGroup.clear(); scheduleRender(); }
else if (action === 'collapse-all') { FINDINGS.forEach(f => S.collapsed.add(f.id)); scheduleRender(); }
else if (action === 'expand-all') { S.collapsed.clear(); scheduleRender(); }
else if (action === 'copy-md') {
e.stopPropagation();
const f = FINDING_BY_ID.get(id);
if (f) copyText(findingToMd(f), b);
}
else if (action === 'download-md') { downloadText(buildMdFull(), 'findings.md'); }
else if (action === 'download-md-short') { downloadText(buildMdShort(), 'findings-short.md'); }
else if (action === 'download-toml') {
downloadText(buildFindingsToml(), 'findings.toml', 'application/toml');
downloadText(buildProjectInfoToml(), 'project-info.toml', 'application/toml');
}
else if (action === 'cycle-status') {
e.stopPropagation();
cycleStatus(id);
}
else if (action === 'toggle-card') {
toggleCard(id);
}
else if (action === 'collapse-group') {
collapseGroup(key, true);
}
else if (action === 'expand-group') {
collapseGroup(key, false);
}
});
// Debounced search — 180 ms after last keystroke
let _searchTimer = 0;
document.getElementById('search').addEventListener('input', e => {
S.search = e.target.value;
clearTimeout(_searchTimer);
_searchTimer = setTimeout(scheduleRender, 180);
});
document.getElementById('groupby').addEventListener('change', e => {
S.groupBy = e.target.value;
S.hiddenGroup.clear();
scheduleRender();
});
// Init
loadStatuses();
initFilterBar();
loadHash();
FINDINGS.forEach(f => S.collapsed.add(f.id));
render();
})();
</script>
</body></html>