usearch 2.21.3 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (602) hide show
  1. package/README.md +24 -24
  2. package/binding.gyp +15 -37
  3. package/c/CMakeLists.txt +4 -5
  4. package/c/README.md +7 -7
  5. package/c/lib.cpp +32 -14
  6. package/c/test.c +83 -1
  7. package/c/usearch.h +24 -3
  8. package/cpp/CMakeLists.txt +4 -0
  9. package/cpp/README.md +5 -5
  10. package/cpp/bench.cpp +97 -116
  11. package/cpp/test.cpp +117 -20
  12. package/include/usearch/index.hpp +235 -52
  13. package/include/usearch/index_dense.hpp +105 -40
  14. package/include/usearch/index_plugins.hpp +1387 -204
  15. package/javascript/dist/cjs/usearch.d.ts +8 -3
  16. package/javascript/dist/cjs/usearch.js +12 -4
  17. package/javascript/dist/esm/usearch.d.ts +8 -3
  18. package/javascript/dist/esm/usearch.js +12 -4
  19. package/javascript/lib.cpp +17 -0
  20. package/javascript/usearch.ts +14 -6
  21. package/numkong/.clang-tidy +30 -0
  22. package/{simsimd → numkong}/.cmake-format.py +18 -0
  23. package/numkong/.editorconfig +12 -0
  24. package/numkong/.git-blame-ignore-revs +123 -0
  25. package/numkong/.mailmap +15 -0
  26. package/numkong/.spi.yml +11 -0
  27. package/numkong/.swift-format +14 -0
  28. package/numkong/CITATION.cff +81 -0
  29. package/numkong/CONTRIBUTING.md +500 -0
  30. package/numkong/Cargo.toml +54 -0
  31. package/numkong/MANIFEST.in +8 -0
  32. package/numkong/Package.swift +84 -0
  33. package/numkong/README.md +569 -0
  34. package/numkong/bench/README.md +217 -0
  35. package/numkong/bench/bench-browser.html +323 -0
  36. package/numkong/bench/bench.cpp +344 -0
  37. package/numkong/bench/bench.hpp +816 -0
  38. package/numkong/bench/bench.js +148 -0
  39. package/numkong/bench/bench.mjs +616 -0
  40. package/numkong/bench/bench_attention.cpp +113 -0
  41. package/numkong/bench/bench_cast.cpp +264 -0
  42. package/numkong/bench/bench_cross_amx.cpp +62 -0
  43. package/numkong/bench/bench_cross_arm.cpp +245 -0
  44. package/numkong/bench/bench_cross_blas.cpp +309 -0
  45. package/numkong/bench/bench_cross_loongarch.cpp +106 -0
  46. package/numkong/bench/bench_cross_power.cpp +84 -0
  47. package/numkong/bench/bench_cross_rvv.cpp +121 -0
  48. package/numkong/bench/bench_cross_serial.cpp +151 -0
  49. package/numkong/bench/bench_cross_sme.cpp +163 -0
  50. package/numkong/bench/bench_cross_wasm.cpp +142 -0
  51. package/numkong/bench/bench_cross_x86.cpp +455 -0
  52. package/numkong/bench/bench_curved.cpp +210 -0
  53. package/numkong/bench/bench_dot.cpp +315 -0
  54. package/numkong/bench/bench_each.cpp +503 -0
  55. package/numkong/bench/bench_geospatial.cpp +112 -0
  56. package/numkong/bench/bench_maxsim.cpp +154 -0
  57. package/numkong/bench/bench_mesh.cpp +148 -0
  58. package/numkong/bench/bench_probability.cpp +60 -0
  59. package/numkong/bench/bench_reduce.cpp +371 -0
  60. package/numkong/bench/bench_set.cpp +73 -0
  61. package/numkong/bench/bench_sparse.cpp +198 -0
  62. package/numkong/bench/bench_spatial.cpp +347 -0
  63. package/numkong/bench/bench_trigonometry.cpp +137 -0
  64. package/numkong/bench/swift/Bench.swift +506 -0
  65. package/numkong/binding.gyp +169 -0
  66. package/numkong/build.rs +484 -0
  67. package/numkong/c/dispatch.h +512 -0
  68. package/numkong/c/dispatch_bf16.c +447 -0
  69. package/numkong/c/dispatch_bf16c.c +52 -0
  70. package/numkong/c/dispatch_e2m3.c +296 -0
  71. package/numkong/c/dispatch_e3m2.c +284 -0
  72. package/numkong/c/dispatch_e4m3.c +318 -0
  73. package/numkong/c/dispatch_e5m2.c +316 -0
  74. package/numkong/c/dispatch_f16.c +452 -0
  75. package/numkong/c/dispatch_f16c.c +58 -0
  76. package/numkong/c/dispatch_f32.c +434 -0
  77. package/numkong/c/dispatch_f32c.c +99 -0
  78. package/numkong/c/dispatch_f64.c +348 -0
  79. package/numkong/c/dispatch_f64c.c +98 -0
  80. package/numkong/c/dispatch_i16.c +96 -0
  81. package/numkong/c/dispatch_i32.c +89 -0
  82. package/numkong/c/dispatch_i4.c +153 -0
  83. package/numkong/c/dispatch_i64.c +86 -0
  84. package/numkong/c/dispatch_i8.c +348 -0
  85. package/numkong/c/dispatch_other.c +348 -0
  86. package/numkong/c/dispatch_u1.c +193 -0
  87. package/numkong/c/dispatch_u16.c +124 -0
  88. package/numkong/c/dispatch_u32.c +118 -0
  89. package/numkong/c/dispatch_u4.c +153 -0
  90. package/numkong/c/dispatch_u64.c +102 -0
  91. package/numkong/c/dispatch_u8.c +364 -0
  92. package/numkong/c/numkong.c +940 -0
  93. package/numkong/cmake/nk_arm_isa_probes.cmake +61 -0
  94. package/numkong/cmake/nk_isa_probe.cmake +90 -0
  95. package/numkong/cmake/nk_loongarch_isa_probes.cmake +15 -0
  96. package/numkong/cmake/nk_math_lib.cmake +46 -0
  97. package/numkong/cmake/nk_power_isa_probes.cmake +15 -0
  98. package/numkong/cmake/nk_riscv_isa_probes.cmake +19 -0
  99. package/numkong/cmake/nk_x86_isa_probes.cmake +35 -0
  100. package/numkong/cmake/toolchain-aarch64-gnu.cmake +86 -0
  101. package/numkong/cmake/toolchain-android-arm64.cmake +25 -0
  102. package/numkong/cmake/toolchain-android-armv7.cmake +25 -0
  103. package/numkong/cmake/toolchain-loongarch64-gnu.cmake +68 -0
  104. package/numkong/cmake/toolchain-ppc64le-gnu.cmake +74 -0
  105. package/numkong/cmake/toolchain-riscv64-gnu.cmake +107 -0
  106. package/numkong/cmake/toolchain-riscv64-llvm.cmake +114 -0
  107. package/numkong/cmake/toolchain-wasi.cmake +77 -0
  108. package/numkong/cmake/toolchain-wasm.cmake +70 -0
  109. package/numkong/cmake/toolchain-wasm64.cmake +62 -0
  110. package/numkong/cmake/toolchain-x86_64-llvm.cmake +47 -0
  111. package/numkong/include/README.md +643 -0
  112. package/numkong/include/module.modulemap +129 -0
  113. package/numkong/include/numkong/attention/sapphireamx.h +1363 -0
  114. package/numkong/include/numkong/attention/sme.h +2075 -0
  115. package/numkong/include/numkong/attention.h +49 -0
  116. package/numkong/include/numkong/capabilities.h +930 -0
  117. package/numkong/include/numkong/cast/README.md +262 -0
  118. package/numkong/include/numkong/cast/diamond.h +64 -0
  119. package/numkong/include/numkong/cast/haswell.h +823 -0
  120. package/numkong/include/numkong/cast/icelake.h +475 -0
  121. package/numkong/include/numkong/cast/loongsonasx.h +252 -0
  122. package/numkong/include/numkong/cast/neon.h +1174 -0
  123. package/numkong/include/numkong/cast/powervsx.h +449 -0
  124. package/numkong/include/numkong/cast/rvv.h +970 -0
  125. package/numkong/include/numkong/cast/sapphire.h +262 -0
  126. package/numkong/include/numkong/cast/serial.h +2336 -0
  127. package/numkong/include/numkong/cast/skylake.h +915 -0
  128. package/numkong/include/numkong/cast/v128relaxed.h +537 -0
  129. package/numkong/include/numkong/cast.h +254 -0
  130. package/numkong/include/numkong/cast.hpp +44 -0
  131. package/numkong/include/numkong/curved/README.md +223 -0
  132. package/numkong/include/numkong/curved/genoa.h +182 -0
  133. package/numkong/include/numkong/curved/haswell.h +276 -0
  134. package/numkong/include/numkong/curved/neon.h +329 -0
  135. package/numkong/include/numkong/curved/neonbfdot.h +211 -0
  136. package/numkong/include/numkong/curved/rvv.h +305 -0
  137. package/numkong/include/numkong/curved/serial.h +207 -0
  138. package/numkong/include/numkong/curved/skylake.h +457 -0
  139. package/numkong/include/numkong/curved/smef64.h +530 -0
  140. package/numkong/include/numkong/curved.h +513 -0
  141. package/numkong/include/numkong/curved.hpp +144 -0
  142. package/numkong/include/numkong/dot/README.md +442 -0
  143. package/numkong/include/numkong/dot/alder.h +563 -0
  144. package/numkong/include/numkong/dot/diamond.h +158 -0
  145. package/numkong/include/numkong/dot/genoa.h +289 -0
  146. package/numkong/include/numkong/dot/haswell.h +1723 -0
  147. package/numkong/include/numkong/dot/icelake.h +997 -0
  148. package/numkong/include/numkong/dot/loongsonasx.h +671 -0
  149. package/numkong/include/numkong/dot/neon.h +869 -0
  150. package/numkong/include/numkong/dot/neonbfdot.h +243 -0
  151. package/numkong/include/numkong/dot/neonfhm.h +358 -0
  152. package/numkong/include/numkong/dot/neonfp8.h +323 -0
  153. package/numkong/include/numkong/dot/neonsdot.h +622 -0
  154. package/numkong/include/numkong/dot/powervsx.h +752 -0
  155. package/numkong/include/numkong/dot/rvv.h +722 -0
  156. package/numkong/include/numkong/dot/rvvbb.h +72 -0
  157. package/numkong/include/numkong/dot/rvvbf16.h +123 -0
  158. package/numkong/include/numkong/dot/rvvhalf.h +129 -0
  159. package/numkong/include/numkong/dot/sapphire.h +141 -0
  160. package/numkong/include/numkong/dot/serial.h +874 -0
  161. package/numkong/include/numkong/dot/sierra.h +405 -0
  162. package/numkong/include/numkong/dot/skylake.h +1116 -0
  163. package/numkong/include/numkong/dot/sve.h +420 -0
  164. package/numkong/include/numkong/dot/svebfdot.h +75 -0
  165. package/numkong/include/numkong/dot/svehalf.h +168 -0
  166. package/numkong/include/numkong/dot/svesdot.h +90 -0
  167. package/numkong/include/numkong/dot/v128relaxed.h +1293 -0
  168. package/numkong/include/numkong/dot.h +1136 -0
  169. package/numkong/include/numkong/dot.hpp +94 -0
  170. package/numkong/include/numkong/dots/README.md +496 -0
  171. package/numkong/include/numkong/dots/alder.h +117 -0
  172. package/numkong/include/numkong/dots/diamond.h +86 -0
  173. package/numkong/include/numkong/dots/genoa.h +100 -0
  174. package/numkong/include/numkong/dots/graniteamx.h +733 -0
  175. package/numkong/include/numkong/dots/haswell.h +310 -0
  176. package/numkong/include/numkong/dots/icelake.h +180 -0
  177. package/numkong/include/numkong/dots/loongsonasx.h +176 -0
  178. package/numkong/include/numkong/dots/neon.h +123 -0
  179. package/numkong/include/numkong/dots/neonbfdot.h +59 -0
  180. package/numkong/include/numkong/dots/neonfhm.h +96 -0
  181. package/numkong/include/numkong/dots/neonfp8.h +99 -0
  182. package/numkong/include/numkong/dots/neonsdot.h +144 -0
  183. package/numkong/include/numkong/dots/powervsx.h +194 -0
  184. package/numkong/include/numkong/dots/rvv.h +2593 -0
  185. package/numkong/include/numkong/dots/sapphireamx.h +4017 -0
  186. package/numkong/include/numkong/dots/serial.h +2867 -0
  187. package/numkong/include/numkong/dots/sierra.h +100 -0
  188. package/numkong/include/numkong/dots/skylake.h +207 -0
  189. package/numkong/include/numkong/dots/sme.h +5041 -0
  190. package/numkong/include/numkong/dots/smebi32.h +477 -0
  191. package/numkong/include/numkong/dots/smef64.h +1328 -0
  192. package/numkong/include/numkong/dots/smehalf.h +47 -0
  193. package/numkong/include/numkong/dots/v128relaxed.h +317 -0
  194. package/numkong/include/numkong/dots.h +3110 -0
  195. package/numkong/include/numkong/dots.hpp +641 -0
  196. package/numkong/include/numkong/each/README.md +465 -0
  197. package/numkong/include/numkong/each/haswell.h +1658 -0
  198. package/numkong/include/numkong/each/icelake.h +272 -0
  199. package/numkong/include/numkong/each/neon.h +1138 -0
  200. package/numkong/include/numkong/each/neonbfdot.h +211 -0
  201. package/numkong/include/numkong/each/neonhalf.h +318 -0
  202. package/numkong/include/numkong/each/rvv.h +1121 -0
  203. package/numkong/include/numkong/each/sapphire.h +343 -0
  204. package/numkong/include/numkong/each/serial.h +282 -0
  205. package/numkong/include/numkong/each/skylake.h +1562 -0
  206. package/numkong/include/numkong/each/v128relaxed.h +562 -0
  207. package/numkong/include/numkong/each.h +2232 -0
  208. package/numkong/include/numkong/each.hpp +434 -0
  209. package/numkong/include/numkong/geospatial/README.md +147 -0
  210. package/numkong/include/numkong/geospatial/haswell.h +633 -0
  211. package/numkong/include/numkong/geospatial/neon.h +615 -0
  212. package/numkong/include/numkong/geospatial/rvv.h +701 -0
  213. package/numkong/include/numkong/geospatial/serial.h +309 -0
  214. package/numkong/include/numkong/geospatial/skylake.h +613 -0
  215. package/numkong/include/numkong/geospatial/v128relaxed.h +652 -0
  216. package/numkong/include/numkong/geospatial.h +453 -0
  217. package/numkong/include/numkong/geospatial.hpp +235 -0
  218. package/numkong/include/numkong/matrix.hpp +336 -0
  219. package/numkong/include/numkong/maxsim/README.md +187 -0
  220. package/numkong/include/numkong/maxsim/alder.h +511 -0
  221. package/numkong/include/numkong/maxsim/genoa.h +115 -0
  222. package/numkong/include/numkong/maxsim/haswell.h +553 -0
  223. package/numkong/include/numkong/maxsim/icelake.h +480 -0
  224. package/numkong/include/numkong/maxsim/neonsdot.h +394 -0
  225. package/numkong/include/numkong/maxsim/sapphireamx.h +877 -0
  226. package/numkong/include/numkong/maxsim/serial.h +490 -0
  227. package/numkong/include/numkong/maxsim/sme.h +993 -0
  228. package/numkong/include/numkong/maxsim/v128relaxed.h +280 -0
  229. package/numkong/include/numkong/maxsim.h +571 -0
  230. package/numkong/include/numkong/maxsim.hpp +133 -0
  231. package/numkong/include/numkong/mesh/README.md +219 -0
  232. package/numkong/include/numkong/mesh/haswell.h +2013 -0
  233. package/numkong/include/numkong/mesh/neon.h +1821 -0
  234. package/numkong/include/numkong/mesh/neonbfdot.h +771 -0
  235. package/numkong/include/numkong/mesh/rvv.h +936 -0
  236. package/numkong/include/numkong/mesh/serial.h +721 -0
  237. package/numkong/include/numkong/mesh/skylake.h +2344 -0
  238. package/numkong/include/numkong/mesh/v128relaxed.h +1081 -0
  239. package/numkong/include/numkong/mesh.h +666 -0
  240. package/numkong/include/numkong/mesh.hpp +701 -0
  241. package/numkong/include/numkong/numkong.h +78 -0
  242. package/numkong/include/numkong/numkong.hpp +58 -0
  243. package/numkong/include/numkong/probability/README.md +177 -0
  244. package/numkong/include/numkong/probability/haswell.h +267 -0
  245. package/numkong/include/numkong/probability/neon.h +255 -0
  246. package/numkong/include/numkong/probability/rvv.h +414 -0
  247. package/numkong/include/numkong/probability/serial.h +181 -0
  248. package/numkong/include/numkong/probability/skylake.h +324 -0
  249. package/numkong/include/numkong/probability.h +380 -0
  250. package/numkong/include/numkong/probability.hpp +120 -0
  251. package/numkong/include/numkong/random.h +50 -0
  252. package/numkong/include/numkong/random.hpp +285 -0
  253. package/numkong/include/numkong/reduce/README.md +552 -0
  254. package/numkong/include/numkong/reduce/alder.h +636 -0
  255. package/numkong/include/numkong/reduce/genoa.h +201 -0
  256. package/numkong/include/numkong/reduce/haswell.h +3785 -0
  257. package/numkong/include/numkong/reduce/icelake.h +549 -0
  258. package/numkong/include/numkong/reduce/neon.h +3980 -0
  259. package/numkong/include/numkong/reduce/neonbfdot.h +142 -0
  260. package/numkong/include/numkong/reduce/neonfhm.h +293 -0
  261. package/numkong/include/numkong/reduce/neonsdot.h +357 -0
  262. package/numkong/include/numkong/reduce/rvv.h +3533 -0
  263. package/numkong/include/numkong/reduce/serial.h +753 -0
  264. package/numkong/include/numkong/reduce/sierra.h +338 -0
  265. package/numkong/include/numkong/reduce/skylake.h +3794 -0
  266. package/numkong/include/numkong/reduce/sve.h +52 -0
  267. package/numkong/include/numkong/reduce/v128relaxed.h +2302 -0
  268. package/numkong/include/numkong/reduce.h +1582 -0
  269. package/numkong/include/numkong/reduce.hpp +778 -0
  270. package/numkong/include/numkong/scalar/README.md +89 -0
  271. package/numkong/include/numkong/scalar/haswell.h +122 -0
  272. package/numkong/include/numkong/scalar/loongsonasx.h +74 -0
  273. package/numkong/include/numkong/scalar/neon.h +122 -0
  274. package/numkong/include/numkong/scalar/neonhalf.h +70 -0
  275. package/numkong/include/numkong/scalar/powervsx.h +96 -0
  276. package/numkong/include/numkong/scalar/rvv.h +211 -0
  277. package/numkong/include/numkong/scalar/sapphire.h +74 -0
  278. package/numkong/include/numkong/scalar/serial.h +332 -0
  279. package/numkong/include/numkong/scalar/v128relaxed.h +56 -0
  280. package/numkong/include/numkong/scalar.h +696 -0
  281. package/numkong/include/numkong/set/README.md +179 -0
  282. package/numkong/include/numkong/set/haswell.h +334 -0
  283. package/numkong/include/numkong/set/icelake.h +485 -0
  284. package/numkong/include/numkong/set/loongsonasx.h +181 -0
  285. package/numkong/include/numkong/set/neon.h +363 -0
  286. package/numkong/include/numkong/set/powervsx.h +326 -0
  287. package/numkong/include/numkong/set/rvv.h +226 -0
  288. package/numkong/include/numkong/set/rvvbb.h +117 -0
  289. package/numkong/include/numkong/set/serial.h +174 -0
  290. package/numkong/include/numkong/set/sve.h +187 -0
  291. package/numkong/include/numkong/set/v128relaxed.h +240 -0
  292. package/numkong/include/numkong/set.h +471 -0
  293. package/numkong/include/numkong/set.hpp +114 -0
  294. package/numkong/include/numkong/sets/README.md +149 -0
  295. package/numkong/include/numkong/sets/haswell.h +63 -0
  296. package/numkong/include/numkong/sets/icelake.h +66 -0
  297. package/numkong/include/numkong/sets/loongsonasx.h +52 -0
  298. package/numkong/include/numkong/sets/neon.h +61 -0
  299. package/numkong/include/numkong/sets/powervsx.h +65 -0
  300. package/numkong/include/numkong/sets/serial.h +43 -0
  301. package/numkong/include/numkong/sets/smebi32.h +1135 -0
  302. package/numkong/include/numkong/sets/v128relaxed.h +58 -0
  303. package/numkong/include/numkong/sets.h +382 -0
  304. package/numkong/include/numkong/sparse/README.md +156 -0
  305. package/numkong/include/numkong/sparse/icelake.h +463 -0
  306. package/numkong/include/numkong/sparse/neon.h +288 -0
  307. package/numkong/include/numkong/sparse/serial.h +117 -0
  308. package/numkong/include/numkong/sparse/sve2.h +504 -0
  309. package/numkong/include/numkong/sparse/turin.h +322 -0
  310. package/numkong/include/numkong/sparse.h +363 -0
  311. package/numkong/include/numkong/sparse.hpp +113 -0
  312. package/numkong/include/numkong/spatial/README.md +439 -0
  313. package/numkong/include/numkong/spatial/alder.h +604 -0
  314. package/numkong/include/numkong/spatial/diamond.h +240 -0
  315. package/numkong/include/numkong/spatial/genoa.h +222 -0
  316. package/numkong/include/numkong/spatial/haswell.h +979 -0
  317. package/numkong/include/numkong/spatial/icelake.h +1067 -0
  318. package/numkong/include/numkong/spatial/loongsonasx.h +483 -0
  319. package/numkong/include/numkong/spatial/neon.h +846 -0
  320. package/numkong/include/numkong/spatial/neonbfdot.h +164 -0
  321. package/numkong/include/numkong/spatial/neonfp8.h +258 -0
  322. package/numkong/include/numkong/spatial/neonsdot.h +429 -0
  323. package/numkong/include/numkong/spatial/powervsx.h +738 -0
  324. package/numkong/include/numkong/spatial/rvv.h +991 -0
  325. package/numkong/include/numkong/spatial/rvvbf16.h +128 -0
  326. package/numkong/include/numkong/spatial/rvvhalf.h +120 -0
  327. package/numkong/include/numkong/spatial/serial.h +347 -0
  328. package/numkong/include/numkong/spatial/sierra.h +516 -0
  329. package/numkong/include/numkong/spatial/skylake.h +605 -0
  330. package/numkong/include/numkong/spatial/sve.h +246 -0
  331. package/numkong/include/numkong/spatial/svebfdot.h +125 -0
  332. package/numkong/include/numkong/spatial/svehalf.h +136 -0
  333. package/numkong/include/numkong/spatial/svesdot.h +143 -0
  334. package/numkong/include/numkong/spatial/v128relaxed.h +969 -0
  335. package/numkong/include/numkong/spatial.h +1681 -0
  336. package/numkong/include/numkong/spatial.hpp +183 -0
  337. package/numkong/include/numkong/spatials/README.md +580 -0
  338. package/numkong/include/numkong/spatials/alder.h +94 -0
  339. package/numkong/include/numkong/spatials/diamond.h +82 -0
  340. package/numkong/include/numkong/spatials/genoa.h +94 -0
  341. package/numkong/include/numkong/spatials/graniteamx.h +173 -0
  342. package/numkong/include/numkong/spatials/haswell.h +219 -0
  343. package/numkong/include/numkong/spatials/icelake.h +113 -0
  344. package/numkong/include/numkong/spatials/loongsonasx.h +153 -0
  345. package/numkong/include/numkong/spatials/neon.h +109 -0
  346. package/numkong/include/numkong/spatials/neonbfdot.h +60 -0
  347. package/numkong/include/numkong/spatials/neonfhm.h +92 -0
  348. package/numkong/include/numkong/spatials/neonfp8.h +111 -0
  349. package/numkong/include/numkong/spatials/neonsdot.h +143 -0
  350. package/numkong/include/numkong/spatials/powervsx.h +153 -0
  351. package/numkong/include/numkong/spatials/rvv.h +1976 -0
  352. package/numkong/include/numkong/spatials/sapphireamx.h +1149 -0
  353. package/numkong/include/numkong/spatials/serial.h +248 -0
  354. package/numkong/include/numkong/spatials/sierra.h +96 -0
  355. package/numkong/include/numkong/spatials/skylake.h +184 -0
  356. package/numkong/include/numkong/spatials/sme.h +1927 -0
  357. package/numkong/include/numkong/spatials/smef64.h +483 -0
  358. package/numkong/include/numkong/spatials/v128relaxed.h +240 -0
  359. package/numkong/include/numkong/spatials.h +3374 -0
  360. package/numkong/include/numkong/spatials.hpp +509 -0
  361. package/numkong/include/numkong/tensor.hpp +1981 -0
  362. package/numkong/include/numkong/trigonometry/README.md +184 -0
  363. package/numkong/include/numkong/trigonometry/haswell.h +653 -0
  364. package/numkong/include/numkong/trigonometry/neon.h +638 -0
  365. package/numkong/include/numkong/trigonometry/rvv.h +700 -0
  366. package/numkong/include/numkong/trigonometry/serial.h +703 -0
  367. package/numkong/include/numkong/trigonometry/skylake.h +725 -0
  368. package/numkong/include/numkong/trigonometry/v128relaxed.h +667 -0
  369. package/numkong/include/numkong/trigonometry.h +467 -0
  370. package/numkong/include/numkong/trigonometry.hpp +166 -0
  371. package/numkong/include/numkong/types.h +1695 -0
  372. package/numkong/include/numkong/types.hpp +6027 -0
  373. package/numkong/include/numkong/vector.hpp +766 -0
  374. package/numkong/javascript/@numkong-darwin-arm64/package.json +26 -0
  375. package/numkong/javascript/@numkong-darwin-x64/package.json +26 -0
  376. package/numkong/javascript/@numkong-linux-arm64/package.json +25 -0
  377. package/numkong/javascript/@numkong-linux-x64/package.json +25 -0
  378. package/numkong/javascript/@numkong-win32-arm64/package.json +25 -0
  379. package/numkong/javascript/@numkong-win32-x64/package.json +25 -0
  380. package/numkong/javascript/README.md +254 -0
  381. package/numkong/javascript/node-gyp-build.d.ts +4 -0
  382. package/numkong/javascript/numkong-browser.ts +40 -0
  383. package/numkong/javascript/numkong-wasm.ts +750 -0
  384. package/numkong/javascript/numkong.c +712 -0
  385. package/numkong/javascript/numkong.ts +613 -0
  386. package/{simsimd → numkong}/javascript/tsconfig-base.json +6 -3
  387. package/numkong/javascript/tsconfig-cjs.json +13 -0
  388. package/{simsimd → numkong}/javascript/tsconfig-esm.json +1 -1
  389. package/numkong/javascript/types.ts +784 -0
  390. package/numkong/numkong.gypi +104 -0
  391. package/numkong/package.json +108 -0
  392. package/numkong/probes/arm_neon.c +8 -0
  393. package/numkong/probes/arm_neon_bfdot.c +9 -0
  394. package/numkong/probes/arm_neon_fhm.c +9 -0
  395. package/numkong/probes/arm_neon_half.c +8 -0
  396. package/numkong/probes/arm_neon_sdot.c +9 -0
  397. package/numkong/probes/arm_neonfp8.c +9 -0
  398. package/numkong/probes/arm_sme.c +16 -0
  399. package/numkong/probes/arm_sme2.c +16 -0
  400. package/numkong/probes/arm_sme2p1.c +16 -0
  401. package/numkong/probes/arm_sme_bf16.c +16 -0
  402. package/numkong/probes/arm_sme_bi32.c +16 -0
  403. package/numkong/probes/arm_sme_f64.c +16 -0
  404. package/numkong/probes/arm_sme_fa64.c +14 -0
  405. package/numkong/probes/arm_sme_half.c +16 -0
  406. package/numkong/probes/arm_sme_lut2.c +15 -0
  407. package/numkong/probes/arm_sve.c +18 -0
  408. package/numkong/probes/arm_sve2.c +20 -0
  409. package/numkong/probes/arm_sve2p1.c +18 -0
  410. package/numkong/probes/arm_sve_bfdot.c +20 -0
  411. package/numkong/probes/arm_sve_half.c +18 -0
  412. package/numkong/probes/arm_sve_sdot.c +21 -0
  413. package/numkong/probes/loongarch_lasx.c +12 -0
  414. package/numkong/probes/power_vsx.c +12 -0
  415. package/numkong/probes/probe.js +127 -0
  416. package/numkong/probes/riscv_rvv.c +14 -0
  417. package/numkong/probes/riscv_rvv_bb.c +15 -0
  418. package/numkong/probes/riscv_rvv_bf16.c +17 -0
  419. package/numkong/probes/riscv_rvv_half.c +14 -0
  420. package/numkong/probes/wasm_v128relaxed.c +11 -0
  421. package/numkong/probes/x86_alder.c +17 -0
  422. package/numkong/probes/x86_diamond.c +17 -0
  423. package/numkong/probes/x86_genoa.c +17 -0
  424. package/numkong/probes/x86_graniteamx.c +19 -0
  425. package/numkong/probes/x86_haswell.c +11 -0
  426. package/numkong/probes/x86_icelake.c +17 -0
  427. package/numkong/probes/x86_sapphire.c +16 -0
  428. package/numkong/probes/x86_sapphireamx.c +18 -0
  429. package/numkong/probes/x86_sierra.c +17 -0
  430. package/numkong/probes/x86_skylake.c +15 -0
  431. package/numkong/probes/x86_turin.c +17 -0
  432. package/numkong/rust/README.md +698 -0
  433. package/numkong/rust/capabilities.rs +96 -0
  434. package/numkong/rust/cast.rs +314 -0
  435. package/numkong/rust/curved.rs +391 -0
  436. package/numkong/rust/each.rs +2721 -0
  437. package/numkong/rust/geospatial.rs +343 -0
  438. package/numkong/rust/matrix.rs +5424 -0
  439. package/numkong/rust/maxsim.rs +540 -0
  440. package/numkong/rust/mesh.rs +837 -0
  441. package/numkong/rust/numkong.rs +409 -0
  442. package/numkong/rust/probability.rs +272 -0
  443. package/numkong/rust/reduce.rs +1249 -0
  444. package/numkong/rust/set.rs +194 -0
  445. package/numkong/rust/sparse.rs +513 -0
  446. package/numkong/rust/spatial.rs +1815 -0
  447. package/numkong/rust/tensor.rs +7565 -0
  448. package/numkong/rust/types.rs +3666 -0
  449. package/numkong/rust/vector.rs +1899 -0
  450. package/numkong/rustfmt.toml +2 -0
  451. package/numkong/swift/Matrix.swift +1388 -0
  452. package/numkong/swift/MaxSim.swift +151 -0
  453. package/numkong/swift/NumKong.swift +283 -0
  454. package/numkong/swift/Numerics.swift +329 -0
  455. package/numkong/swift/README.md +433 -0
  456. package/numkong/swift/Scalar.swift +741 -0
  457. package/numkong/swift/Tensor.swift +209 -0
  458. package/numkong/test/README.md +315 -0
  459. package/numkong/test/playwright.config.ts +16 -0
  460. package/numkong/test/swift/Test.swift +534 -0
  461. package/numkong/test/test-browser-bundle-runner.mjs +36 -0
  462. package/numkong/test/test-browser-bundle.html +188 -0
  463. package/numkong/test/test-browser-runner.mjs +43 -0
  464. package/numkong/test/test-browser.html +305 -0
  465. package/numkong/test/test-dtypes.mjs +453 -0
  466. package/numkong/test/test-wasm.mjs +267 -0
  467. package/numkong/test/test.cpp +442 -0
  468. package/numkong/test/test.hpp +580 -0
  469. package/numkong/test/test.mjs +363 -0
  470. package/numkong/test/test_base.py +1041 -0
  471. package/numkong/test/test_cast.cpp +242 -0
  472. package/numkong/test/test_cross.hpp +551 -0
  473. package/numkong/test/test_cross.py +467 -0
  474. package/numkong/test/test_cross_amx.cpp +50 -0
  475. package/numkong/test/test_cross_arm.cpp +225 -0
  476. package/numkong/test/test_cross_blas.cpp +354 -0
  477. package/numkong/test/test_cross_loongarch.cpp +90 -0
  478. package/numkong/test/test_cross_power.cpp +73 -0
  479. package/numkong/test/test_cross_rvv.cpp +109 -0
  480. package/numkong/test/test_cross_serial.cpp +181 -0
  481. package/numkong/test/test_cross_sme.cpp +148 -0
  482. package/numkong/test/test_cross_wasm.cpp +141 -0
  483. package/numkong/test/test_cross_x86.cpp +442 -0
  484. package/numkong/test/test_curved.cpp +182 -0
  485. package/numkong/test/test_curved.py +207 -0
  486. package/numkong/test/test_dot.cpp +353 -0
  487. package/numkong/test/test_dot.py +296 -0
  488. package/numkong/test/test_each.cpp +437 -0
  489. package/numkong/test/test_each.py +756 -0
  490. package/numkong/test/test_geospatial.cpp +155 -0
  491. package/numkong/test/test_geospatial.py +241 -0
  492. package/numkong/test/test_maxsim.cpp +138 -0
  493. package/numkong/test/test_maxsim.py +209 -0
  494. package/numkong/test/test_mesh.cpp +203 -0
  495. package/numkong/test/test_mesh.py +394 -0
  496. package/numkong/test/test_probability.cpp +136 -0
  497. package/numkong/test/test_probability.py +166 -0
  498. package/numkong/test/test_reduce.cpp +563 -0
  499. package/numkong/test/test_reduce.py +781 -0
  500. package/numkong/test/test_set.cpp +151 -0
  501. package/numkong/test/test_set.py +120 -0
  502. package/numkong/test/test_similarities.py +579 -0
  503. package/numkong/test/test_sparse.cpp +121 -0
  504. package/numkong/test/test_sparse.py +118 -0
  505. package/numkong/test/test_spatial.cpp +518 -0
  506. package/numkong/test/test_spatial.py +269 -0
  507. package/numkong/test/test_tensor.cpp +884 -0
  508. package/numkong/test/test_tensor.py +1417 -0
  509. package/numkong/test/test_trigonometry.cpp +156 -0
  510. package/numkong/test/test_trigonometry.py +180 -0
  511. package/package.json +7 -8
  512. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  513. package/prebuilds/linux-arm64/usearch.node +0 -0
  514. package/prebuilds/linux-x64/usearch.node +0 -0
  515. package/fp16/CMakeLists.txt +0 -176
  516. package/fp16/LICENSE +0 -11
  517. package/fp16/README.md +0 -20
  518. package/fp16/bench/alt-element.cc +0 -89
  519. package/fp16/bench/from-alt-array.cc +0 -175
  520. package/fp16/bench/from-ieee-array.cc +0 -399
  521. package/fp16/bench/ieee-element.cc +0 -272
  522. package/fp16/bench/to-alt-array.cc +0 -91
  523. package/fp16/bench/to-ieee-array.cc +0 -303
  524. package/fp16/cmake/DownloadGoogleBenchmark.cmake +0 -15
  525. package/fp16/cmake/DownloadGoogleTest.cmake +0 -15
  526. package/fp16/cmake/DownloadPSimd.cmake +0 -15
  527. package/fp16/configure.py +0 -65
  528. package/fp16/confu.yaml +0 -8
  529. package/fp16/include/fp16/__init__.py +0 -0
  530. package/fp16/include/fp16/avx.py +0 -50
  531. package/fp16/include/fp16/avx2.py +0 -53
  532. package/fp16/include/fp16/bitcasts.h +0 -92
  533. package/fp16/include/fp16/fp16.h +0 -451
  534. package/fp16/include/fp16/psimd.h +0 -131
  535. package/fp16/include/fp16.h +0 -11
  536. package/fp16/jni/Android.mk +0 -6
  537. package/fp16/jni/Application.mk +0 -4
  538. package/fp16/test/alt-from-fp32-value.cc +0 -498
  539. package/fp16/test/alt-to-fp32-bits.cc +0 -263
  540. package/fp16/test/alt-to-fp32-psimd.cc +0 -145
  541. package/fp16/test/alt-to-fp32-value.cc +0 -348
  542. package/fp16/test/alt-to-fp32x2-psimd.cc +0 -245
  543. package/fp16/test/bitcasts.cc +0 -56
  544. package/fp16/test/ieee-from-fp32-value.cc +0 -523
  545. package/fp16/test/ieee-to-fp32-bits.cc +0 -333
  546. package/fp16/test/ieee-to-fp32-psimd.cc +0 -312
  547. package/fp16/test/ieee-to-fp32-value.cc +0 -420
  548. package/fp16/test/ieee-to-fp32x2-psimd.cc +0 -541
  549. package/fp16/test/peachpy/alt-xmm-to-fp32-xmm-avx.cc +0 -96
  550. package/fp16/test/peachpy/alt-xmm-to-fp32-ymm-avx2.cc +0 -96
  551. package/fp16/test/peachpy/stubs.py +0 -38
  552. package/fp16/test/tables.cc +0 -530
  553. package/fp16/test/tables.h +0 -13
  554. package/fp16/third-party/THHalf.h +0 -92
  555. package/fp16/third-party/eigen-half.h +0 -170
  556. package/fp16/third-party/float16-compressor.h +0 -80
  557. package/fp16/third-party/half.hpp +0 -2916
  558. package/fp16/third-party/npy-halffloat.h +0 -328
  559. package/simsimd/.git-blame-ignore-revs +0 -2
  560. package/simsimd/CONTRIBUTING.md +0 -321
  561. package/simsimd/Cargo.toml +0 -49
  562. package/simsimd/MANIFEST.in +0 -4
  563. package/simsimd/Package.swift +0 -28
  564. package/simsimd/README.md +0 -1493
  565. package/simsimd/binding.gyp +0 -35
  566. package/simsimd/build.rs +0 -70
  567. package/simsimd/c/lib.c +0 -374
  568. package/simsimd/include/module.modulemap +0 -4
  569. package/simsimd/include/simsimd/binary.h +0 -486
  570. package/simsimd/include/simsimd/curved.h +0 -1541
  571. package/simsimd/include/simsimd/dot.h +0 -1853
  572. package/simsimd/include/simsimd/elementwise.h +0 -2465
  573. package/simsimd/include/simsimd/geospatial.h +0 -43
  574. package/simsimd/include/simsimd/mesh.h +0 -69
  575. package/simsimd/include/simsimd/probability.h +0 -606
  576. package/simsimd/include/simsimd/simsimd.h +0 -2567
  577. package/simsimd/include/simsimd/sparse.h +0 -1384
  578. package/simsimd/include/simsimd/spatial.h +0 -2335
  579. package/simsimd/include/simsimd/types.h +0 -666
  580. package/simsimd/javascript/fallback.ts +0 -233
  581. package/simsimd/javascript/lib.c +0 -125
  582. package/simsimd/javascript/node-gyp-build.d.ts +0 -1
  583. package/simsimd/javascript/simsimd.ts +0 -179
  584. package/simsimd/javascript/tsconfig-cjs.json +0 -8
  585. package/simsimd/package-lock.json +0 -1761
  586. package/simsimd/package.json +0 -61
  587. package/simsimd/rust/lib.rs +0 -1821
  588. package/simsimd/scripts/bench.cxx +0 -1170
  589. package/simsimd/scripts/bench.ipynb +0 -187
  590. package/simsimd/scripts/bench.js +0 -127
  591. package/simsimd/scripts/bench.rs +0 -876
  592. package/simsimd/scripts/bench_vectors.py +0 -734
  593. package/simsimd/scripts/bench_vectors_live.py +0 -213
  594. package/simsimd/scripts/test.c +0 -187
  595. package/simsimd/scripts/test.mjs +0 -220
  596. package/simsimd/scripts/test.py +0 -1505
  597. package/simsimd/swift/SimSIMD.swift +0 -149
  598. package/simsimd/swift/Test.swift +0 -128
  599. /package/{simsimd → numkong}/CODE_OF_CONDUCT.md +0 -0
  600. /package/{simsimd → numkong}/LICENSE +0 -0
  601. /package/{simsimd → numkong}/javascript/dist-package-cjs.json +0 -0
  602. /package/{simsimd → numkong}/javascript/dist-package-esm.json +0 -0
@@ -0,0 +1,1174 @@
1
+ /**
2
+ * @brief SIMD-accelerated Type Conversions for NEON.
3
+ * @file include/numkong/cast/neon.h
4
+ * @author Ash Vardanian
5
+ * @date December 27, 2025
6
+ *
7
+ * @sa include/numkong/cast.h
8
+ *
9
+ * @section neon_cast_instructions ARM NEON Conversion Instructions
10
+ *
11
+ * Float ↔ integer conversions:
12
+ *
13
+ * Intrinsic Instruction A76 M5
14
+ * vcvtq_f32_s32 SCVTF (V.4S, V.4S) 3cy @ 2p 3cy @ 4p
15
+ * vcvtq_f32_u32 UCVTF (V.4S, V.4S) 3cy @ 2p 3cy @ 4p
16
+ * vcvtq_s32_f32 FCVTZS (V.4S, V.4S) 3cy @ 2p 3cy @ 4p
17
+ * vcvtq_u32_f32 FCVTZU (V.4S, V.4S) 3cy @ 2p 3cy @ 4p
18
+ *
19
+ * Float precision conversions:
20
+ *
21
+ * Intrinsic Instruction A76 M5
22
+ * vcvt_f32_f16 FCVTL (V.4S, V.4H) 3cy @ 2p 3cy @ 4p
23
+ * vcvt_f16_f32 FCVTN (V.4H, V.4S) 3cy @ 2p 3cy @ 4p
24
+ * vcvt_f64_f32 FCVTL (V.2D, V.2S) 3cy @ 2p 3cy @ 4p
25
+ * vcvt_f32_f64 FCVTN (V.2S, V.2D) 3cy @ 2p 3cy @ 4p
26
+ *
27
+ * Integer narrowing with saturation:
28
+ *
29
+ * Intrinsic Instruction A76 M5
30
+ * vqmovn_s32 SQXTN (V.4H, V.4S) 3cy @ 2p 3cy @ 4p
31
+ * vqmovn_u32 UQXTN (V.4H, V.4S) 3cy @ 2p 3cy @ 4p
32
+ * vqmovun_s32 SQXTUN (V.4H, V.4S) 3cy @ 2p 3cy @ 4p
33
+ *
34
+ * BF16 support (ARMv8.6-A+):
35
+ *
36
+ * Intrinsic Instruction A76 M5
37
+ * vcvtq_low_bf16_f32 BFCVTN (V.4H, V.4S) 3cy @ 2p 3cy @ 4p
38
+ * vcvtq_high_bf16_f32 BFCVTN2 (V.8H, V.4S) 3cy @ 2p 3cy @ 4p
39
+ *
40
+ * BF16 conversions on baseline NEON (emulated via bit shifts):
41
+ * - bf16 → f32: vmovl_u16 + vshlq_n_u32 by 16
42
+ * - f32 → bf16: round-to-nearest + vshrn_n_u32 by 16
43
+ *
44
+ * FP8 (E4M3/E5M2) conversions use NEON bit manipulation:
45
+ * - Field extraction: vandq, vshrq, vshlq
46
+ * - Blending: vbslq for conditional selection
47
+ * - Subnormal handling: vmulq_n_f32 with scale factors (1/512, 1/65536)
48
+ */
49
+ #ifndef NK_CAST_NEON_H
50
+ #define NK_CAST_NEON_H
51
+
52
+ #if NK_TARGET_ARM64_
53
+ #if NK_TARGET_NEON
54
+
55
+ #include "numkong/types.h"
56
+ #include "numkong/cast/serial.h" // `nk_cast_serial`, `nk_dtype_bits`
57
+ #include "numkong/reduce/serial.h" // `nk_reduce_moments_f32_serial`
58
+
59
+ #if defined(__cplusplus)
60
+ extern "C" {
61
+ #endif
62
+
63
+ #if defined(__clang__)
64
+ #pragma clang attribute push(__attribute__((target("arch=armv8-a+simd"))), apply_to = function)
65
+ #elif defined(__GNUC__)
66
+ #pragma GCC push_options
67
+ #pragma GCC target("arch=armv8-a+simd")
68
+ #endif
69
+
70
+ NK_PUBLIC void nk_f16_to_f32_neon(nk_f16_t const *src, nk_f32_t *dest) {
71
+ float16x4_t f16_f16x4 = vreinterpret_f16_u16(vld1_dup_u16((nk_u16_t const *)src));
72
+ float32x4_t f32_f32x4 = vcvt_f32_f16(f16_f16x4);
73
+ *dest = vgetq_lane_f32(f32_f32x4, 0);
74
+ }
75
+
76
+ NK_PUBLIC void nk_f32_to_f16_neon(nk_f32_t const *src, nk_f16_t *dest) {
77
+ float32x4_t f32_f32x4 = vdupq_n_f32(*src);
78
+ float16x4_t f16_f16x4 = vcvt_f16_f32(f32_f32x4);
79
+ vst1_lane_u16((nk_u16_t *)dest, vreinterpret_u16_f16(f16_f16x4), 0);
80
+ }
81
+
82
+ #pragma region Type Punned Loads and Stores
83
+
84
+ /** @brief Type-agnostic 128-bit full load (NEON). */
85
+ NK_INTERNAL void nk_load_b128_neon_(void const *src, nk_b128_vec_t *dst) {
86
+ dst->u8x16 = vld1q_u8((nk_u8_t const *)src);
87
+ }
88
+
89
+ /** @brief Type-agnostic 256-bit full load (NEON). */
90
+ NK_INTERNAL void nk_load_b256_neon_(void const *src, nk_b256_vec_t *dst) {
91
+ dst->u8x16s[0] = vld1q_u8((nk_u8_t const *)src);
92
+ dst->u8x16s[1] = vld1q_u8((nk_u8_t const *)src + 16);
93
+ }
94
+
95
+ /** @brief Type-agnostic 128-bit full store (NEON). */
96
+ NK_INTERNAL void nk_store_b128_neon_(nk_b128_vec_t const *src, void *dst) { vst1q_u8((nk_u8_t *)dst, src->u8x16); }
97
+
98
+ /** @brief Type-agnostic 256-bit full store (NEON). */
99
+ NK_INTERNAL void nk_store_b256_neon_(nk_b256_vec_t const *src, void *dst) {
100
+ vst1q_u8((nk_u8_t *)dst, src->u8x16s[0]);
101
+ vst1q_u8((nk_u8_t *)dst + 16, src->u8x16s[1]);
102
+ }
103
+
104
+ /** @brief Type-agnostic 64-bit full load (NEON). */
105
+ NK_INTERNAL void nk_load_b64_neon_(void const *src, nk_b64_vec_t *dst) { dst->u8x8 = vld1_u8((nk_u8_t const *)src); }
106
+
107
+ /**
108
+ * @brief 8-lane `uint16x8_t` splat that hides the source from the optimizer.
109
+ *
110
+ * GCC 13 lowers `vdupq_n_u16(X)` to `fmov v.8h, #imm` (a FEAT_FP16 encoding) whenever X matches a
111
+ * representable FP16 immediate, including bf16 bit patterns like 1.0 (`0x3F80`). That fails to
112
+ * assemble under a `+bf16`-only pragma. The empty `__asm__` constraint forces `mov w; dup v.8h, w`
113
+ * instead, valid on plain `armv8-a+simd`. No-op on Clang; skipped on MSVC (neither is affected).
114
+ */
115
+ NK_INTERNAL uint16x8_t nk_u16x8_splat_(nk_u16_t bits) {
116
+ #if defined(__GNUC__) || defined(__clang__)
117
+ __asm__("" : "+r"(bits));
118
+ #endif
119
+ return vdupq_n_u16(bits);
120
+ }
121
+
122
+ #pragma endregion Type Punned Loads and Stores
123
+
124
+ #pragma region Vectorized Conversions
125
+
126
+ /** @brief Convert 4x e4m3 → f32x4 via Giesen magic-multiply (NEON).
127
+ * Reinterprets magnitude bits as a tiny f32, then multiplies by 2^(127-bias) to rebias.
128
+ * Handles zero, subnormals, and normals in a single VMUL. NaN fixup for magnitude 0x7F.
129
+ * https://fgiesen.wordpress.com/2012/03/28/half-to-float-done-quic/ */
130
+ NK_INTERNAL float32x4_t nk_e4m3x4_to_f32x4_neon_(nk_b32_vec_t src) {
131
+ uint8x8_t e4m3_u8x8 = vcreate_u8(src.u32);
132
+ uint16x8_t e4m3_u16x8 = vmovl_u8(e4m3_u8x8);
133
+ uint32x4_t e4m3_u32x4 = vmovl_u16(vget_low_u16(e4m3_u16x8));
134
+
135
+ // Extract sign: (raw & 0x80) << 24 → f32 sign bit
136
+ uint32x4_t sign_u32x4 = vshlq_n_u32(vandq_u32(e4m3_u32x4, vdupq_n_u32(0x80)), 24);
137
+ // Strip sign to get 7-bit magnitude, shift left by 20 so E4M3 exponent overlaps f32 exponent
138
+ uint32x4_t nonsign_u32x4 = vandq_u32(e4m3_u32x4, vdupq_n_u32(0x7F));
139
+ uint32x4_t shifted_u32x4 = vshlq_n_u32(nonsign_u32x4, 20);
140
+
141
+ // Magic multiply: reinterpret as f32 × 2^120 rebiases from E4M3 (bias=7) to f32 (bias=127).
142
+ float32x4_t result_f32x4 = vmulq_f32(vreinterpretq_f32_u32(shifted_u32x4),
143
+ vreinterpretq_f32_u32(vdupq_n_u32(0x7B800000))); // 2^120
144
+
145
+ // NaN fixup: E4M3FN NaN only at magnitude 0x7F → force to f32 quiet NaN
146
+ uint32x4_t is_nan_mask_u32x4 = vceqq_u32(nonsign_u32x4, vdupq_n_u32(0x7F));
147
+ uint32x4_t nan_u32x4 = vorrq_u32(sign_u32x4, vdupq_n_u32(0x7FC00000));
148
+ uint32x4_t result_u32x4 = vbslq_u32(is_nan_mask_u32x4, nan_u32x4, vreinterpretq_u32_f32(result_f32x4));
149
+
150
+ // Restore sign
151
+ return vreinterpretq_f32_u32(vorrq_u32(result_u32x4, sign_u32x4));
152
+ }
153
+
154
+ /** @brief Convert 4x e5m2 → f32x4 via Giesen magic-multiply (NEON).
155
+ * Reinterprets magnitude bits as a tiny f32, then multiplies by 2^(127-bias) to rebias.
156
+ * Handles zero, subnormals, and normals in a single VMUL. Inf/NaN fixup for exp=31.
157
+ * https://fgiesen.wordpress.com/2012/03/28/half-to-float-done-quic/ */
158
+ NK_INTERNAL float32x4_t nk_e5m2x4_to_f32x4_neon_(nk_b32_vec_t src) {
159
+ uint8x8_t e5m2_u8x8 = vcreate_u8(src.u32);
160
+ uint16x8_t e5m2_u16x8 = vmovl_u8(e5m2_u8x8);
161
+ uint32x4_t e5m2_u32x4 = vmovl_u16(vget_low_u16(e5m2_u16x8));
162
+
163
+ // Extract sign: (raw & 0x80) << 24 → f32 sign bit
164
+ uint32x4_t sign_u32x4 = vshlq_n_u32(vandq_u32(e5m2_u32x4, vdupq_n_u32(0x80)), 24);
165
+ // Strip sign to get 7-bit magnitude, shift left by 21 so E5M2 exponent overlaps f32 exponent
166
+ uint32x4_t nonsign_u32x4 = vandq_u32(e5m2_u32x4, vdupq_n_u32(0x7F));
167
+ uint32x4_t shifted_u32x4 = vshlq_n_u32(nonsign_u32x4, 21);
168
+
169
+ // Magic multiply: reinterpret as f32 × 2^112 rebiases from E5M2 (bias=15) to f32 (bias=127).
170
+ float32x4_t result_f32x4 = vmulq_f32(vreinterpretq_f32_u32(shifted_u32x4),
171
+ vreinterpretq_f32_u32(vdupq_n_u32(0x77800000))); // 2^112
172
+
173
+ // Inf/NaN fixup: nonsign > 123 means exp=31 → force f32 exponent to 255
174
+ uint32x4_t is_infnan_u32x4 = vcgtq_u32(nonsign_u32x4, vdupq_n_u32(123));
175
+ uint32x4_t result_u32x4 = vorrq_u32(vreinterpretq_u32_f32(result_f32x4),
176
+ vandq_u32(is_infnan_u32x4, vdupq_n_u32(0x7F800000)));
177
+
178
+ // Restore sign
179
+ return vreinterpretq_f32_u32(vorrq_u32(result_u32x4, sign_u32x4));
180
+ }
181
+
182
+ /** @brief Convert 8x e4m3 → f16x8 via bit manipulation (NEON).
183
+ * E4M3FN format: S EEEE MMM (bias=7). F16: S EEEEE MMMMMMMMMM (bias=15).
184
+ * E4M3FN has no ∞; only exp=15, mant=7 is NaN. exp=15, mant ∈ [0,6] are valid normals. */
185
+ NK_INTERNAL float16x8_t nk_e4m3x8_to_f16x8_neon_(uint8x8_t e4m3_u8x8) {
186
+ uint16x8_t e4m3_u16x8 = vmovl_u8(e4m3_u8x8);
187
+ uint16x8_t sign_u16x8 = vshlq_n_u16(vandq_u16(e4m3_u16x8, vdupq_n_u16(0x80)), 8); // sign << 15
188
+ uint16x8_t exp_u16x8 = vandq_u16(vshrq_n_u16(e4m3_u16x8, 3), vdupq_n_u16(0x0F));
189
+ uint16x8_t mant_u16x8 = vandq_u16(e4m3_u16x8, vdupq_n_u16(0x07));
190
+
191
+ // Normal path: F16_exp = E4M3_exp + 8, F16_mant = E4M3_mant << 7
192
+ uint16x8_t f16_exp_u16x8 = vshlq_n_u16(vaddq_u16(exp_u16x8, vdupq_n_u16(8)), 10);
193
+ uint16x8_t f16_mant_u16x8 = vshlq_n_u16(mant_u16x8, 7);
194
+ uint16x8_t normal_u16x8 = vorrq_u16(sign_u16x8, vorrq_u16(f16_exp_u16x8, f16_mant_u16x8));
195
+
196
+ // Subnormal path (exp=0, mant ≠ 0): E4M3 subnormal value = mant × 2⁻⁹ = mant ÷ 512
197
+ // Compute arithmetically: mant → f32 → multiply → f16
198
+ float32x4_t subnormal_low_f32x4 = vmulq_n_f32(vcvtq_f32_u32(vmovl_u16(vget_low_u16(mant_u16x8))), 1.0f / 512.0f);
199
+ float32x4_t subnormal_high_f32x4 = vmulq_n_f32(vcvtq_f32_u32(vmovl_high_u16(mant_u16x8)), 1.0f / 512.0f);
200
+ uint16x8_t subnormal_abs_u16x8 = vreinterpretq_u16_f16(
201
+ vcombine_f16(vcvt_f16_f32(subnormal_low_f32x4), vcvt_f16_f32(subnormal_high_f32x4)));
202
+ uint16x8_t subnormal_u16x8 = vorrq_u16(subnormal_abs_u16x8, sign_u16x8);
203
+
204
+ // NaN path: E4M3FN only has NaN when exp=15 AND mant=7 (0x7F or 0xFF)
205
+ uint16x8_t nan_u16x8 = vorrq_u16(sign_u16x8, vdupq_n_u16(0x7E00)); // F16 quiet NaN
206
+ uint16x8_t is_nan_mask_u16x8 = vandq_u16(vceqq_u16(exp_u16x8, vdupq_n_u16(15)),
207
+ vceqq_u16(mant_u16x8, vdupq_n_u16(7)));
208
+
209
+ // Blend paths: subnormal when exp=0, NaN when exp=15 && mant=7, else normal
210
+ uint16x8_t exp_zero_mask_u16x8 = vceqq_u16(exp_u16x8, vdupq_n_u16(0));
211
+ uint16x8_t result_u16x8 = vbslq_u16(exp_zero_mask_u16x8, subnormal_u16x8, normal_u16x8);
212
+ result_u16x8 = vbslq_u16(is_nan_mask_u16x8, nan_u16x8, result_u16x8);
213
+ return vreinterpretq_f16_u16(result_u16x8);
214
+ }
215
+
216
+ /** @brief Convert 16x e4m3 → 2x f16x8 via TBL lookup (NEON).
217
+ * E4M3FN format: S EEEE MMM (bias=7) → F16: S EEEEE MMMMMMMMMM (bias=15).
218
+ * Uses sign symmetry: negative LUT entries = positive XOR 0x80, so we strip sign,
219
+ * lookup 7-bit absolute value in 2× VQTBL4 (128 bytes), then OR sign back.
220
+ * Arithmetic for the low byte: (has_exp && !nan) ? (lsb << 7) : 0.
221
+ * Exact for all 256 input values including subnormals and NaN.
222
+ *
223
+ * Performance (per 16 elements): ~10-12 instructions vs ~40 for 2× nk_e4m3x8_to_f16x8_neon_ */
224
+ NK_INTERNAL void nk_e4m3x16_to_f16x8x2_neon_(uint8x16_t input_u8x16, float16x8_t *result_low_f16x8,
225
+ float16x8_t *result_high_f16x8) {
226
+ // Precomputed LUT: F16 high byte for unsigned 7-bit e4m3 values (sign handled separately).
227
+ // Quadrant 0: |e4m3| bytes 0x00..0x3F (exp=0..7)
228
+ static nk_u8_t const table_q0_u8x64[64] = {
229
+ 0x00, 0x18, 0x1C, 0x1E, 0x20, 0x21, 0x22, 0x23, 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x27, 0x27,
230
+ 0x28, 0x28, 0x29, 0x29, 0x2A, 0x2A, 0x2B, 0x2B, 0x2C, 0x2C, 0x2D, 0x2D, 0x2E, 0x2E, 0x2F, 0x2F,
231
+ 0x30, 0x30, 0x31, 0x31, 0x32, 0x32, 0x33, 0x33, 0x34, 0x34, 0x35, 0x35, 0x36, 0x36, 0x37, 0x37,
232
+ 0x38, 0x38, 0x39, 0x39, 0x3A, 0x3A, 0x3B, 0x3B, 0x3C, 0x3C, 0x3D, 0x3D, 0x3E, 0x3E, 0x3F, 0x3F,
233
+ };
234
+ // Quadrant 1: |e4m3| bytes 0x40..0x7F (exp=8..15)
235
+ static nk_u8_t const table_q1_u8x64[64] = {
236
+ 0x40, 0x40, 0x41, 0x41, 0x42, 0x42, 0x43, 0x43, 0x44, 0x44, 0x45, 0x45, 0x46, 0x46, 0x47, 0x47,
237
+ 0x48, 0x48, 0x49, 0x49, 0x4A, 0x4A, 0x4B, 0x4B, 0x4C, 0x4C, 0x4D, 0x4D, 0x4E, 0x4E, 0x4F, 0x4F,
238
+ 0x50, 0x50, 0x51, 0x51, 0x52, 0x52, 0x53, 0x53, 0x54, 0x54, 0x55, 0x55, 0x56, 0x56, 0x57, 0x57,
239
+ 0x58, 0x58, 0x59, 0x59, 0x5A, 0x5A, 0x5B, 0x5B, 0x5C, 0x5C, 0x5D, 0x5D, 0x5E, 0x5E, 0x5F, 0x7E,
240
+ };
241
+
242
+ uint8x16x4_t lut_q0_u8x16x4 = vld1q_u8_x4(table_q0_u8x64);
243
+ uint8x16x4_t lut_q1_u8x16x4 = vld1q_u8_x4(table_q1_u8x64);
244
+
245
+ // Strip sign bit, work with 7-bit absolute value
246
+ uint8x16_t sign_u8x16 = vandq_u8(input_u8x16, vdupq_n_u8(0x80));
247
+ uint8x16_t abs_u8x16 = vandq_u8(input_u8x16, vdupq_n_u8(0x7F));
248
+
249
+ // High byte via 2× VQTBL4 on unsigned index, then OR sign back.
250
+ // VQTBL4 returns 0 for out-of-range indices (>= 64), so results OR together cleanly.
251
+ uint8x16_t high_q0_u8x16 = vqtbl4q_u8(lut_q0_u8x16x4, abs_u8x16);
252
+ uint8x16_t offset_q1_u8x16 = vsubq_u8(abs_u8x16, vdupq_n_u8(64));
253
+ uint8x16_t high_q1_u8x16 = vqtbl4q_u8(lut_q1_u8x16x4, offset_q1_u8x16);
254
+ uint8x16_t high_bytes_u8x16 = vorrq_u8(vorrq_u8(high_q0_u8x16, high_q1_u8x16), sign_u8x16);
255
+
256
+ // Low byte: (lsb << 7), masked to 0 for subnormals (exp=0) and NaN (exp=15, mant=7)
257
+ uint8x16_t lsb_shifted_u8x16 = vshlq_n_u8(input_u8x16, 7);
258
+ uint8x16_t exponent_bits_u8x16 = vandq_u8(input_u8x16, vdupq_n_u8(0x78));
259
+ uint8x16_t has_exp_u8x16 = vcgtq_u8(exponent_bits_u8x16, vdupq_n_u8(0));
260
+ // Exclude NaN: |input| == 0x7F means exp=15, mant=7
261
+ uint8x16_t abs_input_u8x16 = vandq_u8(input_u8x16, vdupq_n_u8(0x7F));
262
+ uint8x16_t not_nan_u8x16 = vmvnq_u8(vceqq_u8(abs_input_u8x16, vdupq_n_u8(0x7F)));
263
+ uint8x16_t low_bytes_u8x16 = vandq_u8(lsb_shifted_u8x16, vandq_u8(has_exp_u8x16, not_nan_u8x16));
264
+
265
+ // ZIP to interleave: [l0,l1..l15] + [h0,h1..h15] → [l0,h0,l1,h1..] (little-endian f16)
266
+ uint8x16x2_t interleaved_u8x16x2 = vzipq_u8(low_bytes_u8x16, high_bytes_u8x16);
267
+ *result_low_f16x8 = vreinterpretq_f16_u8(interleaved_u8x16x2.val[0]);
268
+ *result_high_f16x8 = vreinterpretq_f16_u8(interleaved_u8x16x2.val[1]);
269
+ }
270
+
271
+ /** @brief Convert 8x e5m2 → f16x8 via bit shift (NEON).
272
+ * E5M2 (bias=15) and F16 (bias=15) share the same exponent bias, so conversion is trivial.
273
+ * E5M2: S EEEEE MM → F16: S EEEEE MM 00000000. Works for all: zero, subnormal, normal, inf, nan. */
274
+ NK_INTERNAL float16x8_t nk_e5m2x8_to_f16x8_neon_(uint8x8_t e5m2_u8x8) {
275
+ uint16x8_t e5m2_u16x8 = vmovl_u8(e5m2_u8x8);
276
+ return vreinterpretq_f16_u16(vshlq_n_u16(e5m2_u16x8, 8));
277
+ }
278
+
279
+ /** @brief Convert 8x e2m3 → f16x8 via direct bit manipulation (NEON).
280
+ * E2M3FN (FP6): S EE MMM (bias=1) → F16: S EEEEE MMMMMMMMMM (bias=15).
281
+ * Handles subnormals (exp=0) via arithmetic conversion. No Inf/NaN in E2M3FN. */
282
+ NK_INTERNAL float16x8_t nk_e2m3x8_to_f16x8_neon_(uint8x8_t e2m3_u8x8) {
283
+ // Widen to 16-bit for NEON operations
284
+ uint16x8_t e2m3_u16x8 = vmovl_u8(e2m3_u8x8);
285
+
286
+ // Extract fields: format is 0b00SEEMMM (6 bits used)
287
+ uint16x8_t sign_u16x8 = vshlq_n_u16(vandq_u16(e2m3_u16x8, vdupq_n_u16(0x20)), 10); // sign << 15
288
+ uint16x8_t exp_u16x8 = vandq_u16(vshrq_n_u16(e2m3_u16x8, 3), vdupq_n_u16(0x03)); // 2-bit exp
289
+ uint16x8_t mant_u16x8 = vandq_u16(e2m3_u16x8, vdupq_n_u16(0x07)); // 3-bit mant
290
+
291
+ // Normal path: F16_exp = E2M3_exp + 14, F16_mant = E2M3_mant << 7
292
+ uint16x8_t exp_rebiased_u16x8 = vaddq_u16(exp_u16x8, vdupq_n_u16(14));
293
+ uint16x8_t exp_positioned_u16x8 = vshlq_n_u16(exp_rebiased_u16x8, 10);
294
+ uint16x8_t mant_positioned_u16x8 = vshlq_n_u16(mant_u16x8, 7);
295
+ uint16x8_t normal_u16x8 = vorrq_u16(sign_u16x8, vorrq_u16(exp_positioned_u16x8, mant_positioned_u16x8));
296
+
297
+ // Subnormal path (exp=0): E2M3 subnormal = mant / 8
298
+ // Compute via f32: mant → f32 → multiply → f16
299
+ float32x4_t subnormal_low_f32x4 = vmulq_n_f32(vcvtq_f32_u32(vmovl_u16(vget_low_u16(mant_u16x8))), 1.0f / 8.0f);
300
+ float32x4_t subnormal_high_f32x4 = vmulq_n_f32(vcvtq_f32_u32(vmovl_high_u16(mant_u16x8)), 1.0f / 8.0f);
301
+ uint16x8_t subnormal_abs_u16x8 = vreinterpretq_u16_f16(
302
+ vcombine_f16(vcvt_f16_f32(subnormal_low_f32x4), vcvt_f16_f32(subnormal_high_f32x4)));
303
+ uint16x8_t subnormal_u16x8 = vorrq_u16(subnormal_abs_u16x8, sign_u16x8);
304
+
305
+ // Blend: use subnormal result when exp=0, else normal
306
+ uint16x8_t exp_zero_mask_u16x8 = vceqq_u16(exp_u16x8, vdupq_n_u16(0));
307
+ uint16x8_t result_u16x8 = vbslq_u16(exp_zero_mask_u16x8, subnormal_u16x8, normal_u16x8);
308
+
309
+ return vreinterpretq_f16_u16(result_u16x8);
310
+ }
311
+
312
+ /** @brief Convert 8x e3m2 → f16x8 via direct bit manipulation (NEON).
313
+ * E3M2FN (FP6): S EEE MM (bias=3) → F16: S EEEEE MMMMMMMMMM (bias=15).
314
+ * Handles subnormals (exp=0) via arithmetic conversion. No Inf/NaN in E3M2FN. */
315
+ NK_INTERNAL float16x8_t nk_e3m2x8_to_f16x8_neon_(uint8x8_t e3m2_u8x8) {
316
+ // Widen to 16-bit for NEON operations
317
+ uint16x8_t e3m2_u16x8 = vmovl_u8(e3m2_u8x8);
318
+
319
+ // Extract fields: format is 0b00SEEEMM (6 bits used)
320
+ uint16x8_t sign_u16x8 = vshlq_n_u16(vandq_u16(e3m2_u16x8, vdupq_n_u16(0x20)), 10); // sign << 15
321
+ uint16x8_t exp_u16x8 = vandq_u16(vshrq_n_u16(e3m2_u16x8, 2), vdupq_n_u16(0x07)); // 3-bit exp
322
+ uint16x8_t mant_u16x8 = vandq_u16(e3m2_u16x8, vdupq_n_u16(0x03)); // 2-bit mant
323
+
324
+ // Normal path: F16_exp = E3M2_exp + 12, F16_mant = E3M2_mant << 8
325
+ uint16x8_t exp_rebiased_u16x8 = vaddq_u16(exp_u16x8, vdupq_n_u16(12));
326
+ uint16x8_t exp_positioned_u16x8 = vshlq_n_u16(exp_rebiased_u16x8, 10);
327
+ uint16x8_t mant_positioned_u16x8 = vshlq_n_u16(mant_u16x8, 8);
328
+ uint16x8_t normal_u16x8 = vorrq_u16(sign_u16x8, vorrq_u16(exp_positioned_u16x8, mant_positioned_u16x8));
329
+
330
+ // Subnormal path (exp=0): E3M2 subnormal = mant × 2^(-2) × (1/4) = mant / 16
331
+ // Compute via f32: mant → f32 → multiply → f16
332
+ float32x4_t subnormal_low_f32x4 = vmulq_n_f32(vcvtq_f32_u32(vmovl_u16(vget_low_u16(mant_u16x8))), 1.0f / 16.0f);
333
+ float32x4_t subnormal_high_f32x4 = vmulq_n_f32(vcvtq_f32_u32(vmovl_high_u16(mant_u16x8)), 1.0f / 16.0f);
334
+ uint16x8_t subnormal_abs_u16x8 = vreinterpretq_u16_f16(
335
+ vcombine_f16(vcvt_f16_f32(subnormal_low_f32x4), vcvt_f16_f32(subnormal_high_f32x4)));
336
+ uint16x8_t subnormal_u16x8 = vorrq_u16(subnormal_abs_u16x8, sign_u16x8);
337
+
338
+ // Blend: use subnormal result when exp=0, else normal
339
+ uint16x8_t exp_zero_mask_u16x8 = vceqq_u16(exp_u16x8, vdupq_n_u16(0));
340
+ uint16x8_t result_u16x8 = vbslq_u16(exp_zero_mask_u16x8, subnormal_u16x8, normal_u16x8);
341
+
342
+ return vreinterpretq_f16_u16(result_u16x8);
343
+ }
344
+
345
+ /** @brief Convert 16x e2m3 → 2x f16x8 via TBL lookup (NEON).
346
+ * E2M3FN (FP6): S EE MMM (bias=1) → F16: S EEEEE MMMMMMMMMM (bias=15).
347
+ * Uses precomputed lookup tables for 64 possible 6-bit values.
348
+ * VQTBL4 byte shuffle (p01) + VZIP interleave (p01) (~6 instructions, parallel execution) */
349
+ NK_INTERNAL void nk_e2m3x16_to_f16x8x2_neon_(uint8x16_t input_u8x16, float16x8_t *result_low_f16x8,
350
+ float16x8_t *result_high_f16x8) {
351
+ // E2M3FN → F16 conversion using TBL for high byte, arithmetic for low byte.
352
+ // Sign symmetry: negative half = positive XOR 0x80, so we strip sign (bit 5),
353
+ // lookup unsigned 5-bit value in VQTBL2 (32 bytes), then OR sign back.
354
+ // E2M3FN: sign(1) exp(2) mant(3), bias=1
355
+ // F16: sign(1) exp(5) mant(10), bias=15
356
+ // Normal (exp!=0): f16 = (sign << 15) | ((exp + 14) << 10) | (mant << 7)
357
+ // Subnormal (exp=0): f16 = mant/8 converted to f16
358
+ //
359
+ // Low byte pattern: E2M3 has 3 mantissa bits → f16 bits 9-7, so low byte (bits 7-0) is:
360
+ // - Subnormals (exp=0): always 0x00
361
+ // - Normals (exp≠0): (mant & 1) << 7 = 0x00 or 0x80
362
+ // This simple pattern can be computed arithmetically, saving table registers!
363
+ static nk_u8_t const table_high_u8x32[32] = {
364
+ 0x00, 0x30, 0x34, 0x36, 0x38, 0x39, 0x3A, 0x3B, // exp=0 (subnormals)
365
+ 0x3C, 0x3C, 0x3D, 0x3D, 0x3E, 0x3E, 0x3F, 0x3F, // exp=1 → f16_exp=15 (0x3C-0x3F)
366
+ 0x40, 0x40, 0x41, 0x41, 0x42, 0x42, 0x43, 0x43, // exp=2 → f16_exp=16 (0x40-0x43)
367
+ 0x44, 0x44, 0x45, 0x45, 0x46, 0x46, 0x47, 0x47, // exp=3 → f16_exp=17 (0x44-0x47)
368
+ };
369
+
370
+ // Load unsigned high byte table (2 registers instead of 4)
371
+ uint8x16x2_t table_high_u8x16x2 = vld1q_u8_x2(table_high_u8x32);
372
+
373
+ // Extract sign (bit 5 → bit 7 for F16 high byte) and 5-bit unsigned index
374
+ uint8x16_t sign_u8x16 = vshlq_n_u8(vandq_u8(input_u8x16, vdupq_n_u8(0x20)), 2);
375
+ uint8x16_t abs_indices_u8x16 = vandq_u8(input_u8x16, vdupq_n_u8(0x1F));
376
+
377
+ // High bytes via VQTBL2 on unsigned index, then OR sign back
378
+ uint8x16_t high_bytes_u8x16 = vorrq_u8(vqtbl2q_u8(table_high_u8x16x2, abs_indices_u8x16), sign_u8x16);
379
+
380
+ // Low bytes via arithmetic: (exp != 0) ? (bit0 << 7) : 0
381
+ // This uses shift/logic ports instead of permute port, and frees 4 registers
382
+ uint8x16_t shifted_u8x16 = vshlq_n_u8(input_u8x16, 7); // bit 0 → bit 7
383
+ uint8x16_t exp_bits_u8x16 = vandq_u8(input_u8x16, vdupq_n_u8(0x18)); // isolate exp (bits 4-3)
384
+ uint8x16_t is_normal_u8x16 = vcgtq_u8(exp_bits_u8x16, vdupq_n_u8(0)); // 0xFF if exp≠0
385
+ uint8x16_t low_bytes_u8x16 = vandq_u8(shifted_u8x16, is_normal_u8x16); // mask off subnormals
386
+
387
+ // ZIP to interleave bytes into uint16 values: [l0,l1...l15] + [h0,h1...h15] → [l0,h0,l1,h1...]
388
+ uint8x16x2_t interleaved_u8x16x2 = vzipq_u8(low_bytes_u8x16, high_bytes_u8x16);
389
+
390
+ *result_low_f16x8 = vreinterpretq_f16_u8(interleaved_u8x16x2.val[0]); // elements 0-7
391
+ *result_high_f16x8 = vreinterpretq_f16_u8(interleaved_u8x16x2.val[1]); // elements 8-15
392
+ }
393
+
394
+ /** @brief Convert 16x e3m2 → 2x f16x8 via TBL lookup (NEON).
395
+ * E3M2FN (FP6): S EEE MM (bias=3) → F16: S EEEEE MMMMMMMMMM (bias=15).
396
+ * Uses precomputed lookup tables for 64 possible 6-bit values.
397
+ *
398
+ * Performance (per 16 elements):
399
+ * VLD1Q x4 (4 loads) 4 cy latency, 1 cy throughput each
400
+ * VAND (mask) 2 cy latency, 0.5 cy throughput
401
+ * VQTBL4 (table lookup) 3 cy latency, 1 cy throughput
402
+ * VZIP (interleave) 2 cy latency, 1 cy throughput
403
+ * Total: ~6-8 cy latency, ~2.5 cy amortized throughput (dominated by table lookup + zip) */
404
+ NK_INTERNAL void nk_e3m2x16_to_f16x8x2_neon_(uint8x16_t input_u8x16, float16x8_t *result_low_f16x8,
405
+ float16x8_t *result_high_f16x8) {
406
+ // Precomputed lookup table for E3M2FN → F16 conversion (high byte only, unsigned).
407
+ // Sign symmetry: negative half = positive XOR 0x80, so we strip sign (bit 5),
408
+ // lookup unsigned 5-bit value in VQTBL2 (32 bytes), then OR sign back.
409
+ // E3M2FN: sign(1) exp(3) mant(2), bias=3
410
+ // F16: sign(1) exp(5) mant(10), bias=15
411
+ // Normal (exp!=0): f16 = (sign << 15) | ((exp + 12) << 10) | (mant << 8)
412
+ // Subnormal (exp=0): f16 = mant/16 converted to f16
413
+ // E3M2 mantissa (2 bits) maps to F16 bits [9:8]; F16 bits [7:0] = 0
414
+ static nk_u8_t const table_high_u8x32[32] = {
415
+ 0x00, 0x2C, 0x30, 0x32, // exp=0 (subnormals): 0, 1/16, 2/16, 3/16
416
+ 0x34, 0x35, 0x36, 0x37, // exp=1 → f16_exp=13 (0x34-0x37)
417
+ 0x38, 0x39, 0x3A, 0x3B, // exp=2 → f16_exp=14 (0x38-0x3B)
418
+ 0x3C, 0x3D, 0x3E, 0x3F, // exp=3 → f16_exp=15 (0x3C-0x3F)
419
+ 0x40, 0x41, 0x42, 0x43, // exp=4 → f16_exp=16 (0x40-0x43)
420
+ 0x44, 0x45, 0x46, 0x47, // exp=5 → f16_exp=17 (0x44-0x47)
421
+ 0x48, 0x49, 0x4A, 0x4B, // exp=6 → f16_exp=18 (0x48-0x4B)
422
+ 0x4C, 0x4D, 0x4E, 0x4F, // exp=7 → f16_exp=19 (0x4C-0x4F)
423
+ };
424
+
425
+ // Load unsigned high byte table (2 registers instead of 4 - low bytes are always zero!)
426
+ uint8x16x2_t table_high_u8x16x2 = vld1q_u8_x2(table_high_u8x32);
427
+
428
+ // Extract sign (bit 5 → bit 7 for F16 high byte) and 5-bit unsigned index
429
+ uint8x16_t sign_u8x16 = vshlq_n_u8(vandq_u8(input_u8x16, vdupq_n_u8(0x20)), 2);
430
+ uint8x16_t abs_indices_u8x16 = vandq_u8(input_u8x16, vdupq_n_u8(0x1F));
431
+
432
+ // Table lookup for high bytes on unsigned index, then OR sign back
433
+ uint8x16_t high_bytes_u8x16 = vorrq_u8(vqtbl2q_u8(table_high_u8x16x2, abs_indices_u8x16), sign_u8x16);
434
+
435
+ // ZIP zeros with high bytes: [0,0...0] + [h0,h1...h15] → [0,h0,0,h1...]
436
+ uint8x16x2_t interleaved_u8x16x2 = vzipq_u8(vdupq_n_u8(0), high_bytes_u8x16);
437
+
438
+ *result_low_f16x8 = vreinterpretq_f16_u8(interleaved_u8x16x2.val[0]); // elements 0-7
439
+ *result_high_f16x8 = vreinterpretq_f16_u8(interleaved_u8x16x2.val[1]); // elements 8-15
440
+ }
441
+
442
+ /** @brief Convert f16x8 → 8x e4m3 with RNE rounding (NEON).
443
+ * F16: S EEEEE MMMMMMMMMM (bias=15) → E4M3: S EEEE MMM (bias=7).
444
+ * Handles subnormals (exp < 9 → E4M3 subnormal), overflow (> 448 → clamp), inf → max, nan → nan. */
445
+ NK_INTERNAL uint8x8_t nk_f16x8_to_e4m3x8_neon_(float16x8_t f16x8) {
446
+ uint16x8_t bits_u16x8 = vreinterpretq_u16_f16(f16x8);
447
+ uint16x8_t sign_byte_u16x8 = vshrq_n_u16(vandq_u16(bits_u16x8, vdupq_n_u16(0x8000)), 8);
448
+ uint16x8_t f16_exp_u16x8 = vandq_u16(vshrq_n_u16(bits_u16x8, 10), vdupq_n_u16(0x1F));
449
+ uint16x8_t f16_mant_u16x8 = vandq_u16(bits_u16x8, vdupq_n_u16(0x03FF));
450
+
451
+ // Rebias exponent: F16 bias=15 → E4M3 bias=7, subtract 8
452
+ int16x8_t e4m3_exp_i16x8 = vsubq_s16(vreinterpretq_s16_u16(f16_exp_u16x8), vdupq_n_s16(8));
453
+
454
+ // Detect special cases
455
+ uint16x8_t is_f16_zero_u16x8 = vceqq_u16(vandq_u16(bits_u16x8, vdupq_n_u16(0x7FFF)), vdupq_n_u16(0));
456
+ uint16x8_t is_f16_special_u16x8 = vceqq_u16(f16_exp_u16x8, vdupq_n_u16(31)); // inf or nan
457
+ uint16x8_t is_f16_nan_u16x8 = vandq_u16(is_f16_special_u16x8, vcgtq_u16(f16_mant_u16x8, vdupq_n_u16(0)));
458
+ uint16x8_t is_underflow_u16x8 = vcltq_s16(e4m3_exp_i16x8, vdupq_n_s16(1)); // exp < 1 → subnormal/zero
459
+ uint16x8_t is_overflow_u16x8 = vcgtq_s16(e4m3_exp_i16x8, vdupq_n_s16(15)); // exp > 15 → overflow
460
+
461
+ // Normal path with RNE rounding: round mantissa from 10 to 3 bits
462
+ // RNE: add (0x3F + lsb) where lsb = bit 7 of mantissa
463
+ uint16x8_t lsb_u16x8 = vandq_u16(vshrq_n_u16(f16_mant_u16x8, 7), vdupq_n_u16(1));
464
+ uint16x8_t rounded_mant_u16x8 = vaddq_u16(f16_mant_u16x8, vaddq_u16(vdupq_n_u16(0x3F), lsb_u16x8));
465
+ uint16x8_t carry_u16x8 = vshrq_n_u16(rounded_mant_u16x8, 10); // Mantissa overflow → carry to exponent
466
+ e4m3_exp_i16x8 = vaddq_s16(e4m3_exp_i16x8, vreinterpretq_s16_u16(carry_u16x8));
467
+ uint16x8_t e4m3_mant_u16x8 = vandq_u16(vshrq_n_u16(rounded_mant_u16x8, 7), vdupq_n_u16(0x07));
468
+ e4m3_mant_u16x8 = vbicq_u16(e4m3_mant_u16x8, vceqq_u16(carry_u16x8, vdupq_n_u16(1))); // Clear mant if carry
469
+
470
+ // Recheck overflow after rounding (carry might have pushed us over)
471
+ is_overflow_u16x8 = vorrq_u16(is_overflow_u16x8, vcgtq_s16(e4m3_exp_i16x8, vdupq_n_s16(15)));
472
+
473
+ // Clamp exponent to [1, 15] for normal values
474
+ int16x8_t clamped_exp_i16x8 = vmaxq_s16(e4m3_exp_i16x8, vdupq_n_s16(1));
475
+ clamped_exp_i16x8 = vminq_s16(clamped_exp_i16x8, vdupq_n_s16(15));
476
+
477
+ // E4M3FN quirk: exp=15, mant=7 is NaN, so clamp mantissa to 6 when exp=15
478
+ uint16x8_t is_max_exp_u16x8 = vceqq_s16(clamped_exp_i16x8, vdupq_n_s16(15));
479
+ e4m3_mant_u16x8 = vbslq_u16(is_max_exp_u16x8, vminq_u16(e4m3_mant_u16x8, vdupq_n_u16(6)), e4m3_mant_u16x8);
480
+
481
+ // Assemble normal result
482
+ uint16x8_t normal_result_u16x8 = vorrq_u16(
483
+ sign_byte_u16x8, vorrq_u16(vshlq_n_u16(vreinterpretq_u16_s16(clamped_exp_i16x8), 3), e4m3_mant_u16x8));
484
+
485
+ // Subnormal path: E4M3 subnormal = mant × 2⁻⁹
486
+ // Use float conversion for correctness: abs(f16) × 512, round to int, clamp to [0,7]
487
+ float32x4_t abs_low_f32x4 = vabsq_f32(vcvt_f32_f16(vget_low_f16(f16x8)));
488
+ float32x4_t abs_high_f32x4 = vabsq_f32(vcvt_high_f32_f16(f16x8));
489
+ float32x4_t scaled_low_f32x4 = vmulq_n_f32(abs_low_f32x4, 512.0f);
490
+ float32x4_t scaled_high_f32x4 = vmulq_n_f32(abs_high_f32x4, 512.0f);
491
+ int32x4_t subnormal_mantissa_low_i32x4 = vcvtnq_s32_f32(scaled_low_f32x4); // Round to nearest even
492
+ int32x4_t subnormal_mantissa_high_i32x4 = vcvtnq_s32_f32(scaled_high_f32x4);
493
+ subnormal_mantissa_low_i32x4 = vmaxq_s32(vminq_s32(subnormal_mantissa_low_i32x4, vdupq_n_s32(7)), vdupq_n_s32(0));
494
+ subnormal_mantissa_high_i32x4 = vmaxq_s32(vminq_s32(subnormal_mantissa_high_i32x4, vdupq_n_s32(7)), vdupq_n_s32(0));
495
+ int16x4_t subnormal_mantissa_low_i16x4 = vmovn_s32(subnormal_mantissa_low_i32x4);
496
+ int16x4_t subnormal_mantissa_high_i16x4 = vmovn_s32(subnormal_mantissa_high_i32x4);
497
+ uint16x8_t subnormal_mant_u16x8 = vreinterpretq_u16_s16(
498
+ vcombine_s16(subnormal_mantissa_low_i16x4, subnormal_mantissa_high_i16x4));
499
+ uint16x8_t subnormal_result_u16x8 = vorrq_u16(sign_byte_u16x8, subnormal_mant_u16x8);
500
+
501
+ // Special values: E4M3FN has no ∞, max normal = 0x7E (exp=15, mant=6 = 448)
502
+ uint16x8_t e4m3_max_u16x8 = vorrq_u16(sign_byte_u16x8, vdupq_n_u16(0x7E)); // ±448 (exp=15, mant=6)
503
+ uint16x8_t e4m3_nan_u16x8 = vorrq_u16(sign_byte_u16x8, vdupq_n_u16(0x7F)); // ±NaN (exp=15, mant=7)
504
+ uint16x8_t e4m3_zero_u16x8 = sign_byte_u16x8; // ±0
505
+
506
+ // Blend results (order matters: later conditions override earlier)
507
+ uint16x8_t result_u16x8 = normal_result_u16x8;
508
+ result_u16x8 = vbslq_u16(is_underflow_u16x8, subnormal_result_u16x8, result_u16x8);
509
+ result_u16x8 = vbslq_u16(is_overflow_u16x8, e4m3_max_u16x8, result_u16x8);
510
+ result_u16x8 = vbslq_u16(is_f16_special_u16x8, e4m3_max_u16x8,
511
+ result_u16x8); // F16 inf → E4M3 max (no inf in E4M3FN)
512
+ result_u16x8 = vbslq_u16(is_f16_nan_u16x8, e4m3_nan_u16x8, result_u16x8); // F16 nan → E4M3 nan
513
+ result_u16x8 = vbslq_u16(is_f16_zero_u16x8, e4m3_zero_u16x8, result_u16x8); // Preserve ±0
514
+
515
+ return vmovn_u16(result_u16x8);
516
+ }
517
+
518
+ /** @brief Convert f16x8 → 8x e5m2 with RNE rounding (NEON).
519
+ * F16 (bias=15) and E5M2 (bias=15) share the same bias, so conversion is truncation with RNE rounding.
520
+ * F16: S EEEEE MMMMMMMMMM → E5M2: S EEEEE MM. Mantissa overflow carries into exponent. */
521
+ NK_INTERNAL uint8x8_t nk_f16x8_to_e5m2x8_neon_(float16x8_t f16x8) {
522
+ uint16x8_t bits_u16x8 = vreinterpretq_u16_f16(f16x8);
523
+
524
+ // Detect inf/nan (exp=31) - these should not be rounded, just truncated
525
+ uint16x8_t exp_u16x8 = vandq_u16(vshrq_n_u16(bits_u16x8, 10), vdupq_n_u16(0x1F));
526
+ uint16x8_t is_special_mask_u16x8 = vceqq_u16(exp_u16x8, vdupq_n_u16(31));
527
+
528
+ // RNE rounding: add (0x7F + lsb) where lsb = bit 8 of F16
529
+ // This rounds the lower 8 bits correctly and may carry into exponent
530
+ uint16x8_t lsb_u16x8 = vandq_u16(vshrq_n_u16(bits_u16x8, 8), vdupq_n_u16(1));
531
+ uint16x8_t rounding_bias_u16x8 = vaddq_u16(vdupq_n_u16(0x7F), lsb_u16x8);
532
+ uint16x8_t rounded_bits_u16x8 = vaddq_u16(bits_u16x8, rounding_bias_u16x8);
533
+
534
+ // For special values (inf/nan), use original bits without rounding
535
+ uint16x8_t final_bits_u16x8 = vbslq_u16(is_special_mask_u16x8, bits_u16x8, rounded_bits_u16x8);
536
+
537
+ // Shift right by 8 to get E5M2 format
538
+ uint16x8_t e5m2_u16x8 = vshrq_n_u16(final_bits_u16x8, 8);
539
+ return vmovn_u16(e5m2_u16x8);
540
+ }
541
+
542
+ /** @brief Convert 4x bf16 → f32x4 via bit shift (NEON).
543
+ * BF16 format: S EEEEEEEE MMMMMMM (bias=127, same as f32 but truncated mantissa).
544
+ * F32 = bf16 << 16. */
545
+ NK_INTERNAL float32x4_t nk_bf16x4_to_f32x4_neon_(uint16x4_t bf16_u16x4) {
546
+ uint32x4_t bits_u32x4 = vshlq_n_u32(vmovl_u16(bf16_u16x4), 16);
547
+ return vreinterpretq_f32_u32(bits_u32x4);
548
+ }
549
+
550
+ /** @brief Convert f32x4 → 4x bf16 with RNE rounding (NEON).
551
+ * Round-to-nearest-even: add (0x7FFF + lsb) before truncation. */
552
+ NK_INTERNAL uint16x4_t nk_f32x4_to_bf16x4_neon_(float32x4_t f32x4) {
553
+ uint32x4_t bits_u32x4 = vreinterpretq_u32_f32(f32x4);
554
+ uint32x4_t lsb_u32x4 = vandq_u32(vshrq_n_u32(bits_u32x4, 16), vdupq_n_u32(1));
555
+ uint32x4_t rounding_u32x4 = vaddq_u32(vdupq_n_u32(0x7FFF), lsb_u32x4);
556
+ bits_u32x4 = vaddq_u32(bits_u32x4, rounding_u32x4);
557
+ return vmovn_u32(vshrq_n_u32(bits_u32x4, 16));
558
+ }
559
+
560
+ /** @brief Convert 8x e4m3 → bf16x8 via direct bit manipulation (NEON).
561
+ * E4M3FN format: S EEEE MMM (bias=7). BF16: S EEEEEEEE MMMMMMM (bias=127).
562
+ * Direct conversion without F16 ÷ F32 intermediate for hot loop efficiency. */
563
+ NK_INTERNAL uint16x8_t nk_e4m3x8_to_bf16x8_neon_(uint8x8_t e4m3_u8x8) {
564
+ uint16x8_t e4m3_u16x8 = vmovl_u8(e4m3_u8x8);
565
+ uint16x8_t sign_u16x8 = vshlq_n_u16(vandq_u16(e4m3_u16x8, vdupq_n_u16(0x80)), 8); // sign << 15
566
+ uint16x8_t exp_u16x8 = vandq_u16(vshrq_n_u16(e4m3_u16x8, 3), vdupq_n_u16(0x0F));
567
+ uint16x8_t mant_u16x8 = vandq_u16(e4m3_u16x8, vdupq_n_u16(0x07));
568
+
569
+ // Normal path: BF16_exp = E4M3_exp + 120, BF16_mant = E4M3_mant << 4
570
+ uint16x8_t bf16_exp_u16x8 = vshlq_n_u16(vaddq_u16(exp_u16x8, vdupq_n_u16(120)), 7);
571
+ uint16x8_t bf16_mant_u16x8 = vshlq_n_u16(mant_u16x8, 4);
572
+ uint16x8_t normal_u16x8 = vorrq_u16(sign_u16x8, vorrq_u16(bf16_exp_u16x8, bf16_mant_u16x8));
573
+
574
+ // Subnormal path (exp=0): E4M3 subnormal = mant × 2⁻⁹ = mant ÷ 512 → BF16
575
+ // Compute via f32: mant → f32 → multiply → truncate to bf16
576
+ float32x4_t subnormal_low_f32x4 = vmulq_n_f32(vcvtq_f32_u32(vmovl_u16(vget_low_u16(mant_u16x8))), 1.0f / 512.0f);
577
+ float32x4_t subnormal_high_f32x4 = vmulq_n_f32(vcvtq_f32_u32(vmovl_high_u16(mant_u16x8)), 1.0f / 512.0f);
578
+ uint16x8_t subnormal_abs_u16x8 = vcombine_u16(nk_f32x4_to_bf16x4_neon_(subnormal_low_f32x4),
579
+ nk_f32x4_to_bf16x4_neon_(subnormal_high_f32x4));
580
+ uint16x8_t subnormal_u16x8 = vorrq_u16(subnormal_abs_u16x8, sign_u16x8);
581
+
582
+ // NaN path: E4M3FN only has NaN when exp=15 AND mant=7 (0x7F or 0xFF)
583
+ uint16x8_t nan_u16x8 = vorrq_u16(sign_u16x8, vdupq_n_u16(0x7FC0)); // BF16 quiet NaN
584
+ uint16x8_t is_nan_mask_u16x8 = vandq_u16(vceqq_u16(exp_u16x8, vdupq_n_u16(15)),
585
+ vceqq_u16(mant_u16x8, vdupq_n_u16(7)));
586
+
587
+ // Blend paths: subnormal when exp=0, NaN when exp=15 && mant=7, else normal
588
+ uint16x8_t exp_zero_mask_u16x8 = vceqq_u16(exp_u16x8, vdupq_n_u16(0));
589
+ uint16x8_t result_u16x8 = vbslq_u16(exp_zero_mask_u16x8, subnormal_u16x8, normal_u16x8);
590
+ result_u16x8 = vbslq_u16(is_nan_mask_u16x8, nan_u16x8, result_u16x8);
591
+ return result_u16x8;
592
+ }
593
+
594
+ /** @brief Convert 8x e5m2 → bf16x8 via direct bit manipulation (NEON).
595
+ * E5M2 format: S EEEEE MM (bias=15). BF16: S EEEEEEEE MMMMMMM (bias=127).
596
+ * Direct conversion without F16 ÷ F32 intermediate for hot loop efficiency. */
597
+ NK_INTERNAL uint16x8_t nk_e5m2x8_to_bf16x8_neon_(uint8x8_t e5m2_u8x8) {
598
+ uint16x8_t e5m2_u16x8 = vmovl_u8(e5m2_u8x8);
599
+ uint16x8_t sign_u16x8 = vshlq_n_u16(vandq_u16(e5m2_u16x8, vdupq_n_u16(0x80)), 8); // sign << 15
600
+ uint16x8_t exp_u16x8 = vandq_u16(vshrq_n_u16(e5m2_u16x8, 2), vdupq_n_u16(0x1F));
601
+ uint16x8_t mant_u16x8 = vandq_u16(e5m2_u16x8, vdupq_n_u16(0x03));
602
+
603
+ // Normal path: BF16_exp = E5M2_exp + 112, BF16_mant = E5M2_mant << 5
604
+ uint16x8_t bf16_exp_u16x8 = vshlq_n_u16(vaddq_u16(exp_u16x8, vdupq_n_u16(112)), 7);
605
+ uint16x8_t bf16_mant_u16x8 = vshlq_n_u16(mant_u16x8, 5);
606
+ uint16x8_t normal_u16x8 = vorrq_u16(sign_u16x8, vorrq_u16(bf16_exp_u16x8, bf16_mant_u16x8));
607
+
608
+ // Subnormal path (exp=0): E5M2 subnormal = mant × 2⁻¹⁶ = mant ÷ 65536 → BF16
609
+ // Compute via f32: mant → f32 → multiply → truncate to bf16
610
+ float32x4_t subnormal_low_f32x4 = vmulq_n_f32(vcvtq_f32_u32(vmovl_u16(vget_low_u16(mant_u16x8))), 1.0f / 65536.0f);
611
+ float32x4_t subnormal_high_f32x4 = vmulq_n_f32(vcvtq_f32_u32(vmovl_high_u16(mant_u16x8)), 1.0f / 65536.0f);
612
+ uint16x8_t subnormal_abs_u16x8 = vcombine_u16(nk_f32x4_to_bf16x4_neon_(subnormal_low_f32x4),
613
+ nk_f32x4_to_bf16x4_neon_(subnormal_high_f32x4));
614
+ uint16x8_t subnormal_u16x8 = vorrq_u16(subnormal_abs_u16x8, sign_u16x8);
615
+
616
+ // Special path (exp=31): inf (mant=0) or nan (mant≠0)
617
+ uint16x8_t infinity_u16x8 = vorrq_u16(sign_u16x8, vdupq_n_u16(0x7F80));
618
+ uint16x8_t nan_u16x8 = vorrq_u16(sign_u16x8, vdupq_n_u16(0x7FC0));
619
+ uint16x8_t mant_zero_mask_u16x8 = vceqq_u16(mant_u16x8, vdupq_n_u16(0));
620
+ uint16x8_t special_u16x8 = vbslq_u16(mant_zero_mask_u16x8, infinity_u16x8, nan_u16x8);
621
+
622
+ // Blend paths based on exponent value
623
+ uint16x8_t exp_zero_mask_u16x8 = vceqq_u16(exp_u16x8, vdupq_n_u16(0));
624
+ uint16x8_t exp_max_mask_u16x8 = vceqq_u16(exp_u16x8, vdupq_n_u16(31));
625
+ uint16x8_t result_u16x8 = vbslq_u16(exp_zero_mask_u16x8, subnormal_u16x8, normal_u16x8);
626
+ result_u16x8 = vbslq_u16(exp_max_mask_u16x8, special_u16x8, result_u16x8);
627
+ return result_u16x8;
628
+ }
629
+
630
+ /** @brief Convert 4x i16 → f32x4 (NEON). Widen to i32, then convert. */
631
+ NK_INTERNAL float32x4_t nk_i16x4_to_f32x4_neon_(int16x4_t i16x4) { return vcvtq_f32_s32(vmovl_s16(i16x4)); }
632
+
633
+ /** @brief Convert 4x u16 → f32x4 (NEON). Widen to u32, then convert. */
634
+ NK_INTERNAL float32x4_t nk_u16x4_to_f32x4_neon_(uint16x4_t u16x4) { return vcvtq_f32_u32(vmovl_u16(u16x4)); }
635
+
636
+ /** @brief Convert 4x i8 → f32x4 (NEON). Loads exactly 4 bytes via nk_b32_vec_t to avoid overread. */
637
+ NK_INTERNAL float32x4_t nk_i8x4_to_f32x4_neon_(nk_b32_vec_t in_vec) {
638
+ int8x8_t i8x8 = vcreate_s8((nk_u64_t)in_vec.u32);
639
+ int16x8_t i16x8 = vmovl_s8(i8x8);
640
+ return vcvtq_f32_s32(vmovl_s16(vget_low_s16(i16x8)));
641
+ }
642
+
643
+ /** @brief Convert 4x u8 → f32x4 (NEON). Loads exactly 4 bytes via nk_b32_vec_t to avoid overread. */
644
+ NK_INTERNAL float32x4_t nk_u8x4_to_f32x4_neon_(nk_b32_vec_t in_vec) {
645
+ uint8x8_t u8x8 = vcreate_u8((nk_u64_t)in_vec.u32);
646
+ uint16x8_t u16x8 = vmovl_u8(u8x8);
647
+ return vcvtq_f32_u32(vmovl_u16(vget_low_u16(u16x8)));
648
+ }
649
+
650
+ /** @brief Convert f32x4 → 4x i16 with saturation (NEON). Convert to i32, narrow. */
651
+ NK_INTERNAL int16x4_t nk_f32x4_to_i16x4_neon_(float32x4_t f32x4) {
652
+ int32x4_t i32x4 = vcvtnq_s32_f32(f32x4);
653
+ return vqmovn_s32(i32x4);
654
+ }
655
+
656
+ /** @brief Convert f32x4 → 4x u16 with saturation (NEON). Convert to u32, narrow. */
657
+ NK_INTERNAL uint16x4_t nk_f32x4_to_u16x4_neon_(float32x4_t f32x4) {
658
+ uint32x4_t u32x4 = vcvtnq_u32_f32(f32x4);
659
+ return vqmovn_u32(u32x4);
660
+ }
661
+
662
+ /** @brief Convert f32x4 → 4x i8 with saturation (NEON). Convert to i32, narrow twice. */
663
+ NK_INTERNAL nk_b32_vec_t nk_f32x4_to_i8x4_neon_(float32x4_t f32x4) {
664
+ int32x4_t i32x4 = vcvtnq_s32_f32(f32x4);
665
+ int16x4_t i16x4 = vqmovn_s32(i32x4);
666
+ int8x8_t i8x8 = vqmovn_s16(vcombine_s16(i16x4, i16x4));
667
+ nk_b32_vec_t result_vec;
668
+ result_vec.u32 = vget_lane_u32(vreinterpret_u32_s8(i8x8), 0);
669
+ return result_vec;
670
+ }
671
+
672
+ /** @brief Convert f32x4 → 4x u8 with saturation (NEON). Convert to u32, narrow twice. */
673
+ NK_INTERNAL nk_b32_vec_t nk_f32x4_to_u8x4_neon_(float32x4_t f32x4) {
674
+ uint32x4_t u32x4 = vcvtnq_u32_f32(f32x4);
675
+ uint16x4_t u16x4 = vqmovn_u32(u32x4);
676
+ uint8x8_t u8x8 = vqmovn_u16(vcombine_u16(u16x4, u16x4));
677
+ nk_b32_vec_t result_vec;
678
+ result_vec.u32 = vget_lane_u32(vreinterpret_u32_u8(u8x8), 0);
679
+ return result_vec;
680
+ }
681
+
682
+ /** @brief Convert f32x4 → 4x e4m3 via bit manipulation (NEON).
683
+ * E4M3 format: S EEEE MMM (bias=7). Handles normal, subnormal, and overflow cases.
684
+ * Uses RNE (round to nearest even) for mantissa rounding. Returns packed result in nk_b32_vec_t. */
685
+ NK_INTERNAL nk_b32_vec_t nk_f32x4_to_e4m3x4_neon_(float32x4_t f32x4) {
686
+ uint32x4_t bits_u32x4 = vreinterpretq_u32_f32(f32x4);
687
+ uint32x4_t sign_u32x4 = vshrq_n_u32(bits_u32x4, 31);
688
+ uint32x4_t f32_exp_u32x4 = vandq_u32(vshrq_n_u32(bits_u32x4, 23), vdupq_n_u32(0xFF));
689
+
690
+ // Round mantissa from 23 to 3 bits using RNE (round to nearest, ties to even)
691
+ // RNE trick: add (half - 1 + lsb) where lsb is the bit that will become the new lsb after shift
692
+ uint32x4_t significand_u32x4 = vorrq_u32(vandq_u32(bits_u32x4, vdupq_n_u32(0x007FFFFF)),
693
+ vdupq_n_u32(0x00800000)); // Add implicit 1 bit
694
+ uint32x4_t lsb_u32x4 = vandq_u32(vshrq_n_u32(significand_u32x4, 20), vdupq_n_u32(1));
695
+ uint32x4_t rounding_bias_u32x4 = vaddq_u32(vdupq_n_u32(0x0007FFFF), lsb_u32x4);
696
+ uint32x4_t rounded_sig_u32x4 = vaddq_u32(significand_u32x4, rounding_bias_u32x4);
697
+ uint32x4_t carry_u32x4 = vshrq_n_u32(rounded_sig_u32x4, 24); // Carry into exponent if bit 24 set
698
+ uint32x4_t f32_mantissa_u32x4 = vandq_u32(vshrq_n_u32(rounded_sig_u32x4, 20), vdupq_n_u32(0x07));
699
+ // If carry, mantissa becomes 0 (we rounded up to next power of 2)
700
+ uint32x4_t carry_mask_u32x4 = vceqq_u32(carry_u32x4, vdupq_n_u32(1));
701
+ f32_mantissa_u32x4 = vbicq_u32(f32_mantissa_u32x4, carry_mask_u32x4);
702
+
703
+ // Rebias exponent: f32 bias 127 → e4m3 bias 7 (subtract 120)
704
+ int32x4_t e4m3_exp_i32x4 = vsubq_s32(
705
+ vaddq_s32(vreinterpretq_s32_u32(f32_exp_u32x4), vreinterpretq_s32_u32(carry_u32x4)), vdupq_n_s32(120));
706
+
707
+ // Detect underflow (exp <= 0, maps to subnormal/zero) and overflow (exp > 15)
708
+ uint32x4_t is_subnormal_u32x4 = vcltq_s32(e4m3_exp_i32x4, vdupq_n_s32(1));
709
+ uint32x4_t overflow_u32x4 = vcgtq_s32(e4m3_exp_i32x4, vdupq_n_s32(15));
710
+
711
+ // Normal path: clamp exp to [1,15], extract mantissa bits
712
+ // e4m3FN quirk: exp=15 with mantissa=7 is NaN (0x7F), so clamp mantissa to 6 when exp=15.
713
+ int32x4_t clamped_exp_i32x4 = vmaxq_s32(e4m3_exp_i32x4, vdupq_n_s32(1));
714
+ clamped_exp_i32x4 = vminq_s32(clamped_exp_i32x4, vdupq_n_s32(15));
715
+ uint32x4_t is_max_exp_u32x4 = vceqq_s32(clamped_exp_i32x4, vdupq_n_s32(15));
716
+ uint32x4_t max_mantissa_u32x4 = vbslq_u32(is_max_exp_u32x4, vdupq_n_u32(6), vdupq_n_u32(7));
717
+ uint32x4_t normal_mantissa_u32x4 = vminq_u32(f32_mantissa_u32x4, max_mantissa_u32x4);
718
+ normal_mantissa_u32x4 = vbslq_u32(overflow_u32x4, vdupq_n_u32(0x06), normal_mantissa_u32x4);
719
+ uint32x4_t normal_e4m3_u32x4 = vorrq_u32(
720
+ vshlq_n_u32(sign_u32x4, 7),
721
+ vorrq_u32(vshlq_n_u32(vreinterpretq_u32_s32(clamped_exp_i32x4), 3), normal_mantissa_u32x4));
722
+
723
+ // Subnormal path: mantissa = round(abs_f32 * 512)
724
+ // If mantissa rounds to 8 or higher, promote to first normal (exp_field=1, mantissa=0) = 0x08
725
+ float32x4_t abs_f32x4 = vabsq_f32(f32x4);
726
+ float32x4_t scaled_f32x4 = vmulq_n_f32(abs_f32x4, 512.0f);
727
+ int32x4_t subnormal_mantissa_i32x4 = vcvtnq_s32_f32(scaled_f32x4);
728
+ uint32x4_t promotes_to_normal_u32x4 = vcgtq_s32(subnormal_mantissa_i32x4, vdupq_n_s32(7));
729
+ subnormal_mantissa_i32x4 = vminq_s32(subnormal_mantissa_i32x4, vdupq_n_s32(7));
730
+ subnormal_mantissa_i32x4 = vmaxq_s32(subnormal_mantissa_i32x4, vdupq_n_s32(0));
731
+ uint32x4_t subnormal_e4m3_u32x4 = vorrq_u32(vshlq_n_u32(sign_u32x4, 7),
732
+ vreinterpretq_u32_s32(subnormal_mantissa_i32x4));
733
+ // When mantissa rounds to 8, use first normal value (0x08) instead of clamped subnormal
734
+ uint32x4_t first_normal_e4m3_u32x4 = vorrq_u32(vshlq_n_u32(sign_u32x4, 7), vdupq_n_u32(0x08));
735
+ subnormal_e4m3_u32x4 = vbslq_u32(promotes_to_normal_u32x4, first_normal_e4m3_u32x4, subnormal_e4m3_u32x4);
736
+
737
+ // Blend: use subnormal result when exp <= 0, else normal
738
+ uint32x4_t e4m3_u32x4 = vbslq_u32(is_subnormal_u32x4, subnormal_e4m3_u32x4, normal_e4m3_u32x4);
739
+
740
+ // Pack 4 u32s to 4 u8s
741
+ uint16x4_t e4m3_u16x4 = vmovn_u32(e4m3_u32x4);
742
+ uint8x8_t e4m3_u8x8 = vmovn_u16(vcombine_u16(e4m3_u16x4, e4m3_u16x4));
743
+ nk_b32_vec_t result;
744
+ result.u32 = vget_lane_u32(vreinterpret_u32_u8(e4m3_u8x8), 0);
745
+ return result;
746
+ }
747
+
748
+ /** @brief Convert f32x4 → 4x e5m2 via bit manipulation (NEON).
749
+ * E5M2 format: S EEEEE MM (bias=15). Handles normal, subnormal, and overflow cases.
750
+ * Uses RNE (round to nearest even) for mantissa rounding. Returns packed result in nk_b32_vec_t. */
751
+ NK_INTERNAL nk_b32_vec_t nk_f32x4_to_e5m2x4_neon_(float32x4_t f32x4) {
752
+ uint32x4_t bits_u32x4 = vreinterpretq_u32_f32(f32x4);
753
+ uint32x4_t sign_u32x4 = vshrq_n_u32(bits_u32x4, 31);
754
+ uint32x4_t f32_exp_u32x4 = vandq_u32(vshrq_n_u32(bits_u32x4, 23), vdupq_n_u32(0xFF));
755
+
756
+ // Round mantissa from 23 to 2 bits using RNE (round to nearest, ties to even)
757
+ // RNE trick: add (half - 1 + lsb) where lsb is the bit that will become the new lsb after shift
758
+ uint32x4_t significand_u32x4 = vorrq_u32(vandq_u32(bits_u32x4, vdupq_n_u32(0x007FFFFF)),
759
+ vdupq_n_u32(0x00800000)); // Add implicit 1 bit
760
+ uint32x4_t lsb_u32x4 = vandq_u32(vshrq_n_u32(significand_u32x4, 21), vdupq_n_u32(1));
761
+ uint32x4_t rounding_bias_u32x4 = vaddq_u32(vdupq_n_u32(0x000FFFFF), lsb_u32x4); // half = 0x100000
762
+ uint32x4_t rounded_sig_u32x4 = vaddq_u32(significand_u32x4, rounding_bias_u32x4);
763
+ uint32x4_t carry_u32x4 = vshrq_n_u32(rounded_sig_u32x4, 24); // Carry into exponent if bit 24 set
764
+ uint32x4_t f32_mantissa_u32x4 = vandq_u32(vshrq_n_u32(rounded_sig_u32x4, 21), vdupq_n_u32(0x03));
765
+ // If carry, mantissa becomes 0 (we rounded up to next power of 2)
766
+ uint32x4_t carry_mask_u32x4 = vceqq_u32(carry_u32x4, vdupq_n_u32(1));
767
+ f32_mantissa_u32x4 = vbicq_u32(f32_mantissa_u32x4, carry_mask_u32x4);
768
+
769
+ // Rebias exponent: f32 bias 127 → e5m2 bias 15 (subtract 112)
770
+ int32x4_t e5m2_exp_i32x4 = vsubq_s32(
771
+ vaddq_s32(vreinterpretq_s32_u32(f32_exp_u32x4), vreinterpretq_s32_u32(carry_u32x4)), vdupq_n_s32(112));
772
+
773
+ // Detect subnormal (exp <= 0) and overflow (exp > 31)
774
+ uint32x4_t is_subnormal_u32x4 = vcltq_s32(e5m2_exp_i32x4, vdupq_n_s32(1));
775
+ uint32x4_t overflow_u32x4 = vcgtq_s32(e5m2_exp_i32x4, vdupq_n_s32(31));
776
+
777
+ // Normal path: clamp exp to [1,31], on overflow return infinity (exp=31, mantissa=0 = 0x7C)
778
+ int32x4_t clamped_exp_i32x4 = vmaxq_s32(e5m2_exp_i32x4, vdupq_n_s32(1));
779
+ clamped_exp_i32x4 = vminq_s32(clamped_exp_i32x4, vdupq_n_s32(31));
780
+ uint32x4_t normal_mantissa_u32x4 = vbslq_u32(overflow_u32x4, vdupq_n_u32(0), f32_mantissa_u32x4);
781
+ uint32x4_t normal_e5m2_u32x4 = vorrq_u32(
782
+ vshlq_n_u32(sign_u32x4, 7),
783
+ vorrq_u32(vshlq_n_u32(vreinterpretq_u32_s32(clamped_exp_i32x4), 2), normal_mantissa_u32x4));
784
+
785
+ // Subnormal path: mantissa = round(abs_f32 * 65536)
786
+ // If mantissa rounds to 4 or higher, promote to first normal (exp_field=1, mantissa=0) = 0x04
787
+ float32x4_t abs_f32x4 = vabsq_f32(f32x4);
788
+ float32x4_t scaled_f32x4 = vmulq_n_f32(abs_f32x4, 65536.0f);
789
+ int32x4_t subnormal_mantissa_i32x4 = vcvtnq_s32_f32(scaled_f32x4);
790
+ uint32x4_t promotes_to_normal_u32x4 = vcgtq_s32(subnormal_mantissa_i32x4, vdupq_n_s32(3));
791
+ subnormal_mantissa_i32x4 = vminq_s32(subnormal_mantissa_i32x4, vdupq_n_s32(3));
792
+ subnormal_mantissa_i32x4 = vmaxq_s32(subnormal_mantissa_i32x4, vdupq_n_s32(0));
793
+ uint32x4_t subnormal_e5m2_u32x4 = vorrq_u32(vshlq_n_u32(sign_u32x4, 7),
794
+ vreinterpretq_u32_s32(subnormal_mantissa_i32x4));
795
+ // When mantissa rounds to 4, use first normal value (0x04) instead of clamped subnormal
796
+ uint32x4_t first_normal_e5m2_u32x4 = vorrq_u32(vshlq_n_u32(sign_u32x4, 7), vdupq_n_u32(0x04));
797
+ subnormal_e5m2_u32x4 = vbslq_u32(promotes_to_normal_u32x4, first_normal_e5m2_u32x4, subnormal_e5m2_u32x4);
798
+
799
+ // Blend: use subnormal result when exp <= 0
800
+ uint32x4_t e5m2_u32x4 = vbslq_u32(is_subnormal_u32x4, subnormal_e5m2_u32x4, normal_e5m2_u32x4);
801
+
802
+ // Pack 4 u32s to 4 u8s
803
+ uint16x4_t e5m2_u16x4 = vmovn_u32(e5m2_u32x4);
804
+ uint8x8_t e5m2_u8x8 = vmovn_u16(vcombine_u16(e5m2_u16x4, e5m2_u16x4));
805
+ nk_b32_vec_t result;
806
+ result.u32 = vget_lane_u32(vreinterpret_u32_u8(e5m2_u8x8), 0);
807
+ return result;
808
+ }
809
+
810
+ /** @brief Convert 4x e2m3 → f32x4 via bit manipulation (NEON).
811
+ * E2M3 format: S EE MMM (bias=1). F32: sign<<31, (exp+126)<<23, mantissa<<20.
812
+ * Handles subnormals (exp=0, mant ≠ 0). */
813
+ NK_INTERNAL float32x4_t nk_e2m3x4_to_f32x4_neon_(nk_b32_vec_t src) {
814
+ uint8x8_t e2m3_u8x8 = vcreate_u8(src.u32);
815
+ uint16x8_t e2m3_u16x8 = vmovl_u8(e2m3_u8x8);
816
+ uint32x4_t e2m3_u32x4 = vmovl_u16(vget_low_u16(e2m3_u16x8));
817
+
818
+ // Extract sign: bit 5 → bit 31
819
+ uint32x4_t sign_u32x4 = vshlq_n_u32(vandq_u32(e2m3_u32x4, vdupq_n_u32(0x20)), 26);
820
+ uint32x4_t exp_u32x4 = vandq_u32(vshrq_n_u32(e2m3_u32x4, 3), vdupq_n_u32(0x03));
821
+ uint32x4_t mant_u32x4 = vandq_u32(e2m3_u32x4, vdupq_n_u32(0x07));
822
+
823
+ // Normal path: f32 = sign | ((exp+126)<<23) | (mant<<20)
824
+ uint32x4_t f32_exp_u32x4 = vshlq_n_u32(vaddq_u32(exp_u32x4, vdupq_n_u32(126)), 23);
825
+ uint32x4_t f32_mant_u32x4 = vshlq_n_u32(mant_u32x4, 20);
826
+ uint32x4_t normal_u32x4 = vorrq_u32(sign_u32x4, vorrq_u32(f32_exp_u32x4, f32_mant_u32x4));
827
+
828
+ // Subnormal path (exp=0, mant ≠ 0): value = ±mantissa × 2⁻³
829
+ float32x4_t subnormal_f32x4 = vmulq_n_f32(vcvtq_f32_u32(mant_u32x4), 1.0f / 8.0f);
830
+ uint32x4_t subnormal_u32x4 = vorrq_u32(vreinterpretq_u32_f32(subnormal_f32x4), sign_u32x4);
831
+
832
+ // Blend paths: subnormal when exp=0, else normal
833
+ uint32x4_t exp_zero_mask_u32x4 = vceqq_u32(exp_u32x4, vdupq_n_u32(0));
834
+ uint32x4_t result_u32x4 = vbslq_u32(exp_zero_mask_u32x4, subnormal_u32x4, normal_u32x4);
835
+ return vreinterpretq_f32_u32(result_u32x4);
836
+ }
837
+
838
+ /** @brief Convert 4x e3m2 → f32x4 via bit manipulation (NEON).
839
+ * E3M2 format: S EEE MM (bias=3). F32: sign<<31, (exp+124)<<23, mantissa<<21.
840
+ * Handles subnormals (exp=0, mant ≠ 0). */
841
+ NK_INTERNAL float32x4_t nk_e3m2x4_to_f32x4_neon_(nk_b32_vec_t src) {
842
+ uint8x8_t e3m2_u8x8 = vcreate_u8(src.u32);
843
+ uint16x8_t e3m2_u16x8 = vmovl_u8(e3m2_u8x8);
844
+ uint32x4_t e3m2_u32x4 = vmovl_u16(vget_low_u16(e3m2_u16x8));
845
+
846
+ // Extract sign: bit 5 → bit 31
847
+ uint32x4_t sign_u32x4 = vshlq_n_u32(vandq_u32(e3m2_u32x4, vdupq_n_u32(0x20)), 26);
848
+ uint32x4_t exp_u32x4 = vandq_u32(vshrq_n_u32(e3m2_u32x4, 2), vdupq_n_u32(0x07));
849
+ uint32x4_t mant_u32x4 = vandq_u32(e3m2_u32x4, vdupq_n_u32(0x03));
850
+
851
+ // Normal path: f32 = sign | ((exp+124)<<23) | (mant<<21)
852
+ uint32x4_t f32_exp_u32x4 = vshlq_n_u32(vaddq_u32(exp_u32x4, vdupq_n_u32(124)), 23);
853
+ uint32x4_t f32_mant_u32x4 = vshlq_n_u32(mant_u32x4, 21);
854
+ uint32x4_t normal_u32x4 = vorrq_u32(sign_u32x4, vorrq_u32(f32_exp_u32x4, f32_mant_u32x4));
855
+
856
+ // Subnormal path (exp=0, mant ≠ 0): value = ±mantissa × 2⁻⁴
857
+ float32x4_t subnormal_f32x4 = vmulq_n_f32(vcvtq_f32_u32(mant_u32x4), 1.0f / 16.0f);
858
+ uint32x4_t subnormal_u32x4 = vorrq_u32(vreinterpretq_u32_f32(subnormal_f32x4), sign_u32x4);
859
+
860
+ // Blend paths: subnormal when exp=0, else normal
861
+ uint32x4_t exp_zero_mask_u32x4 = vceqq_u32(exp_u32x4, vdupq_n_u32(0));
862
+ uint32x4_t result_u32x4 = vbslq_u32(exp_zero_mask_u32x4, subnormal_u32x4, normal_u32x4);
863
+ return vreinterpretq_f32_u32(result_u32x4);
864
+ }
865
+
866
+ /** @brief Convert f32x4 → 4x e2m3 via bit manipulation (NEON).
867
+ * E2M3 format: S EE MMM (bias=1). Handles normal, subnormal, and overflow cases.
868
+ * Uses RNE (round to nearest even) for mantissa rounding. Returns packed result in nk_b32_vec_t. */
869
+ NK_INTERNAL nk_b32_vec_t nk_f32x4_to_e2m3x4_neon_(float32x4_t f32x4) {
870
+ uint32x4_t bits_u32x4 = vreinterpretq_u32_f32(f32x4);
871
+ uint32x4_t sign_u32x4 = vshrq_n_u32(bits_u32x4, 31);
872
+ uint32x4_t f32_exp_u32x4 = vandq_u32(vshrq_n_u32(bits_u32x4, 23), vdupq_n_u32(0xFF));
873
+
874
+ // Round mantissa from 23 to 3 bits using RNE (round to nearest, ties to even)
875
+ uint32x4_t significand_u32x4 = vorrq_u32(vandq_u32(bits_u32x4, vdupq_n_u32(0x007FFFFF)),
876
+ vdupq_n_u32(0x00800000)); // Add implicit 1 bit
877
+ uint32x4_t lsb_u32x4 = vandq_u32(vshrq_n_u32(significand_u32x4, 20), vdupq_n_u32(1));
878
+ uint32x4_t rounding_bias_u32x4 = vaddq_u32(vdupq_n_u32(0x0007FFFF), lsb_u32x4);
879
+ uint32x4_t rounded_sig_u32x4 = vaddq_u32(significand_u32x4, rounding_bias_u32x4);
880
+ uint32x4_t carry_u32x4 = vshrq_n_u32(rounded_sig_u32x4, 24); // Carry into exponent if bit 24 set
881
+ uint32x4_t f32_mantissa_u32x4 = vandq_u32(vshrq_n_u32(rounded_sig_u32x4, 20), vdupq_n_u32(0x07));
882
+ // If carry, mantissa becomes 0 (we rounded up to next power of 2)
883
+ uint32x4_t carry_mask_u32x4 = vceqq_u32(carry_u32x4, vdupq_n_u32(1));
884
+ f32_mantissa_u32x4 = vbicq_u32(f32_mantissa_u32x4, carry_mask_u32x4);
885
+
886
+ // Rebias exponent: f32 bias 127 → e2m3 bias 1 (subtract 126)
887
+ int32x4_t e2m3_exp_i32x4 = vsubq_s32(
888
+ vaddq_s32(vreinterpretq_s32_u32(f32_exp_u32x4), vreinterpretq_s32_u32(carry_u32x4)), vdupq_n_s32(126));
889
+
890
+ // Detect underflow (exp <= 0, maps to subnormal/zero) and overflow (exp > 3)
891
+ uint32x4_t is_subnormal_u32x4 = vcltq_s32(e2m3_exp_i32x4, vdupq_n_s32(1));
892
+ uint32x4_t overflow_u32x4 = vcgtq_s32(e2m3_exp_i32x4, vdupq_n_s32(3));
893
+
894
+ // Normal path: clamp exp to [1,3], extract mantissa bits
895
+ int32x4_t clamped_exp_i32x4 = vmaxq_s32(e2m3_exp_i32x4, vdupq_n_s32(1));
896
+ clamped_exp_i32x4 = vminq_s32(clamped_exp_i32x4, vdupq_n_s32(3));
897
+ uint32x4_t normal_mantissa_u32x4 = vbslq_u32(overflow_u32x4, vdupq_n_u32(0x07), f32_mantissa_u32x4);
898
+ uint32x4_t normal_e2m3_u32x4 = vorrq_u32(
899
+ vshlq_n_u32(sign_u32x4, 5),
900
+ vorrq_u32(vshlq_n_u32(vreinterpretq_u32_s32(clamped_exp_i32x4), 3), normal_mantissa_u32x4));
901
+
902
+ // Subnormal path: mantissa = round(abs_f32 * 8)
903
+ // If mantissa rounds to 8 or higher, promote to first normal (exp_field=1, mantissa=0) = 0x08
904
+ float32x4_t abs_f32x4 = vabsq_f32(f32x4);
905
+ float32x4_t scaled_f32x4 = vmulq_n_f32(abs_f32x4, 8.0f);
906
+ int32x4_t subnormal_mantissa_i32x4 = vcvtnq_s32_f32(scaled_f32x4);
907
+ uint32x4_t promotes_to_normal_u32x4 = vcgtq_s32(subnormal_mantissa_i32x4, vdupq_n_s32(7));
908
+ subnormal_mantissa_i32x4 = vminq_s32(subnormal_mantissa_i32x4, vdupq_n_s32(7));
909
+ subnormal_mantissa_i32x4 = vmaxq_s32(subnormal_mantissa_i32x4, vdupq_n_s32(0));
910
+ uint32x4_t subnormal_e2m3_u32x4 = vorrq_u32(vshlq_n_u32(sign_u32x4, 5),
911
+ vreinterpretq_u32_s32(subnormal_mantissa_i32x4));
912
+ // When mantissa rounds to 8, use first normal value (0x08) instead of clamped subnormal
913
+ uint32x4_t first_normal_e2m3_u32x4 = vorrq_u32(vshlq_n_u32(sign_u32x4, 5), vdupq_n_u32(0x08));
914
+ subnormal_e2m3_u32x4 = vbslq_u32(promotes_to_normal_u32x4, first_normal_e2m3_u32x4, subnormal_e2m3_u32x4);
915
+
916
+ // Blend: use subnormal result when exp <= 0, else normal
917
+ uint32x4_t e2m3_u32x4 = vbslq_u32(is_subnormal_u32x4, subnormal_e2m3_u32x4, normal_e2m3_u32x4);
918
+
919
+ // Pack 4 u32s to 4 u8s
920
+ uint16x4_t e2m3_u16x4 = vmovn_u32(e2m3_u32x4);
921
+ uint8x8_t e2m3_u8x8 = vmovn_u16(vcombine_u16(e2m3_u16x4, e2m3_u16x4));
922
+ nk_b32_vec_t result;
923
+ result.u32 = vget_lane_u32(vreinterpret_u32_u8(e2m3_u8x8), 0);
924
+ return result;
925
+ }
926
+
927
+ /** @brief Convert f32x4 → 4x e3m2 via bit manipulation (NEON).
928
+ * E3M2 format: S EEE MM (bias=3). Handles normal, subnormal, and overflow cases.
929
+ * Uses RNE (round to nearest even) for mantissa rounding. Returns packed result in nk_b32_vec_t. */
930
+ NK_INTERNAL nk_b32_vec_t nk_f32x4_to_e3m2x4_neon_(float32x4_t f32x4) {
931
+ uint32x4_t bits_u32x4 = vreinterpretq_u32_f32(f32x4);
932
+ uint32x4_t sign_u32x4 = vshrq_n_u32(bits_u32x4, 31);
933
+ uint32x4_t f32_exp_u32x4 = vandq_u32(vshrq_n_u32(bits_u32x4, 23), vdupq_n_u32(0xFF));
934
+
935
+ // Round mantissa from 23 to 2 bits using RNE (round to nearest, ties to even)
936
+ uint32x4_t significand_u32x4 = vorrq_u32(vandq_u32(bits_u32x4, vdupq_n_u32(0x007FFFFF)),
937
+ vdupq_n_u32(0x00800000)); // Add implicit 1 bit
938
+ uint32x4_t lsb_u32x4 = vandq_u32(vshrq_n_u32(significand_u32x4, 21), vdupq_n_u32(1));
939
+ uint32x4_t rounding_bias_u32x4 = vaddq_u32(vdupq_n_u32(0x000FFFFF), lsb_u32x4);
940
+ uint32x4_t rounded_sig_u32x4 = vaddq_u32(significand_u32x4, rounding_bias_u32x4);
941
+ uint32x4_t carry_u32x4 = vshrq_n_u32(rounded_sig_u32x4, 24); // Carry into exponent if bit 24 set
942
+ uint32x4_t f32_mantissa_u32x4 = vandq_u32(vshrq_n_u32(rounded_sig_u32x4, 21), vdupq_n_u32(0x03));
943
+ // If carry, mantissa becomes 0 (we rounded up to next power of 2)
944
+ uint32x4_t carry_mask_u32x4 = vceqq_u32(carry_u32x4, vdupq_n_u32(1));
945
+ f32_mantissa_u32x4 = vbicq_u32(f32_mantissa_u32x4, carry_mask_u32x4);
946
+
947
+ // Rebias exponent: f32 bias 127 → e3m2 bias 3 (subtract 124)
948
+ int32x4_t e3m2_exp_i32x4 = vsubq_s32(
949
+ vaddq_s32(vreinterpretq_s32_u32(f32_exp_u32x4), vreinterpretq_s32_u32(carry_u32x4)), vdupq_n_s32(124));
950
+
951
+ // Detect underflow (exp <= 0, maps to subnormal/zero) and overflow (exp > 7)
952
+ uint32x4_t is_subnormal_u32x4 = vcltq_s32(e3m2_exp_i32x4, vdupq_n_s32(1));
953
+ uint32x4_t overflow_u32x4 = vcgtq_s32(e3m2_exp_i32x4, vdupq_n_s32(7));
954
+
955
+ // Normal path: clamp exp to [1,7], extract mantissa bits
956
+ int32x4_t clamped_exp_i32x4 = vmaxq_s32(e3m2_exp_i32x4, vdupq_n_s32(1));
957
+ clamped_exp_i32x4 = vminq_s32(clamped_exp_i32x4, vdupq_n_s32(7));
958
+ uint32x4_t normal_mantissa_u32x4 = vbslq_u32(overflow_u32x4, vdupq_n_u32(0x03), f32_mantissa_u32x4);
959
+ uint32x4_t normal_e3m2_u32x4 = vorrq_u32(
960
+ vshlq_n_u32(sign_u32x4, 5),
961
+ vorrq_u32(vshlq_n_u32(vreinterpretq_u32_s32(clamped_exp_i32x4), 2), normal_mantissa_u32x4));
962
+
963
+ // Subnormal path: mantissa = round(abs_f32 * 16)
964
+ // If mantissa rounds to 4 or higher, promote to first normal (exp_field=1, mantissa=0) = 0x04
965
+ float32x4_t abs_f32x4 = vabsq_f32(f32x4);
966
+ float32x4_t scaled_f32x4 = vmulq_n_f32(abs_f32x4, 16.0f);
967
+ int32x4_t subnormal_mantissa_i32x4 = vcvtnq_s32_f32(scaled_f32x4);
968
+ uint32x4_t promotes_to_normal_u32x4 = vcgtq_s32(subnormal_mantissa_i32x4, vdupq_n_s32(3));
969
+ subnormal_mantissa_i32x4 = vminq_s32(subnormal_mantissa_i32x4, vdupq_n_s32(3));
970
+ subnormal_mantissa_i32x4 = vmaxq_s32(subnormal_mantissa_i32x4, vdupq_n_s32(0));
971
+ uint32x4_t subnormal_e3m2_u32x4 = vorrq_u32(vshlq_n_u32(sign_u32x4, 5),
972
+ vreinterpretq_u32_s32(subnormal_mantissa_i32x4));
973
+ // When mantissa rounds to 4, use first normal value (0x04) instead of clamped subnormal
974
+ uint32x4_t first_normal_e3m2_u32x4 = vorrq_u32(vshlq_n_u32(sign_u32x4, 5), vdupq_n_u32(0x04));
975
+ subnormal_e3m2_u32x4 = vbslq_u32(promotes_to_normal_u32x4, first_normal_e3m2_u32x4, subnormal_e3m2_u32x4);
976
+
977
+ // Blend: use subnormal result when exp <= 0
978
+ uint32x4_t e3m2_u32x4 = vbslq_u32(is_subnormal_u32x4, subnormal_e3m2_u32x4, normal_e3m2_u32x4);
979
+
980
+ // Pack 4 u32s to 4 u8s
981
+ uint16x4_t e3m2_u16x4 = vmovn_u32(e3m2_u32x4);
982
+ uint8x8_t e3m2_u8x8 = vmovn_u16(vcombine_u16(e3m2_u16x4, e3m2_u16x4));
983
+ nk_b32_vec_t result;
984
+ result.u32 = vget_lane_u32(vreinterpret_u32_u8(e3m2_u8x8), 0);
985
+ return result;
986
+ }
987
+
988
+ #pragma endregion Vectorized Conversions
989
+
990
+ #pragma region Public API
991
+
992
+ NK_PUBLIC void nk_cast_neon(void const *from, nk_dtype_t from_type, nk_size_t n, void *to, nk_dtype_t to_type) {
993
+ // Same-type fast path
994
+ if (from_type == to_type) {
995
+ nk_size_t size_bits = nk_dtype_bits(from_type);
996
+ if (size_bits > 0) nk_copy_bytes_(to, from, nk_size_divide_round_up_(n * size_bits, 8));
997
+ return;
998
+ }
999
+
1000
+ // Validate supported types (f32 and smaller)
1001
+ int from_ok = (from_type == nk_f32_k || from_type == nk_f16_k || from_type == nk_bf16_k || from_type == nk_e4m3_k ||
1002
+ from_type == nk_e5m2_k || from_type == nk_e2m3_k || from_type == nk_e3m2_k || from_type == nk_i8_k ||
1003
+ from_type == nk_u8_k || from_type == nk_i16_k || from_type == nk_u16_k || from_type == nk_i32_k ||
1004
+ from_type == nk_u32_k);
1005
+ int to_ok = (to_type == nk_f32_k || to_type == nk_f16_k || to_type == nk_bf16_k || to_type == nk_e4m3_k ||
1006
+ to_type == nk_e5m2_k || to_type == nk_e2m3_k || to_type == nk_e3m2_k || to_type == nk_i8_k ||
1007
+ to_type == nk_u8_k || to_type == nk_i16_k || to_type == nk_u16_k || to_type == nk_i32_k ||
1008
+ to_type == nk_u32_k);
1009
+
1010
+ // Fall back to serial for unsupported or i32<->u32 (loses precision through f32)
1011
+ if (!from_ok || !to_ok || (from_type == nk_i32_k && to_type == nk_u32_k) ||
1012
+ (from_type == nk_u32_k && to_type == nk_i32_k)) {
1013
+ nk_cast_serial(from, from_type, n, to, to_type);
1014
+ return;
1015
+ }
1016
+
1017
+ // Check if F16 hub is applicable (FP8/F16/BF16 conversions, 8 elements/iter)
1018
+ // Exception: BF16 ↔ F16 skips F16 hub since it needs F32 intermediate anyway
1019
+ int from_f16_hub = (from_type == nk_e4m3_k || from_type == nk_e5m2_k || from_type == nk_e2m3_k ||
1020
+ from_type == nk_e3m2_k || from_type == nk_f16_k || from_type == nk_bf16_k);
1021
+ int to_f16_hub = (to_type == nk_e4m3_k || to_type == nk_e5m2_k || to_type == nk_f16_k || to_type == nk_bf16_k ||
1022
+ to_type == nk_f32_k);
1023
+ int is_bf16_f16 = (from_type == nk_bf16_k && to_type == nk_f16_k) ||
1024
+ (from_type == nk_f16_k && to_type == nk_bf16_k);
1025
+
1026
+ if (from_f16_hub && to_f16_hub && !is_bf16_f16) {
1027
+ // F16 hub: 8 elements per iteration (float16x8_t intermediate)
1028
+ nk_size_t batches = n / 8;
1029
+ nk_size_t from_step = 8 * nk_dtype_bits(from_type) / 8;
1030
+ nk_size_t to_step = 8 * nk_dtype_bits(to_type) / 8;
1031
+ nk_u8_t const *from_ptr = (nk_u8_t const *)from;
1032
+ nk_u8_t *to_ptr = (nk_u8_t *)to;
1033
+
1034
+ for (nk_size_t idx = 0; idx < batches; ++idx, from_ptr += from_step, to_ptr += to_step) {
1035
+ nk_b128_vec_t hub_vec;
1036
+
1037
+ // Upcast to f16x8 hub
1038
+ switch (from_type) {
1039
+ case nk_e4m3_k: hub_vec.u16x8 = vreinterpretq_u16_f16(nk_e4m3x8_to_f16x8_neon_(vld1_u8(from_ptr))); break;
1040
+ case nk_e5m2_k: hub_vec.u16x8 = vreinterpretq_u16_f16(nk_e5m2x8_to_f16x8_neon_(vld1_u8(from_ptr))); break;
1041
+ case nk_e2m3_k: hub_vec.u16x8 = vreinterpretq_u16_f16(nk_e2m3x8_to_f16x8_neon_(vld1_u8(from_ptr))); break;
1042
+ case nk_e3m2_k: hub_vec.u16x8 = vreinterpretq_u16_f16(nk_e3m2x8_to_f16x8_neon_(vld1_u8(from_ptr))); break;
1043
+ case nk_f16_k: hub_vec.u16x8 = vld1q_u16((nk_u16_t const *)from_ptr); break;
1044
+ case nk_bf16_k: {
1045
+ float32x4_t low_f32x4 = nk_bf16x4_to_f32x4_neon_(vld1_u16((nk_u16_t const *)from_ptr));
1046
+ float32x4_t high_f32x4 = nk_bf16x4_to_f32x4_neon_(vld1_u16((nk_u16_t const *)(from_ptr + 8)));
1047
+ hub_vec.u16x8 = vreinterpretq_u16_f16(vcombine_f16(vcvt_f16_f32(low_f32x4), vcvt_f16_f32(high_f32x4)));
1048
+ } break;
1049
+ default: hub_vec.u16x8 = vdupq_n_u16(0); break;
1050
+ }
1051
+
1052
+ // Downcast from f16x8 hub
1053
+ switch (to_type) {
1054
+ case nk_e4m3_k: vst1_u8(to_ptr, nk_f16x8_to_e4m3x8_neon_(vreinterpretq_f16_u16(hub_vec.u16x8))); break;
1055
+ case nk_e5m2_k: vst1_u8(to_ptr, nk_f16x8_to_e5m2x8_neon_(vreinterpretq_f16_u16(hub_vec.u16x8))); break;
1056
+ case nk_f16_k: vst1q_u16((nk_u16_t *)to_ptr, hub_vec.u16x8); break;
1057
+ case nk_bf16_k: {
1058
+ float32x4_t low_f32x4 = vcvt_f32_f16(vget_low_f16(vreinterpretq_f16_u16(hub_vec.u16x8)));
1059
+ float32x4_t high_f32x4 = vcvt_high_f32_f16(vreinterpretq_f16_u16(hub_vec.u16x8));
1060
+ vst1_u16((nk_u16_t *)to_ptr, nk_f32x4_to_bf16x4_neon_(low_f32x4));
1061
+ vst1_u16((nk_u16_t *)(to_ptr + 8), nk_f32x4_to_bf16x4_neon_(high_f32x4));
1062
+ } break;
1063
+ case nk_f32_k: {
1064
+ vst1q_f32((nk_f32_t *)to_ptr, vcvt_f32_f16(vget_low_f16(vreinterpretq_f16_u16(hub_vec.u16x8))));
1065
+ vst1q_f32((nk_f32_t *)(to_ptr + 16), vcvt_high_f32_f16(vreinterpretq_f16_u16(hub_vec.u16x8)));
1066
+ } break;
1067
+ default: break;
1068
+ }
1069
+ }
1070
+
1071
+ // Handle remaining elements (0-7) with F32 hub or serial
1072
+ n = n % 8;
1073
+ from = from_ptr;
1074
+ to = to_ptr;
1075
+ if (n == 0) return;
1076
+ }
1077
+
1078
+ // F32 hub: 4 elements per iteration (f32x4 intermediate)
1079
+ nk_size_t batches = n / 4;
1080
+ nk_size_t tail = n % 4;
1081
+ nk_size_t from_step = 4 * nk_dtype_bits(from_type) / 8;
1082
+ nk_size_t to_step = 4 * nk_dtype_bits(to_type) / 8;
1083
+ nk_u8_t const *from_ptr = (nk_u8_t const *)from;
1084
+ nk_u8_t *to_ptr = (nk_u8_t *)to;
1085
+
1086
+ for (nk_size_t idx = 0; idx < batches; ++idx, from_ptr += from_step, to_ptr += to_step) {
1087
+ nk_b128_vec_t hub_vec;
1088
+
1089
+ // Upcast to f32x4 hub
1090
+ switch (from_type) {
1091
+ case nk_f32_k: hub_vec.f32x4 = vld1q_f32((nk_f32_t const *)from_ptr); break;
1092
+ case nk_f16_k: hub_vec.f32x4 = vcvt_f32_f16(vreinterpret_f16_u16(vld1_u16((nk_u16_t const *)from_ptr))); break;
1093
+ case nk_bf16_k: hub_vec.f32x4 = nk_bf16x4_to_f32x4_neon_(vld1_u16((nk_u16_t const *)from_ptr)); break;
1094
+ case nk_e4m3_k:
1095
+ hub_vec.u32s[0] = *(nk_u32_t const *)from_ptr;
1096
+ hub_vec.f32x4 = nk_e4m3x4_to_f32x4_neon_(*(nk_b32_vec_t *)&hub_vec);
1097
+ break;
1098
+ case nk_e5m2_k:
1099
+ hub_vec.u32s[0] = *(nk_u32_t const *)from_ptr;
1100
+ hub_vec.f32x4 = nk_e5m2x4_to_f32x4_neon_(*(nk_b32_vec_t *)&hub_vec);
1101
+ break;
1102
+ case nk_e2m3_k:
1103
+ hub_vec.u32s[0] = *(nk_u32_t const *)from_ptr;
1104
+ hub_vec.f32x4 = nk_e2m3x4_to_f32x4_neon_(*(nk_b32_vec_t *)&hub_vec);
1105
+ break;
1106
+ case nk_e3m2_k:
1107
+ hub_vec.u32s[0] = *(nk_u32_t const *)from_ptr;
1108
+ hub_vec.f32x4 = nk_e3m2x4_to_f32x4_neon_(*(nk_b32_vec_t *)&hub_vec);
1109
+ break;
1110
+ case nk_i32_k: hub_vec.f32x4 = vcvtq_f32_s32(vld1q_s32((nk_i32_t const *)from_ptr)); break;
1111
+ case nk_u32_k: hub_vec.f32x4 = vcvtq_f32_u32(vld1q_u32((nk_u32_t const *)from_ptr)); break;
1112
+ case nk_i16_k: hub_vec.f32x4 = nk_i16x4_to_f32x4_neon_(vld1_s16((nk_i16_t const *)from_ptr)); break;
1113
+ case nk_u16_k: hub_vec.f32x4 = nk_u16x4_to_f32x4_neon_(vld1_u16((nk_u16_t const *)from_ptr)); break;
1114
+ case nk_i8_k:
1115
+ hub_vec.u32s[0] = *(nk_u32_t const *)from_ptr;
1116
+ hub_vec.f32x4 = nk_i8x4_to_f32x4_neon_(*(nk_b32_vec_t *)&hub_vec);
1117
+ break;
1118
+ case nk_u8_k:
1119
+ hub_vec.u32s[0] = *(nk_u32_t const *)from_ptr;
1120
+ hub_vec.f32x4 = nk_u8x4_to_f32x4_neon_(*(nk_b32_vec_t *)&hub_vec);
1121
+ break;
1122
+ default: hub_vec.f32x4 = vdupq_n_f32(0); break;
1123
+ }
1124
+
1125
+ // Downcast from f32x4 hub and store
1126
+ switch (to_type) {
1127
+ case nk_f32_k: vst1q_f32((nk_f32_t *)to_ptr, hub_vec.f32x4); break;
1128
+ case nk_f16_k: vst1_u16((nk_u16_t *)to_ptr, vreinterpret_u16_f16(vcvt_f16_f32(hub_vec.f32x4))); break;
1129
+ case nk_bf16_k: vst1_u16((nk_u16_t *)to_ptr, nk_f32x4_to_bf16x4_neon_(hub_vec.f32x4)); break;
1130
+ case nk_e4m3_k:
1131
+ vst1_lane_u32((nk_u32_t *)to_ptr, vcreate_u32(nk_f32x4_to_e4m3x4_neon_(hub_vec.f32x4).u32), 0);
1132
+ break;
1133
+ case nk_e5m2_k:
1134
+ vst1_lane_u32((nk_u32_t *)to_ptr, vcreate_u32(nk_f32x4_to_e5m2x4_neon_(hub_vec.f32x4).u32), 0);
1135
+ break;
1136
+ case nk_e2m3_k:
1137
+ vst1_lane_u32((nk_u32_t *)to_ptr, vcreate_u32(nk_f32x4_to_e2m3x4_neon_(hub_vec.f32x4).u32), 0);
1138
+ break;
1139
+ case nk_e3m2_k:
1140
+ vst1_lane_u32((nk_u32_t *)to_ptr, vcreate_u32(nk_f32x4_to_e3m2x4_neon_(hub_vec.f32x4).u32), 0);
1141
+ break;
1142
+ case nk_i32_k: vst1q_s32((nk_i32_t *)to_ptr, vcvtnq_s32_f32(hub_vec.f32x4)); break;
1143
+ case nk_u32_k: vst1q_u32((nk_u32_t *)to_ptr, vcvtnq_u32_f32(hub_vec.f32x4)); break;
1144
+ case nk_i16_k: vst1_s16((nk_i16_t *)to_ptr, nk_f32x4_to_i16x4_neon_(hub_vec.f32x4)); break;
1145
+ case nk_u16_k: vst1_u16((nk_u16_t *)to_ptr, nk_f32x4_to_u16x4_neon_(hub_vec.f32x4)); break;
1146
+ case nk_i8_k:
1147
+ vst1_lane_u32((nk_u32_t *)to_ptr, vcreate_u32(nk_f32x4_to_i8x4_neon_(hub_vec.f32x4).u32), 0);
1148
+ break;
1149
+ case nk_u8_k:
1150
+ vst1_lane_u32((nk_u32_t *)to_ptr, vcreate_u32(nk_f32x4_to_u8x4_neon_(hub_vec.f32x4).u32), 0);
1151
+ break;
1152
+ default: break;
1153
+ }
1154
+ }
1155
+
1156
+ // Handle tail elements with serial fallback
1157
+ if (tail) nk_cast_serial(from_ptr, from_type, tail, to_ptr, to_type);
1158
+ }
1159
+
1160
+ #pragma endregion Public API
1161
+
1162
+ #if defined(__clang__)
1163
+ #pragma clang attribute pop
1164
+ #elif defined(__GNUC__)
1165
+ #pragma GCC pop_options
1166
+ #endif
1167
+
1168
+ #if defined(__cplusplus)
1169
+ } // extern "C"
1170
+ #endif
1171
+
1172
+ #endif // NK_TARGET_NEON
1173
+ #endif // NK_TARGET_ARM64_
1174
+ #endif // NK_CAST_NEON_H