changeset 28:c6c99ab1088a

*: add min/max functions and a big big refactor (again) agh, this time I added a few more implementations (and generally made the code just a little faster...)
author Paper <paper@tflc.us>
date Thu, 24 Apr 2025 00:54:02 -0400
parents d00b95f95dd1
children e59c91d050c0
files CMakeLists.txt include/vec/cpu.h include/vec/impl/gcc.h include/vec/impl/generic.h include/vec/impl/ppc/altivec.h include/vec/impl/x86/avx512bw.h include/vec/impl/x86/avx512dq.h include/vec/impl/x86/avx512f.h include/vec/impl/x86/sse2.h include/vec/impl/x86/sse3.h include/vec/impl/x86/sse41.h include/vec/impl/x86/sse42.h include/vec/vec.h src/cpu.c src/impl/arm/neon.c src/impl/fallback.c src/impl/gcc.c src/impl/generic.c src/impl/ppc/altivec.c src/impl/x86/avx2.c src/impl/x86/avx512bw.c src/impl/x86/avx512dq.c src/impl/x86/avx512f.c src/impl/x86/mmx.c src/impl/x86/sse2.c src/impl/x86/sse3.c src/impl/x86/sse41.c src/impl/x86/sse42.c src/vec.c test/test_arith.h
diffstat 30 files changed, 2346 insertions(+), 942 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Mon Nov 25 00:33:02 2024 -0500
+++ b/CMakeLists.txt	Thu Apr 24 00:54:02 2025 -0400
@@ -2,7 +2,13 @@
 
 project(vec VERSION 3.0.0 DESCRIPTION "a tiny C99 SIMD vector library" LANGUAGES C)
 
-add_library(vec SHARED "src/vec.c;src/cpu.c;src/impl/generic.c;src/impl/fallback.c")
+add_library(vec "src/vec.c")
+
+target_sources(vec PRIVATE
+	"src/cpu.c"
+	"src/impl/generic.c"
+	# "src/impl/fallback.c" -- deadcode
+)
 
 include(CheckCCompilerFlag)
 
@@ -49,10 +55,18 @@
 	if(COMPILER_HAS_SSE2)
 		set(COMPILER_SSE2_FLAGS "-msse2")
 	endif()
+	check_c_compiler_flag("-msse3" COMPILER_HAS_SSE3)
+	if(COMPILER_HAS_SSE3)
+		set(COMPILER_SSE3_FLAGS "-msse3")
+	endif()
 	check_c_compiler_flag("-msse4.1" COMPILER_HAS_SSE41)
 	if(COMPILER_HAS_SSE41)
 		set(COMPILER_SSE41_FLAGS "-msse4.1")
 	endif()
+	check_c_compiler_flag("-msse4.2" COMPILER_HAS_SSE42)
+	if(COMPILER_HAS_SSE42)
+		set(COMPILER_SSE42_FLAGS "-msse4.2")
+	endif()
 	check_c_compiler_flag("-mavx2" COMPILER_HAS_AVX2)
 	if(COMPILER_HAS_AVX2)
 		set(COMPILER_AVX2_FLAGS "-mavx2")
@@ -61,6 +75,14 @@
 	if(COMPILER_HAS_AVX512F)
 		set(COMPILER_AVX512F_FLAGS "-mavx512f")
 	endif()
+	check_c_compiler_flag("-mavx512bw" COMPILER_HAS_AVX512BW)
+	if(COMPILER_HAS_AVX512BW)
+		set(COMPILER_AVX512BW_FLAGS "-mavx512bw")
+	endif()
+	check_c_compiler_flag("-mavx512dq" COMPILER_HAS_AVX512DQ)
+	if(COMPILER_HAS_AVX512DQ)
+		set(COMPILER_AVX512DQ_FLAGS "-mavx512dq")
+	endif()
 endif()
 
 if(COMPILER_HAS_ALTIVEC)
@@ -87,12 +109,24 @@
 	target_compile_definitions(vec PRIVATE "-DVEC_COMPILER_HAS_SSE2")
 endif()
 
+if(COMPILER_HAS_SSE3)
+	target_sources(vec PRIVATE "src/impl/x86/sse3.c")
+	set_source_files_properties("src/impl/x86/sse3.c" PROPERTIES COMPILE_FLAGS "${COMPILER_SSE3_FLAGS}")
+	target_compile_definitions(vec PRIVATE "-DVEC_COMPILER_HAS_SSE3")
+endif()
+
 if(COMPILER_HAS_SSE41)
 	target_sources(vec PRIVATE "src/impl/x86/sse41.c")
 	set_source_files_properties("src/impl/x86/sse41.c" PROPERTIES COMPILE_FLAGS "${COMPILER_SSE41_FLAGS}")
 	target_compile_definitions(vec PRIVATE "-DVEC_COMPILER_HAS_SSE41")
 endif()
 
+if(COMPILER_HAS_SSE42)
+	target_sources(vec PRIVATE "src/impl/x86/sse42.c")
+	set_source_files_properties("src/impl/x86/sse42.c" PROPERTIES COMPILE_FLAGS "${COMPILER_SSE42_FLAGS}")
+	target_compile_definitions(vec PRIVATE "-DVEC_COMPILER_HAS_SSE42")
+endif()
+
 if(COMPILER_HAS_AVX2)
 	target_sources(vec PRIVATE "src/impl/x86/avx2.c")
 	set_source_files_properties("src/impl/x86/avx2.c" PROPERTIES COMPILE_FLAGS "${COMPILER_AVX2_FLAGS}")
@@ -105,6 +139,17 @@
 	target_compile_definitions(vec PRIVATE "-DVEC_COMPILER_HAS_AVX512F")
 endif()
 
+if(COMPILER_HAS_AVX512BW)
+	target_sources(vec PRIVATE "src/impl/x86/avx512bw.c")
+	set_source_files_properties("src/impl/x86/avx512bw.c" PROPERTIES COMPILE_FLAGS "${COMPILER_AVX512BW_FLAGS}")
+	target_compile_definitions(vec PRIVATE "-DVEC_COMPILER_HAS_AVX512BW")
+endif()
+
+if(COMPILER_HAS_AVX512DQ)
+	target_sources(vec PRIVATE "src/impl/x86/avx512dq.c")
+	set_source_files_properties("src/impl/x86/avx512dq.c" PROPERTIES COMPILE_FLAGS "${COMPILER_AVX512DQ_FLAGS}")
+	target_compile_definitions(vec PRIVATE "-DVEC_COMPILER_HAS_AVX512DQ")
+endif()
 
 #########################################################################
 # integer types; it's nice to accommodate for older broken systems that
--- a/include/vec/cpu.h	Mon Nov 25 00:33:02 2024 -0500
+++ b/include/vec/cpu.h	Thu Apr 24 00:54:02 2025 -0400
@@ -42,6 +42,8 @@
 	VEC_CPU_HAS_AVX2 = (1 << 9),
 	VEC_CPU_HAS_AVX512F = (1 << 10),
 	VEC_CPU_HAS_NEON = (1 << 11),
+	VEC_CPU_HAS_AVX512BW = (1 << 12),
+	VEC_CPU_HAS_AVX512DQ = (1 << 13),
 };
 
 // NOT thread-safe.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/vec/impl/gcc.h	Thu Apr 24 00:54:02 2025 -0400
@@ -0,0 +1,80 @@
+/**
+ * vec - a tiny SIMD vector library in plain C99
+ * 
+ * Copyright (c) 2024 Paper
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+**/
+
+/* Generic array-based implementation. */
+
+#ifndef VEC_IMPL_GENERIC_H_
+#define VEC_IMPL_GENERIC_H_
+
+#include "vec/vec.h"
+
+// 16-bit
+extern const vint8x2_impl    vint8x2_impl_generic;
+extern const vuint8x2_impl   vuint8x2_impl_generic;
+
+// 32-bit
+extern const vint8x4_impl    vint8x4_impl_generic;
+extern const vuint8x4_impl   vuint8x4_impl_generic;
+extern const vint16x2_impl   vint16x2_impl_generic;
+extern const vuint16x2_impl  vuint16x2_impl_generic;
+
+// 64-bit
+extern const vint8x8_impl    vint8x8_impl_generic;
+extern const vuint8x8_impl   vuint8x8_impl_generic;
+extern const vint16x4_impl   vint16x4_impl_generic;
+extern const vuint16x4_impl  vuint16x4_impl_generic;
+extern const vint32x2_impl   vint32x2_impl_generic;
+extern const vuint32x2_impl  vuint32x2_impl_generic;
+
+// 128-bit
+extern const vint8x16_impl   vint8x16_impl_generic;
+extern const vuint8x16_impl  vuint8x16_impl_generic;
+extern const vint16x8_impl   vint16x8_impl_generic;
+extern const vuint16x8_impl  vuint16x8_impl_generic;
+extern const vint32x4_impl   vint32x4_impl_generic;
+extern const vuint32x4_impl  vuint32x4_impl_generic;
+extern const vint64x2_impl   vint64x2_impl_generic;
+extern const vuint64x2_impl  vuint64x2_impl_generic;
+
+// 256-bit
+extern const vint8x32_impl   vint8x32_impl_generic;
+extern const vuint8x32_impl  vuint8x32_impl_generic;
+extern const vint16x16_impl  vint16x16_impl_generic;
+extern const vuint16x16_impl vuint16x16_impl_generic;
+extern const vint32x8_impl   vint32x8_impl_generic;
+extern const vuint32x8_impl  vuint32x8_impl_generic;
+extern const vint64x4_impl   vint64x4_impl_generic;
+extern const vuint64x4_impl  vuint64x4_impl_generic;
+
+// 512-bit
+extern const vint8x64_impl   vint8x64_impl_generic;
+extern const vuint8x64_impl  vuint8x64_impl_generic;
+extern const vint16x32_impl  vint16x32_impl_generic;
+extern const vuint16x32_impl vuint16x32_impl_generic;
+extern const vint32x16_impl  vint32x16_impl_generic;
+extern const vuint32x16_impl vuint32x16_impl_generic;
+extern const vint64x8_impl   vint64x8_impl_generic;
+extern const vuint64x8_impl  vuint64x8_impl_generic;
+
+#endif /* VEC_IMPL_GENERIC_H_ */
--- a/include/vec/impl/generic.h	Mon Nov 25 00:33:02 2024 -0500
+++ b/include/vec/impl/generic.h	Thu Apr 24 00:54:02 2025 -0400
@@ -29,65 +29,6 @@
 
 #include "vec/vec.h"
 
-#define VEC_DEFINE_GENERIC_OPERATIONS_SIGN(sign, csign, bits, size) \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_splat(vec_##sign##int##bits x); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_load(const vec_##sign##int##bits in[size]); \
-	void v##sign##int##bits##x##size##_generic_store(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_div(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_avg(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_not(v##sign##int##bits##x##size vec); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmplt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmple(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpeq(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpge(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpgt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_lshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_rshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_lrshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2);
-
-#define VEC_DEFINE_GENERIC_OPERATIONS(bits, size) \
-	VEC_DEFINE_GENERIC_OPERATIONS_SIGN( ,  , bits, size) \
-	VEC_DEFINE_GENERIC_OPERATIONS_SIGN(u, U, bits, size)
-
-// 16-bit
-VEC_DEFINE_GENERIC_OPERATIONS(8, 2)
-
-// 32-bit
-VEC_DEFINE_GENERIC_OPERATIONS(8, 4)
-VEC_DEFINE_GENERIC_OPERATIONS(16, 2)
-
-// 64-bit
-VEC_DEFINE_GENERIC_OPERATIONS(8, 8)
-VEC_DEFINE_GENERIC_OPERATIONS(16, 4)
-VEC_DEFINE_GENERIC_OPERATIONS(32, 2)
-
-// 128-bit
-VEC_DEFINE_GENERIC_OPERATIONS(8, 16)
-VEC_DEFINE_GENERIC_OPERATIONS(16, 8)
-VEC_DEFINE_GENERIC_OPERATIONS(32, 4)
-VEC_DEFINE_GENERIC_OPERATIONS(64, 2)
-
-// 256-bit
-VEC_DEFINE_GENERIC_OPERATIONS(8, 32)
-VEC_DEFINE_GENERIC_OPERATIONS(16, 16)
-VEC_DEFINE_GENERIC_OPERATIONS(32, 8)
-VEC_DEFINE_GENERIC_OPERATIONS(64, 4)
-
-// 512-bit
-VEC_DEFINE_GENERIC_OPERATIONS(8, 64)
-VEC_DEFINE_GENERIC_OPERATIONS(16, 32)
-VEC_DEFINE_GENERIC_OPERATIONS(32, 16)
-VEC_DEFINE_GENERIC_OPERATIONS(64, 8)
-
-#undef VEC_DEFINE_GENERIC_OPERATIONS
-#undef VEC_DEFINE_GENERIC_OPERATIONS_SIGN
-
 // 16-bit
 extern const vint8x2_impl    vint8x2_impl_generic;
 extern const vuint8x2_impl   vuint8x2_impl_generic;
--- a/include/vec/impl/ppc/altivec.h	Mon Nov 25 00:33:02 2024 -0500
+++ b/include/vec/impl/ppc/altivec.h	Thu Apr 24 00:54:02 2025 -0400
@@ -22,8 +22,6 @@
  * SOFTWARE.
 **/
 
-/* Altivec vector support. */
-
 #ifndef VEC_IMPL_PPC_ALTIVEC_H_
 #define VEC_IMPL_PPC_ALTIVEC_H_
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/vec/impl/x86/avx512bw.h	Thu Apr 24 00:54:02 2025 -0400
@@ -0,0 +1,35 @@
+/**
+ * vec - a tiny SIMD vector library in C99
+ * 
+ * Copyright (c) 2024 Paper
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+**/
+
+#ifndef VEC_IMPL_X86_AVX512BW_H_
+#define VEC_IMPL_X86_AVX512BW_H_
+
+#include "vec/vec.h"
+
+extern const vint8x64_impl   vint8x64_impl_avx512bw;
+extern const vint16x32_impl  vint16x32_impl_avx512bw;
+extern const vuint8x64_impl  vuint8x64_impl_avx512bw;
+extern const vuint16x32_impl vuint16x32_impl_avx512bw;
+
+#endif /* VEC_IMPL_X86_AVX512BW_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/vec/impl/x86/avx512dq.h	Thu Apr 24 00:54:02 2025 -0400
@@ -0,0 +1,33 @@
+/**
+ * vec - a tiny SIMD vector library in C99
+ * 
+ * Copyright (c) 2024 Paper
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+**/
+
+#ifndef VEC_IMPL_X86_AVX512DQ_H_
+#define VEC_IMPL_X86_AVX512DQ_H_
+
+#include "vec/vec.h"
+
+extern const vint64x8_impl   vint64x8_impl_avx512dq;
+extern const vuint64x8_impl  vuint64x8_impl_avx512dq;
+
+#endif /* VEC_IMPL_X86_AVX512DQ_H_ */
--- a/include/vec/impl/x86/avx512f.h	Mon Nov 25 00:33:02 2024 -0500
+++ b/include/vec/impl/x86/avx512f.h	Thu Apr 24 00:54:02 2025 -0400
@@ -27,12 +27,8 @@
 
 #include "vec/vec.h"
 
-extern const vint8x64_impl   vint8x64_impl_avx512f;
-extern const vint16x32_impl  vint16x32_impl_avx512f;
 extern const vint32x16_impl  vint32x16_impl_avx512f;
 extern const vint64x8_impl   vint64x8_impl_avx512f;
-extern const vuint8x64_impl  vuint8x64_impl_avx512f;
-extern const vuint16x32_impl vuint16x32_impl_avx512f;
 extern const vuint32x16_impl vuint32x16_impl_avx512f;
 extern const vuint64x8_impl  vuint64x8_impl_avx512f;
 
--- a/include/vec/impl/x86/sse2.h	Mon Nov 25 00:33:02 2024 -0500
+++ b/include/vec/impl/x86/sse2.h	Thu Apr 24 00:54:02 2025 -0400
@@ -27,33 +27,6 @@
 
 #include "vec/vec.h"
 
-// These are only extern because the SSE 4.1 translation unit needs to access it.
-#define VEC_DEFINE_SSE2_OPERATIONS_SIGN(sign, csign, bits, size) \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_splat(vec_##sign##int##bits x); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_load_aligned(const vec_##sign##int##bits in[size]); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_load(const vec_##sign##int##bits in[size]); \
-	void v##sign##int##bits##x##size##_sse2_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]); \
-	void v##sign##int##bits##x##size##_sse2_store(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_cmpeq(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2);
-
-#define VEC_DEFINE_SSE2_OPERATIONS(bits, size) \
-	VEC_DEFINE_SSE2_OPERATIONS_SIGN( ,  , bits, size) \
-	VEC_DEFINE_SSE2_OPERATIONS_SIGN(u, U, bits, size)
-
-VEC_DEFINE_SSE2_OPERATIONS(8, 16)
-VEC_DEFINE_SSE2_OPERATIONS(16, 8)
-VEC_DEFINE_SSE2_OPERATIONS(32, 4)
-VEC_DEFINE_SSE2_OPERATIONS(64, 2)
-
-#undef VEC_DEFINE_SSE2_OPERATIONS
-#undef VEC_DEFINE_SSE2_OPERATIONS_SIGN
-
 extern const vint8x16_impl vint8x16_impl_sse2;
 extern const vint16x8_impl vint16x8_impl_sse2;
 extern const vint32x4_impl vint32x4_impl_sse2;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/vec/impl/x86/sse3.h	Thu Apr 24 00:54:02 2025 -0400
@@ -0,0 +1,39 @@
+/**
+ * vec - a tiny SIMD vector library in C99
+ * 
+ * Copyright (c) 2024 Paper
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+**/
+
+#ifndef VEC_IMPL_X86_SSE3_H_
+#define VEC_IMPL_X86_SSE3_H_
+
+#include "vec/vec.h"
+
+extern const vint8x16_impl vint8x16_impl_sse3;
+extern const vint16x8_impl vint16x8_impl_sse3;
+extern const vint32x4_impl vint32x4_impl_sse3;
+extern const vint64x2_impl vint64x2_impl_sse3;
+extern const vuint8x16_impl vuint8x16_impl_sse3;
+extern const vuint16x8_impl vuint16x8_impl_sse3;
+extern const vuint32x4_impl vuint32x4_impl_sse3;
+extern const vuint64x2_impl vuint64x2_impl_sse3;
+
+#endif /* VEC_IMPL_X86_SSE3_H_ */
--- a/include/vec/impl/x86/sse41.h	Mon Nov 25 00:33:02 2024 -0500
+++ b/include/vec/impl/x86/sse41.h	Thu Apr 24 00:54:02 2025 -0400
@@ -27,7 +27,13 @@
 
 #include "vec/vec.h"
 
-extern const vint32x4_impl  vint32x4_impl_sse41;
+extern const vint8x16_impl vint8x16_impl_sse41;
+extern const vint16x8_impl vint16x8_impl_sse41;
+extern const vint32x4_impl vint32x4_impl_sse41;
+extern const vint64x2_impl vint64x2_impl_sse41;
+extern const vuint8x16_impl vuint8x16_impl_sse41;
+extern const vuint16x8_impl vuint16x8_impl_sse41;
 extern const vuint32x4_impl vuint32x4_impl_sse41;
+extern const vuint64x2_impl vuint64x2_impl_sse41;
 
 #endif /* VEC_IMPL_X86_SSE41_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/vec/impl/x86/sse42.h	Thu Apr 24 00:54:02 2025 -0400
@@ -0,0 +1,33 @@
+/**
+ * vec - a tiny SIMD vector library in C99
+ * 
+ * Copyright (c) 2024 Paper
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+**/
+
+#ifndef VEC_IMPL_X86_SSE42_H_
+#define VEC_IMPL_X86_SSE42_H_
+
+#include "vec/vec.h"
+
+extern const vint64x2_impl vint64x2_impl_sse42;
+extern const vuint64x2_impl vuint64x2_impl_sse42;
+
+#endif /* VEC_IMPL_X86_SSE42_H_ */
--- a/include/vec/vec.h	Mon Nov 25 00:33:02 2024 -0500
+++ b/include/vec/vec.h	Thu Apr 24 00:54:02 2025 -0400
@@ -84,6 +84,16 @@
 		[!!sizeof (struct { int __error_if_negative: (x) ? 2 : -1; })]
 #endif
 
+#if VEC_GNUC_HAS_ATTRIBUTE(__always_inline__, 3, 1, 1)
+# define VEC_ALWAYS_INLINE __attribute__((__always_inline__))
+#elif VEC_MSVC_ATLEAST(12, 0, 0)
+# define VEC_ALWAYS_INLINE __forceinline
+#else
+# define VEC_ALWAYS_INLINE
+#endif
+
+#define VEC_FUNC_IMPL static inline VEC_ALWAYS_INLINE
+
 //////////////////////////////////////////////////////////////////////////////
 // Detect compiler SIMD support
 
@@ -675,7 +685,6 @@
 		v##sign##int##bits##x##size (*band)(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
 		v##sign##int##bits##x##size (*bor)(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
 		v##sign##int##bits##x##size (*bxor)(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-		v##sign##int##bits##x##size (*bnot)(v##sign##int##bits##x##size vec); \
 		v##sign##int##bits##x##size (*lshift)(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2); \
 		v##sign##int##bits##x##size (*rshift)(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2); \
 		v##sign##int##bits##x##size (*lrshift)(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2); \
@@ -684,6 +693,8 @@
 		v##sign##int##bits##x##size (*cmpeq)(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
 		v##sign##int##bits##x##size (*cmpge)(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
 		v##sign##int##bits##x##size (*cmpgt)(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
+		v##sign##int##bits##x##size (*min)(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
+		v##sign##int##bits##x##size (*max)(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
 	} v##sign##int##bits##x##size##_impl;
 
 #define VEC_DEFINE_IMPL_STRUCT(bits, size) \
@@ -723,53 +734,56 @@
 #undef VEC_DEFINE_IMPL_STRUCT
 #undef VEC_DEFINE_IMPL_STRUCT_SIGN
 
+/* these are generally read only, unless you REALLY know
+ * exactly what you're doing. */
+
 // 16-bit
-extern const vint8x2_impl   *vint8x2_impl_cpu;
-extern const vuint8x2_impl  *vuint8x2_impl_cpu;
+extern vint8x2_impl   vint8x2_impl_cpu;
+extern vuint8x2_impl  vuint8x2_impl_cpu;
 
 // 32-bit
-extern const vint8x4_impl   *vint8x4_impl_cpu;
-extern const vuint8x4_impl  *vuint8x4_impl_cpu;
-extern const vint16x2_impl  *vint16x2_impl_cpu;
-extern const vuint16x2_impl *vuint16x2_impl_cpu;
+extern vint8x4_impl   vint8x4_impl_cpu;
+extern vuint8x4_impl  vuint8x4_impl_cpu;
+extern vint16x2_impl  vint16x2_impl_cpu;
+extern vuint16x2_impl vuint16x2_impl_cpu;
 
 // 64-bit
-extern const vint8x8_impl   *vint8x8_impl_cpu;
-extern const vuint8x8_impl  *vuint8x8_impl_cpu;
-extern const vint16x4_impl  *vint16x4_impl_cpu;
-extern const vuint16x4_impl *vuint16x4_impl_cpu;
-extern const vint32x2_impl  *vint32x2_impl_cpu;
-extern const vuint32x2_impl *vuint32x2_impl_cpu;
+extern vint8x8_impl   vint8x8_impl_cpu;
+extern vuint8x8_impl  vuint8x8_impl_cpu;
+extern vint16x4_impl  vint16x4_impl_cpu;
+extern vuint16x4_impl vuint16x4_impl_cpu;
+extern vint32x2_impl  vint32x2_impl_cpu;
+extern vuint32x2_impl vuint32x2_impl_cpu;
 
 // 128-bit
-extern const vint8x16_impl  *vint8x16_impl_cpu;
-extern const vuint8x16_impl *vuint8x16_impl_cpu;
-extern const vint16x8_impl  *vint16x8_impl_cpu;
-extern const vuint16x8_impl *vuint16x8_impl_cpu;
-extern const vint32x4_impl  *vint32x4_impl_cpu;
-extern const vuint32x4_impl *vuint32x4_impl_cpu;
-extern const vint64x2_impl  *vint64x2_impl_cpu;
-extern const vuint64x2_impl *vuint64x2_impl_cpu;
+extern vint8x16_impl  vint8x16_impl_cpu;
+extern vuint8x16_impl vuint8x16_impl_cpu;
+extern vint16x8_impl  vint16x8_impl_cpu;
+extern vuint16x8_impl vuint16x8_impl_cpu;
+extern vint32x4_impl  vint32x4_impl_cpu;
+extern vuint32x4_impl vuint32x4_impl_cpu;
+extern vint64x2_impl  vint64x2_impl_cpu;
+extern vuint64x2_impl vuint64x2_impl_cpu;
 
 // 256-bit
-extern const vint8x32_impl   *vint8x32_impl_cpu;
-extern const vuint8x32_impl  *vuint8x32_impl_cpu;
-extern const vint16x16_impl  *vint16x16_impl_cpu;
-extern const vuint16x16_impl *vuint16x16_impl_cpu;
-extern const vint32x8_impl   *vint32x8_impl_cpu;
-extern const vuint32x8_impl  *vuint32x8_impl_cpu;
-extern const vint64x4_impl   *vint64x4_impl_cpu;
-extern const vuint64x4_impl  *vuint64x4_impl_cpu;
+extern vint8x32_impl   vint8x32_impl_cpu;
+extern vuint8x32_impl  vuint8x32_impl_cpu;
+extern vint16x16_impl  vint16x16_impl_cpu;
+extern vuint16x16_impl vuint16x16_impl_cpu;
+extern vint32x8_impl   vint32x8_impl_cpu;
+extern vuint32x8_impl  vuint32x8_impl_cpu;
+extern vint64x4_impl   vint64x4_impl_cpu;
+extern vuint64x4_impl  vuint64x4_impl_cpu;
 
 // 512-bit
-extern const vint8x64_impl  *vint8x64_impl_cpu;
-extern const vuint8x64_impl *vuint8x64_impl_cpu;
-extern const vint16x32_impl  *vint16x32_impl_cpu;
-extern const vuint16x32_impl *vuint16x32_impl_cpu;
-extern const vint32x16_impl  *vint32x16_impl_cpu;
-extern const vuint32x16_impl *vuint32x16_impl_cpu;
-extern const vint64x8_impl  *vint64x8_impl_cpu;
-extern const vuint64x8_impl *vuint64x8_impl_cpu;
+extern vint8x64_impl  vint8x64_impl_cpu;
+extern vuint8x64_impl vuint8x64_impl_cpu;
+extern vint16x32_impl  vint16x32_impl_cpu;
+extern vuint16x32_impl vuint16x32_impl_cpu;
+extern vint32x16_impl  vint32x16_impl_cpu;
+extern vuint32x16_impl vuint32x16_impl_cpu;
+extern vint64x8_impl  vint64x8_impl_cpu;
+extern vuint64x8_impl vuint64x8_impl_cpu;
 
 //////////////////////////////////////////////////////////////////////////////
 // declared as inline for  ! performance : )
@@ -777,112 +791,122 @@
 #define VEC_DEFINE_OPERATIONS_SIGN(sign, bits, size) \
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_splat(vec_##sign##int##bits x) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->splat(x); \
+		return v##sign##int##bits##x##size##_impl_cpu.splat(x); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_load_aligned(const vec_##sign##int##bits in[size]) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->load_aligned(in); \
+		return v##sign##int##bits##x##size##_impl_cpu.load_aligned(in); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_load(const vec_##sign##int##bits in[size]) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->load(in); \
+		return v##sign##int##bits##x##size##_impl_cpu.load(in); \
 	} \
 	\
 	inline void v##sign##int##bits##x##size##_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
 	{ \
-		v##sign##int##bits##x##size##_impl_cpu->store_aligned(vec, out); \
+		v##sign##int##bits##x##size##_impl_cpu.store_aligned(vec, out); \
 	} \
 	\
 	inline void v##sign##int##bits##x##size##_store(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->store(vec, out); \
+		return v##sign##int##bits##x##size##_impl_cpu.store(vec, out); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->add(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.add(vec1, vec2); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->sub(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.sub(vec1, vec2); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->mul(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.mul(vec1, vec2); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_div(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->div(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.div(vec1, vec2); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_avg(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->avg(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.avg(vec1, vec2); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->band(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.band(vec1, vec2); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->bor(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.bor(vec1, vec2); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->bxor(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.bxor(vec1, vec2); \
 	} \
 	\
-	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_not(v##sign##int##bits##x##size vec) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_not(v##sign##int##bits##x##size vec) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->bnot(vec); \
+		return v##sign##int##bits##x##size##_xor(vec, v##sign##int##bits##x##size##_splat((vec_##sign##int##bits)VEC_UINT##bits##_MAX)); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_cmplt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->cmplt(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.cmplt(vec1, vec2); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_cmple(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->cmple(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.cmple(vec1, vec2); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_cmpeq(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->cmpeq(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.cmpeq(vec1, vec2); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_cmpge(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->cmpge(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.cmpge(vec1, vec2); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_cmpgt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->cmpgt(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.cmpgt(vec1, vec2); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_lshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->lshift(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.lshift(vec1, vec2); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_rshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->rshift(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.rshift(vec1, vec2); \
 	} \
 	\
 	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_lrshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
 	{ \
-		return v##sign##int##bits##x##size##_impl_cpu->lrshift(vec1, vec2); \
+		return v##sign##int##bits##x##size##_impl_cpu.lrshift(vec1, vec2); \
+	} \
+	\
+	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_min(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		return v##sign##int##bits##x##size##_impl_cpu.min(vec1, vec2); \
+	} \
+	\
+	inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_max(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		return v##sign##int##bits##x##size##_impl_cpu.max(vec1, vec2); \
 	}
 
 #define VEC_DEFINE_OPERATIONS(bits, size) \
--- a/src/cpu.c	Mon Nov 25 00:33:02 2024 -0500
+++ b/src/cpu.c	Thu Apr 24 00:54:02 2025 -0400
@@ -362,7 +362,29 @@
 	if (vec_CPU_OSSavesYMM && (vec_CPU_CPUIDMaxFunction >= 7)) {
 		int a, b, c, d;
 		VEC_CPU_CPUID(7, a, b, c, d);
-		return b & 0x00000020;
+		return b & 0x00010000;
+		(void)a, (void)c, (void)d;
+	}
+	return 0;
+}
+
+static inline int vec_CPU_have_AVX512DQ(void)
+{
+	if (vec_CPU_OSSavesYMM && (vec_CPU_CPUIDMaxFunction >= 7)) {
+		int a, b, c, d;
+		VEC_CPU_CPUID(7, a, b, c, d);
+		return b & 0x00020000;
+		(void)a, (void)c, (void)d;
+	}
+	return 0;
+}
+
+static inline int vec_CPU_have_AVX512BW(void)
+{
+	if (vec_CPU_OSSavesYMM && (vec_CPU_CPUIDMaxFunction >= 7)) {
+		int a, b, c, d;
+		VEC_CPU_CPUID(7, a, b, c, d);
+		return b & 0x40000000;
 		(void)a, (void)c, (void)d;
 	}
 	return 0;
@@ -491,6 +513,10 @@
 			vec_CPU_features |= VEC_CPU_HAS_AVX2;
 		if (vec_CPU_have_AVX512F())
 			vec_CPU_features |= VEC_CPU_HAS_AVX512F;
+		if (vec_CPU_have_AVX512BW())
+			vec_CPU_features |= VEC_CPU_HAS_AVX512BW;
+		if (vec_CPU_have_AVX512DQ())
+			vec_CPU_features |= VEC_CPU_HAS_AVX512DQ;
 		if (vec_CPU_have_NEON())
 			vec_CPU_features |= VEC_CPU_HAS_NEON;
 	}
--- a/src/impl/arm/neon.c	Mon Nov 25 00:33:02 2024 -0500
+++ b/src/impl/arm/neon.c	Thu Apr 24 00:54:02 2025 -0400
@@ -39,19 +39,19 @@
 	VEC_STATIC_ASSERT(VEC_ALIGNOF(sign##int##bits##x##size##_t) <= VEC_ALIGNOF(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " alignment needs to be expanded to fit intrinsic type size"); \
 	VEC_STATIC_ASSERT(sizeof(sign##int##bits##x##size##_t) <= sizeof(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " needs to be expanded to fit intrinsic type size"); \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_load_aligned(const vec_##sign##int##bits in[size]) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_load_aligned(const vec_##sign##int##bits in[size]) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data vec; \
 		vec.neon = vld1_##sign##bits(in); \
 		return vec.vec; \
 	} \
 	\
-	static void v##sign##int##bits##x##size##_neon_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_neon_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
 	{ \
 		vstore_lane_##bits(sign, ((union v##sign##int##bits##x##size##_impl_data *)&vec)->neon, out); \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -60,7 +60,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -69,7 +69,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -78,7 +78,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_lshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_lshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union vuint##bits##x##size##_impl_data *vec2d = (union vuint##bits##x##size##_impl_data *)&vec2; \
@@ -87,7 +87,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -96,7 +96,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -105,7 +105,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_neon_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -114,29 +114,18 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_neon = { \
-		v##sign##int##bits##x##size##_fallback_splat, \
-		v##sign##int##bits##x##size##_neon_load_aligned, \
-		v##sign##int##bits##x##size##_neon_load_aligned, \
-		v##sign##int##bits##x##size##_neon_store_aligned, \
-		v##sign##int##bits##x##size##_neon_store_aligned, \
-		v##sign##int##bits##x##size##_neon_add, \
-		v##sign##int##bits##x##size##_neon_sub, \
-		v##sign##int##bits##x##size##_neon_mul, \
-		v##sign##int##bits##x##size##_fallback_div, \
-		v##sign##int##bits##x##size##_fallback_avg, \
-		v##sign##int##bits##x##size##_neon_and, \
-		v##sign##int##bits##x##size##_neon_or, \
-		v##sign##int##bits##x##size##_neon_xor, \
-		v##sign##int##bits##x##size##_fallback_not, \
-		v##sign##int##bits##x##size##_neon_lshift, \
-		v##sign##int##bits##x##size##_fallback_rshift, \
-		v##sign##int##bits##x##size##_fallback_lrshift, \
-		v##sign##int##bits##x##size##_fallback_cmplt, \
-		v##sign##int##bits##x##size##_fallback_cmple, \
-		v##sign##int##bits##x##size##_fallback_cmpeq, \
-		v##sign##int##bits##x##size##_fallback_cmpge, \
-		v##sign##int##bits##x##size##_fallback_cmpgt, \
+	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_neon = { \
+		.load = v##sign##int##bits##x##size##_neon_load_aligned, \
+		.load_aligned = v##sign##int##bits##x##size##_neon_load_aligned, \
+		.store = v##sign##int##bits##x##size##_neon_store_aligned, \
+		.store_aligned = v##sign##int##bits##x##size##_neon_store_aligned, \
+		.add = v##sign##int##bits##x##size##_neon_add, \
+		.sub = v##sign##int##bits##x##size##_neon_sub, \
+		.mul = v##sign##int##bits##x##size##_neon_mul, \
+		.band = v##sign##int##bits##x##size##_neon_and, \
+		.bor = v##sign##int##bits##x##size##_neon_or, \
+		.bxor = v##sign##int##bits##x##size##_neon_xor, \
+		.lshift = v##sign##int##bits##x##size##_neon_lshift, \
 	};
 
 #define VEC_DEFINE_OPERATIONS(bits, size) \
@@ -244,7 +233,7 @@
 
 // NEON doesn't have native 64-bit multiplication, so we have
 // to do it ourselves
-static inline int64x2_t vmulq_s64(const int64x2_t a, const int64x2_t b)
+VEC_FUNC_IMPL int64x2_t vmulq_s64(const int64x2_t a, const int64x2_t b)
 {
     const uint32x2_t ac = vreinterpret_u32_s32(vmovn_s64(a));
     const uint32x2_t pr = vreinterpret_u32_s32(vmovn_s64(b));
@@ -254,7 +243,7 @@
     return vreinterpretq_s64_u64(vmlal_u32(vreinterpretq_u64_s64(vshlq_n_s64(vreinterpretq_s64_u64(vpaddlq_u32(vreinterpretq_u32_s32(hi))), 32)), ac, pr));
 }
 
-static inline uint64x2_t vmulq_u64(const uint64x2_t a, const uint64x2_t b)
+VEC_FUNC_IMPL uint64x2_t vmulq_u64(const uint64x2_t a, const uint64x2_t b)
 {
     const uint32x2_t ac = vmovn_u64(a);
     const uint32x2_t pr = vmovn_u64(b);
--- a/src/impl/fallback.c	Mon Nov 25 00:33:02 2024 -0500
+++ b/src/impl/fallback.c	Thu Apr 24 00:54:02 2025 -0400
@@ -1,3 +1,27 @@
+/**
+ * vec - a tiny SIMD vector library in C99
+ * 
+ * Copyright (c) 2024 Paper
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+**/
+
 #include "vec/impl/fallback.h"
 
 #include <string.h>
@@ -7,8 +31,8 @@
 // memory is unknown or yields incorrect results from the generic functions.
 // This is *extremely* unlikely; for x86 the layout is exactly the same in
 // memory as the generic functions (i.e. it is literally stored as an array of
-// integers). This is likely true for AltiVec and NEON as well, but those
-// aren't tested for now.
+// integers). This is also true for AltiVec. This is likely true for NEON as well,
+// but that isn't tested for now.
 
 #define VEC_FALLBACK_OPERATION(op, sign, csign, bits, size) \
 	do { \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/impl/gcc.c	Thu Apr 24 00:54:02 2025 -0400
@@ -0,0 +1,517 @@
+/**
+ * vec - a tiny SIMD vector library in C99
+ * 
+ * Copyright (c) 2024 Paper
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+**/
+
+#include "vec/impl/gcc.h"
+
+// -----------------------------------------------------------------
+
+#define VEC_GENERIC_OPERATION(op, sign, csign, bits, size) \
+	do { \
+		for (int i = 0; i < size; i++) \
+			((union v##sign##int##bits##x##size##_impl_data *)&vec1)->impl[i] = (op); \
+	\
+		return vec1; \
+	} while (0)
+
+#define VEC_GENERIC_BUILTIN_OPERATION(op, sign, csign, bits, size) \
+	VEC_GENERIC_OPERATION(((union v##sign##int##bits##x##size##_impl_data *)&vec1)->impl[i] op ((union v##sign##int##bits##x##size##_impl_data *)&vec2)->impl[i], sign, csign, bits, size)
+
+#define VEC_GENERIC_CMP(op, sign, csign, bits, size) \
+	VEC_GENERIC_OPERATION((((union v##sign##int##bits##x##size##_impl_data *)&vec1)->impl[i] op ((union v##sign##int##bits##x##size##_impl_data *)&vec2)->impl[i]) ? VEC_UINT##bits##_MAX : 0, sign, csign, bits, size)
+
+// TODO implement these so we don't waste stack space by doing the
+// generics
+#define VEC_GENERIC_DEFINE_OPERATIONS_SIGN(sign, csign, bits, size) \
+	union v##sign##int##bits##x##size##_impl_data { \
+		v##sign##int##bits##x##size vec; \
+		vec_##sign##int##bits impl[size]; \
+	}; \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_splat(vec_##sign##int##bits x) \
+	{ \
+		v##sign##int##bits##x##size vec; \
+		for (int i = 0; i < size; i++) \
+			((union v##sign##int##bits##x##size##_impl_data *)&vec)->impl[i] = x; \
+		return vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_load_aligned(const vec_##sign##int##bits in[size]) \
+	{ \
+		v##sign##int##bits##x##size vec; \
+		memcpy(&vec, in, sizeof(vec_##sign##int##bits) * size); \
+		return vec; \
+	} \
+	\
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_generic_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	{ \
+		memcpy(out, &vec, sizeof(vec_##sign##int##bits) * size); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		VEC_GENERIC_BUILTIN_OPERATION(+, sign, csign, bits, size); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		VEC_GENERIC_BUILTIN_OPERATION(-, sign, csign, bits, size); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		VEC_GENERIC_BUILTIN_OPERATION(*, sign, csign, bits, size); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_div(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		VEC_GENERIC_OPERATION(((union v##sign##int##bits##x##size##_impl_data *)&vec2)->impl[i] ? (((union v##sign##int##bits##x##size##_impl_data *)&vec1)->impl[i] / ((union v##sign##int##bits##x##size##_impl_data *)&vec2)->impl[i]) : 0, sign, csign, bits, size); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_avg(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+	    union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+	    union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		for (int i = 0; i < size; i++) \
+			vec1d->impl[i] = vec_##sign##avg(vec1d->impl[i], vec2d->impl[i]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		VEC_GENERIC_BUILTIN_OPERATION(&, sign, csign, bits, size); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		VEC_GENERIC_BUILTIN_OPERATION(|, sign, csign, bits, size); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		VEC_GENERIC_BUILTIN_OPERATION(^, sign, csign, bits, size); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmplt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		VEC_GENERIC_CMP(<, sign, csign, bits, size); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmple(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		return v##sign##int##bits##x##size##_not(v##sign##int##bits##x##size##_cmpgt(vec1, vec2)); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpeq(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		VEC_GENERIC_CMP(==, sign, csign, bits, size); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpge(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		return v##sign##int##bits##x##size##_not(v##sign##int##bits##x##size##_cmplt(vec1, vec2)); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpgt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		VEC_GENERIC_CMP(>, sign, csign, bits, size); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_lshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	{ \
+		VEC_GENERIC_OPERATION(vec_##sign##lshift(((union v##sign##int##bits##x##size##_impl_data *)&vec1)->impl[i], ((union v##sign##int##bits##x##size##_impl_data *)&vec2)->impl[i]), sign, csign, bits, size); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_rshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	{ \
+		VEC_GENERIC_OPERATION(vec_##sign##rshift(((union v##sign##int##bits##x##size##_impl_data *)&vec1)->impl[i], ((union v##sign##int##bits##x##size##_impl_data *)&vec2)->impl[i]), sign, csign, bits, size); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_lrshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	{ \
+		VEC_GENERIC_OPERATION(vec_lrshift((vec_uint##bits)(((union v##sign##int##bits##x##size##_impl_data *)&vec1)->impl[i]), ((union v##sign##int##bits##x##size##_impl_data *)&vec2)->impl[i]), sign, csign, bits, size); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_min(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		v##sign##int##bits##x##size cmplt = v##sign##int##bits##x##size##_cmplt(vec1, vec2); \
+	\
+		v##sign##int##bits##x##size a = v##sign##int##bits##x##size##_and(vec1, cmplt); \
+		v##sign##int##bits##x##size b = v##sign##int##bits##x##size##_and(vec2, v##sign##int##bits##x##size##_not(cmplt)); \
+	\
+		return v##sign##int##bits##x##size##_or(a, b); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_max(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		v##sign##int##bits##x##size cmplt = v##sign##int##bits##x##size##_cmpgt(vec1, vec2); \
+	\
+		v##sign##int##bits##x##size a = v##sign##int##bits##x##size##_and(vec1, cmplt); \
+		v##sign##int##bits##x##size b = v##sign##int##bits##x##size##_and(vec2, v##sign##int##bits##x##size##_not(cmplt)); \
+	\
+		return v##sign##int##bits##x##size##_or(a, b); \
+	} \
+	\
+	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_generic = { \
+		.splat = v##sign##int##bits##x##size##_generic_splat, \
+		.load_aligned = v##sign##int##bits##x##size##_generic_load_aligned, \
+		.load = v##sign##int##bits##x##size##_generic_load_aligned, \
+		.store_aligned = v##sign##int##bits##x##size##_generic_store_aligned, \
+		.store = v##sign##int##bits##x##size##_generic_store_aligned, \
+		.add = v##sign##int##bits##x##size##_generic_add, \
+		.sub = v##sign##int##bits##x##size##_generic_sub, \
+		.mul = v##sign##int##bits##x##size##_generic_mul, \
+		.div = v##sign##int##bits##x##size##_generic_div, \
+		.avg = v##sign##int##bits##x##size##_generic_avg, \
+		.band = v##sign##int##bits##x##size##_generic_and, \
+		.bor = v##sign##int##bits##x##size##_generic_or, \
+		.bxor = v##sign##int##bits##x##size##_generic_xor, \
+		.lshift = v##sign##int##bits##x##size##_generic_lshift, \
+		.rshift = v##sign##int##bits##x##size##_generic_rshift, \
+		.lrshift = v##sign##int##bits##x##size##_generic_lrshift, \
+		.cmplt = v##sign##int##bits##x##size##_generic_cmplt, \
+		.cmple = v##sign##int##bits##x##size##_generic_cmple, \
+		.cmpeq = v##sign##int##bits##x##size##_generic_cmpeq, \
+		.cmpge = v##sign##int##bits##x##size##_generic_cmpge, \
+		.cmpgt = v##sign##int##bits##x##size##_generic_cmpgt, \
+		.min = v##sign##int##bits##x##size##_generic_min, \
+		.max = v##sign##int##bits##x##size##_generic_max, \
+	};
+
+#define VEC_GENERIC_DEFINE_OPERATIONS(bits, size) \
+	VEC_GENERIC_DEFINE_OPERATIONS_SIGN(u, U, bits, size) \
+	VEC_GENERIC_DEFINE_OPERATIONS_SIGN( ,  , bits, size)
+
+VEC_GENERIC_DEFINE_OPERATIONS(8, 2)
+VEC_GENERIC_DEFINE_OPERATIONS(16, 2)
+VEC_GENERIC_DEFINE_OPERATIONS(32, 2)
+VEC_GENERIC_DEFINE_OPERATIONS(64, 2)
+
+#undef VEC_GENERIC_DEFINE_OPERATIONS
+#undef VEC_GENERIC_DEFINE_OPERATIONS_SIGN
+
+// -----------------------------------------------------------------
+// now we can just keep doubling the same implementation
+
+#define VEC_GENERIC_DEFINE_OPERATIONS_SIGN(sign, csign, bits, size, halfsize) \
+	union v##sign##int##bits##x##size##_impl_data { \
+		v##sign##int##bits##x##size vec; \
+		v##sign##int##bits##x##halfsize impl[2]; \
+	}; \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_splat(vec_##sign##int##bits x) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data vec; \
+		vec.impl[0] = v##sign##int##bits##x##halfsize##_splat(x); \
+		vec.impl[1] = v##sign##int##bits##x##halfsize##_splat(x); \
+		return vec.vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_load_aligned(const vec_##sign##int##bits in[size]) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data vec; \
+		vec.impl[0] = v##sign##int##bits##x##halfsize##_load_aligned(in); \
+		vec.impl[1] = v##sign##int##bits##x##halfsize##_load_aligned(in + halfsize); \
+		return vec.vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_load(const vec_##sign##int##bits in[size]) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data vec; \
+		vec.impl[0] = v##sign##int##bits##x##halfsize##_load(in); \
+		vec.impl[1] = v##sign##int##bits##x##halfsize##_load(in + halfsize); \
+		return vec.vec; \
+	} \
+	\
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_generic_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vecd = (union v##sign##int##bits##x##size##_impl_data *)&vec; \
+	\
+		v##sign##int##bits##x##halfsize##_store_aligned(vecd->impl[0], out); \
+		v##sign##int##bits##x##halfsize##_store_aligned(vecd->impl[1], out + halfsize); \
+	} \
+	\
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_generic_store(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vecd = (union v##sign##int##bits##x##size##_impl_data *)&vec; \
+	\
+		v##sign##int##bits##x##halfsize##_store(vecd->impl[0], out); \
+		v##sign##int##bits##x##halfsize##_store(vecd->impl[1], out + halfsize); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_add(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_add(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_sub(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_sub(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_mul(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_mul(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_div(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_div(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_div(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_avg(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_avg(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_avg(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_and(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_and(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_or(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_or(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_xor(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_xor(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_lshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union vuint##bits##x##size##_impl_data *vec2d = (union vuint##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_lshift(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_lshift(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_rshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union vuint##bits##x##size##_impl_data *vec2d = (union vuint##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_rshift(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_rshift(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_lrshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union vuint##bits##x##size##_impl_data *vec2d = (union vuint##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_lrshift(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_lrshift(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmplt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_cmplt(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_cmplt(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmple(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_cmple(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_cmple(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpeq(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_cmpeq(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_cmpeq(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpge(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_cmpge(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_cmpge(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpgt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_cmpgt(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_cmpgt(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_min(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_min(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_min(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_max(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_max(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_max(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_generic = { \
+		.splat = v##sign##int##bits##x##size##_generic_splat, \
+		.load_aligned = v##sign##int##bits##x##size##_generic_load_aligned, \
+		.load = v##sign##int##bits##x##size##_generic_load, \
+		.store_aligned = v##sign##int##bits##x##size##_generic_store_aligned, \
+		.store = v##sign##int##bits##x##size##_generic_store, \
+		.add = v##sign##int##bits##x##size##_generic_add, \
+		.sub = v##sign##int##bits##x##size##_generic_sub, \
+		.mul = v##sign##int##bits##x##size##_generic_mul, \
+		.div = v##sign##int##bits##x##size##_generic_div, \
+		.avg = v##sign##int##bits##x##size##_generic_avg, \
+		.band = v##sign##int##bits##x##size##_generic_and, \
+		.bor = v##sign##int##bits##x##size##_generic_or, \
+		.bxor = v##sign##int##bits##x##size##_generic_xor, \
+		.lshift = v##sign##int##bits##x##size##_generic_lshift, \
+		.rshift = v##sign##int##bits##x##size##_generic_rshift, \
+		.lrshift = v##sign##int##bits##x##size##_generic_lrshift, \
+		.cmplt = v##sign##int##bits##x##size##_generic_cmplt, \
+		.cmple = v##sign##int##bits##x##size##_generic_cmple, \
+		.cmpeq = v##sign##int##bits##x##size##_generic_cmpeq, \
+		.cmpge = v##sign##int##bits##x##size##_generic_cmpge, \
+		.cmpgt = v##sign##int##bits##x##size##_generic_cmpgt, \
+		.min = v##sign##int##bits##x##size##_generic_min, \
+		.max = v##sign##int##bits##x##size##_generic_max, \
+	};
+
+#define VEC_GENERIC_DEFINE_OPERATIONS(bits, size, halfsize) \
+	VEC_GENERIC_DEFINE_OPERATIONS_SIGN(u, U, bits, size, halfsize) \
+	VEC_GENERIC_DEFINE_OPERATIONS_SIGN( ,  , bits, size, halfsize)
+
+// 32-bit
+VEC_GENERIC_DEFINE_OPERATIONS(8, 4, 2)
+
+// 64-bit
+VEC_GENERIC_DEFINE_OPERATIONS(8, 8, 4)
+VEC_GENERIC_DEFINE_OPERATIONS(16, 4, 2)
+
+// 128-bit
+VEC_GENERIC_DEFINE_OPERATIONS(8, 16, 8)
+VEC_GENERIC_DEFINE_OPERATIONS(16, 8, 4)
+VEC_GENERIC_DEFINE_OPERATIONS(32, 4, 2)
+
+// 256-bit
+VEC_GENERIC_DEFINE_OPERATIONS(8, 32, 16)
+VEC_GENERIC_DEFINE_OPERATIONS(16, 16, 8)
+VEC_GENERIC_DEFINE_OPERATIONS(32, 8, 4)
+VEC_GENERIC_DEFINE_OPERATIONS(64, 4, 2)
+
+// 512-bit
+VEC_GENERIC_DEFINE_OPERATIONS(8, 64, 32)
+VEC_GENERIC_DEFINE_OPERATIONS(16, 32, 16)
+VEC_GENERIC_DEFINE_OPERATIONS(32, 16, 8)
+VEC_GENERIC_DEFINE_OPERATIONS(64, 8, 4)
+
+#undef VEC_GENERIC_DEFINE_OPERATIONS
+#undef VEC_GENERIC_DEFINE_OPERATIONS_SIGN
--- a/src/impl/generic.c	Mon Nov 25 00:33:02 2024 -0500
+++ b/src/impl/generic.c	Thu Apr 24 00:54:02 2025 -0400
@@ -1,3 +1,27 @@
+/**
+ * vec - a tiny SIMD vector library in C99
+ * 
+ * Copyright (c) 2024 Paper
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+**/
+
 #include "vec/impl/generic.h"
 
 #include <string.h>
@@ -26,7 +50,7 @@
 		vec_##sign##int##bits impl[size]; \
 	}; \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_splat(vec_##sign##int##bits x) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_splat(vec_##sign##int##bits x) \
 	{ \
 		v##sign##int##bits##x##size vec; \
 		for (int i = 0; i < size; i++) \
@@ -34,39 +58,39 @@
 		return vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_load_aligned(const vec_##sign##int##bits in[size]) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_load_aligned(const vec_##sign##int##bits in[size]) \
 	{ \
 		v##sign##int##bits##x##size vec; \
 		memcpy(&vec, in, sizeof(vec_##sign##int##bits) * size); \
 		return vec; \
 	} \
 	\
-	void v##sign##int##bits##x##size##_generic_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_generic_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
 	{ \
 		memcpy(out, &vec, sizeof(vec_##sign##int##bits) * size); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		VEC_GENERIC_BUILTIN_OPERATION(+, sign, csign, bits, size); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		VEC_GENERIC_BUILTIN_OPERATION(-, sign, csign, bits, size); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		VEC_GENERIC_BUILTIN_OPERATION(*, sign, csign, bits, size); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_div(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_div(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		VEC_GENERIC_OPERATION(((union v##sign##int##bits##x##size##_impl_data *)&vec2)->impl[i] ? (((union v##sign##int##bits##x##size##_impl_data *)&vec1)->impl[i] / ((union v##sign##int##bits##x##size##_impl_data *)&vec2)->impl[i]) : 0, sign, csign, bits, size); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_avg(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_avg(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 	    union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 	    union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -77,89 +101,105 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		VEC_GENERIC_BUILTIN_OPERATION(&, sign, csign, bits, size); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		VEC_GENERIC_BUILTIN_OPERATION(|, sign, csign, bits, size); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		VEC_GENERIC_BUILTIN_OPERATION(^, sign, csign, bits, size); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_not(v##sign##int##bits##x##size vec) \
-	{ \
-		return v##sign##int##bits##x##size##_generic_xor(vec, v##sign##int##bits##x##size##_generic_splat((vec_##sign##int##bits)VEC_UINT##bits##_MAX)); \
-	} \
-	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmplt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmplt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		VEC_GENERIC_CMP(<, sign, csign, bits, size); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmple(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmple(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		VEC_GENERIC_CMP(<=, sign, csign, bits, size); \
+		return v##sign##int##bits##x##size##_not(v##sign##int##bits##x##size##_cmpgt(vec1, vec2)); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpeq(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpeq(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		VEC_GENERIC_CMP(==, sign, csign, bits, size); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpge(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpge(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
-		VEC_GENERIC_CMP(>=, sign, csign, bits, size); \
+		return v##sign##int##bits##x##size##_not(v##sign##int##bits##x##size##_cmplt(vec1, vec2)); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpgt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpgt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		VEC_GENERIC_CMP(>, sign, csign, bits, size); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_lshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_lshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
 	{ \
 		VEC_GENERIC_OPERATION(vec_##sign##lshift(((union v##sign##int##bits##x##size##_impl_data *)&vec1)->impl[i], ((union v##sign##int##bits##x##size##_impl_data *)&vec2)->impl[i]), sign, csign, bits, size); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_rshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_rshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
 	{ \
 		VEC_GENERIC_OPERATION(vec_##sign##rshift(((union v##sign##int##bits##x##size##_impl_data *)&vec1)->impl[i], ((union v##sign##int##bits##x##size##_impl_data *)&vec2)->impl[i]), sign, csign, bits, size); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_lrshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_lrshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
 	{ \
 		VEC_GENERIC_OPERATION(vec_lrshift((vec_uint##bits)(((union v##sign##int##bits##x##size##_impl_data *)&vec1)->impl[i]), ((union v##sign##int##bits##x##size##_impl_data *)&vec2)->impl[i]), sign, csign, bits, size); \
 	} \
 	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_min(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		v##sign##int##bits##x##size cmplt = v##sign##int##bits##x##size##_cmplt(vec1, vec2); \
+	\
+		v##sign##int##bits##x##size a = v##sign##int##bits##x##size##_and(vec1, cmplt); \
+		v##sign##int##bits##x##size b = v##sign##int##bits##x##size##_and(vec2, v##sign##int##bits##x##size##_not(cmplt)); \
+	\
+		return v##sign##int##bits##x##size##_or(a, b); \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_max(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		v##sign##int##bits##x##size cmplt = v##sign##int##bits##x##size##_cmpgt(vec1, vec2); \
+	\
+		v##sign##int##bits##x##size a = v##sign##int##bits##x##size##_and(vec1, cmplt); \
+		v##sign##int##bits##x##size b = v##sign##int##bits##x##size##_and(vec2, v##sign##int##bits##x##size##_not(cmplt)); \
+	\
+		return v##sign##int##bits##x##size##_or(a, b); \
+	} \
+	\
 	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_generic = { \
-		v##sign##int##bits##x##size##_generic_splat, \
-		v##sign##int##bits##x##size##_generic_load_aligned, \
-		v##sign##int##bits##x##size##_generic_load_aligned, \
-		v##sign##int##bits##x##size##_generic_store_aligned, \
-		v##sign##int##bits##x##size##_generic_store_aligned, \
-		v##sign##int##bits##x##size##_generic_add, \
-		v##sign##int##bits##x##size##_generic_sub, \
-		v##sign##int##bits##x##size##_generic_mul, \
-		v##sign##int##bits##x##size##_generic_div, \
-		v##sign##int##bits##x##size##_generic_avg, \
-		v##sign##int##bits##x##size##_generic_and, \
-		v##sign##int##bits##x##size##_generic_or, \
-		v##sign##int##bits##x##size##_generic_xor, \
-		v##sign##int##bits##x##size##_generic_not, \
-		v##sign##int##bits##x##size##_generic_lshift, \
-		v##sign##int##bits##x##size##_generic_rshift, \
-		v##sign##int##bits##x##size##_generic_lrshift, \
-		v##sign##int##bits##x##size##_generic_cmplt, \
-		v##sign##int##bits##x##size##_generic_cmple, \
-		v##sign##int##bits##x##size##_generic_cmpeq, \
-		v##sign##int##bits##x##size##_generic_cmpge, \
-		v##sign##int##bits##x##size##_generic_cmpgt, \
+		.splat = v##sign##int##bits##x##size##_generic_splat, \
+		.load_aligned = v##sign##int##bits##x##size##_generic_load_aligned, \
+		.load = v##sign##int##bits##x##size##_generic_load_aligned, \
+		.store_aligned = v##sign##int##bits##x##size##_generic_store_aligned, \
+		.store = v##sign##int##bits##x##size##_generic_store_aligned, \
+		.add = v##sign##int##bits##x##size##_generic_add, \
+		.sub = v##sign##int##bits##x##size##_generic_sub, \
+		.mul = v##sign##int##bits##x##size##_generic_mul, \
+		.div = v##sign##int##bits##x##size##_generic_div, \
+		.avg = v##sign##int##bits##x##size##_generic_avg, \
+		.band = v##sign##int##bits##x##size##_generic_and, \
+		.bor = v##sign##int##bits##x##size##_generic_or, \
+		.bxor = v##sign##int##bits##x##size##_generic_xor, \
+		.lshift = v##sign##int##bits##x##size##_generic_lshift, \
+		.rshift = v##sign##int##bits##x##size##_generic_rshift, \
+		.lrshift = v##sign##int##bits##x##size##_generic_lrshift, \
+		.cmplt = v##sign##int##bits##x##size##_generic_cmplt, \
+		.cmple = v##sign##int##bits##x##size##_generic_cmple, \
+		.cmpeq = v##sign##int##bits##x##size##_generic_cmpeq, \
+		.cmpge = v##sign##int##bits##x##size##_generic_cmpge, \
+		.cmpgt = v##sign##int##bits##x##size##_generic_cmpgt, \
+		.min = v##sign##int##bits##x##size##_generic_min, \
+		.max = v##sign##int##bits##x##size##_generic_max, \
 	};
 
 #define VEC_GENERIC_DEFINE_OPERATIONS(bits, size) \
@@ -183,7 +223,7 @@
 		v##sign##int##bits##x##halfsize impl[2]; \
 	}; \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_splat(vec_##sign##int##bits x) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_splat(vec_##sign##int##bits x) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data vec; \
 		vec.impl[0] = v##sign##int##bits##x##halfsize##_splat(x); \
@@ -191,7 +231,7 @@
 		return vec.vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_load_aligned(const vec_##sign##int##bits in[size]) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_load_aligned(const vec_##sign##int##bits in[size]) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data vec; \
 		vec.impl[0] = v##sign##int##bits##x##halfsize##_load_aligned(in); \
@@ -199,7 +239,7 @@
 		return vec.vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_load(const vec_##sign##int##bits in[size]) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_load(const vec_##sign##int##bits in[size]) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data vec; \
 		vec.impl[0] = v##sign##int##bits##x##halfsize##_load(in); \
@@ -207,7 +247,7 @@
 		return vec.vec; \
 	} \
 	\
-	void v##sign##int##bits##x##size##_generic_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_generic_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vecd = (union v##sign##int##bits##x##size##_impl_data *)&vec; \
 	\
@@ -215,7 +255,7 @@
 		v##sign##int##bits##x##halfsize##_store_aligned(vecd->impl[1], out + halfsize); \
 	} \
 	\
-	void v##sign##int##bits##x##size##_generic_store(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_generic_store(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vecd = (union v##sign##int##bits##x##size##_impl_data *)&vec; \
 	\
@@ -223,7 +263,7 @@
 		v##sign##int##bits##x##halfsize##_store(vecd->impl[1], out + halfsize); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -234,7 +274,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -245,7 +285,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -256,7 +296,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_div(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_div(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -267,7 +307,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_avg(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_avg(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -278,7 +318,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -289,7 +329,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -300,7 +340,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -311,17 +351,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_not(v##sign##int##bits##x##size vec1) \
-	{ \
-		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
-	\
-		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_not(vec1d->impl[0]); \
-		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_not(vec1d->impl[1]); \
-	\
-		return vec1d->vec; \
-	} \
-	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_lshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_lshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union vuint##bits##x##size##_impl_data *vec2d = (union vuint##bits##x##size##_impl_data *)&vec2; \
@@ -332,7 +362,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_rshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_rshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union vuint##bits##x##size##_impl_data *vec2d = (union vuint##bits##x##size##_impl_data *)&vec2; \
@@ -343,7 +373,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_lrshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_lrshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union vuint##bits##x##size##_impl_data *vec2d = (union vuint##bits##x##size##_impl_data *)&vec2; \
@@ -354,7 +384,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmplt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmplt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -365,7 +395,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmple(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmple(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -376,7 +406,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpeq(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpeq(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -387,7 +417,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpge(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpge(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -398,7 +428,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpgt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_cmpgt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -409,29 +439,52 @@
 		return vec1d->vec; \
 	} \
 	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_min(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_min(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_min(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_generic_max(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->impl[0] = v##sign##int##bits##x##halfsize##_max(vec1d->impl[0], vec2d->impl[0]); \
+		vec1d->impl[1] = v##sign##int##bits##x##halfsize##_max(vec1d->impl[1], vec2d->impl[1]); \
+	\
+		return vec1d->vec; \
+	} \
+	\
 	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_generic = { \
-		v##sign##int##bits##x##size##_generic_splat, \
-		v##sign##int##bits##x##size##_generic_load_aligned, \
-		v##sign##int##bits##x##size##_generic_load, \
-		v##sign##int##bits##x##size##_generic_store_aligned, \
-		v##sign##int##bits##x##size##_generic_store, \
-		v##sign##int##bits##x##size##_generic_add, \
-		v##sign##int##bits##x##size##_generic_sub, \
-		v##sign##int##bits##x##size##_generic_mul, \
-		v##sign##int##bits##x##size##_generic_div, \
-		v##sign##int##bits##x##size##_generic_avg, \
-		v##sign##int##bits##x##size##_generic_and, \
-		v##sign##int##bits##x##size##_generic_or, \
-		v##sign##int##bits##x##size##_generic_xor, \
-		v##sign##int##bits##x##size##_generic_not, \
-		v##sign##int##bits##x##size##_generic_lshift, \
-		v##sign##int##bits##x##size##_generic_rshift, \
-		v##sign##int##bits##x##size##_generic_lrshift, \
-		v##sign##int##bits##x##size##_generic_cmplt, \
-		v##sign##int##bits##x##size##_generic_cmple, \
-		v##sign##int##bits##x##size##_generic_cmpeq, \
-		v##sign##int##bits##x##size##_generic_cmpge, \
-		v##sign##int##bits##x##size##_generic_cmpgt, \
+		.splat = v##sign##int##bits##x##size##_generic_splat, \
+		.load_aligned = v##sign##int##bits##x##size##_generic_load_aligned, \
+		.load = v##sign##int##bits##x##size##_generic_load, \
+		.store_aligned = v##sign##int##bits##x##size##_generic_store_aligned, \
+		.store = v##sign##int##bits##x##size##_generic_store, \
+		.add = v##sign##int##bits##x##size##_generic_add, \
+		.sub = v##sign##int##bits##x##size##_generic_sub, \
+		.mul = v##sign##int##bits##x##size##_generic_mul, \
+		.div = v##sign##int##bits##x##size##_generic_div, \
+		.avg = v##sign##int##bits##x##size##_generic_avg, \
+		.band = v##sign##int##bits##x##size##_generic_and, \
+		.bor = v##sign##int##bits##x##size##_generic_or, \
+		.bxor = v##sign##int##bits##x##size##_generic_xor, \
+		.lshift = v##sign##int##bits##x##size##_generic_lshift, \
+		.rshift = v##sign##int##bits##x##size##_generic_rshift, \
+		.lrshift = v##sign##int##bits##x##size##_generic_lrshift, \
+		.cmplt = v##sign##int##bits##x##size##_generic_cmplt, \
+		.cmple = v##sign##int##bits##x##size##_generic_cmple, \
+		.cmpeq = v##sign##int##bits##x##size##_generic_cmpeq, \
+		.cmpge = v##sign##int##bits##x##size##_generic_cmpge, \
+		.cmpgt = v##sign##int##bits##x##size##_generic_cmpgt, \
+		.min = v##sign##int##bits##x##size##_generic_min, \
+		.max = v##sign##int##bits##x##size##_generic_max, \
 	};
 
 #define VEC_GENERIC_DEFINE_OPERATIONS(bits, size, halfsize) \
--- a/src/impl/ppc/altivec.c	Mon Nov 25 00:33:02 2024 -0500
+++ b/src/impl/ppc/altivec.c	Thu Apr 24 00:54:02 2025 -0400
@@ -48,7 +48,7 @@
 /* GCC 4.2.1 on Mac OS X doesn't have these for some reason */
 #ifdef vec_mul
 # define VEC_ALTIVEC_DEFINE_MUL(sign, csign, bits, size) \
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -57,34 +57,34 @@
 		return vec1d->vec; \
 	}
 # define VEC_ALTIVEC_STRUCT_MUL(sign, csign, bits, size) \
-	v##sign##int##bits##x##size##_altivec_mul
+	.mul = v##sign##int##bits##x##size##_altivec_mul,
 #else
 # define VEC_ALTIVEC_DEFINE_MUL(sign, csign, bits, size)
 # define VEC_ALTIVEC_STRUCT_MUL(sign, csign, bits, size) \
-    v##sign##int##bits##x##size##_generic_mul
+    /* nothing */
 #endif
 
 #ifdef vec_splats
 # define VEC_ALTIVEC_DEFINE_SPLAT(sign, csign, bits, size) \
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_splat(vec_##sign##int##bits x) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_splat(vec_##sign##int##bits x) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data vec; \
 		vec.altivec = vec_splats(x); \
 		return vec.vec; \
 	}
 # define VEC_ALTIVEC_STRUCT_SPLAT(sign, csign, bits, size) \
-	v##sign##int##bits##x##size##_altivec_splat
+	.splat = v##sign##int##bits##x##size##_altivec_splat,
 #else
 # define VEC_ALTIVEC_DEFINE_SPLAT(sign, csign, bits, size)
 # define VEC_ALTIVEC_STRUCT_SPLAT(sign, csign, bits, size) \
-    v##sign##int##bits##x##size##_generic_splat
+    /* nothing */
 #endif
 
 #define VEC_ALTIVEC_uRSHIFT vec_sr
 #define VEC_ALTIVEC_RSHIFT vec_sra
 
 #define VEC_ALTIVEC_DEFINE_uLRSHIFT(sign, csign, bits, size) \
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_lrshift(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_lrshift(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union vuint##bits##x##size##_impl_data *vec2d = (union vuint##bits##x##size##_impl_data *)&vec2; \
@@ -93,11 +93,10 @@
 		return vec1d->vec; \
 	}
 #define VEC_ALTIVEC_STRUCT_uLRSHIFT(sign, csign, bits, size) \
-	v##sign##int##bits##x##size##_altivec_lrshift
+	.lrshift = v##sign##int##bits##x##size##_altivec_lrshift,
 
 #define VEC_ALTIVEC_DEFINE_LRSHIFT(sign, csign, bits, size)
-#define VEC_ALTIVEC_STRUCT_LRSHIFT(sign, csign, bits, size) \
-    v##sign##int##bits##x##size##_generic_lrshift
+#define VEC_ALTIVEC_STRUCT_LRSHIFT(sign, csign, bits, size) /* nothing */
 
 #define VEC_ALTIVEC_CAST_BOOL_8 (vector signed char)
 #define VEC_ALTIVEC_CAST_BOOL_U8 (vector unsigned char)
@@ -109,26 +108,26 @@
 /* Since altivec conveniently made their API super user friendly, we can just use
  * one giant macro to define literally everything */
 #define VEC_DEFINE_OPERATIONS_SIGN(sign, csign, bits, size) \
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_load_aligned(const vec_##sign##int##bits in[size]) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_load_aligned(const vec_##sign##int##bits in[size]) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data vec; \
 		vec.altivec = vec_ld(0, in); \
 		return vec.vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_load(const vec_##sign##int##bits in[size]) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_load(const vec_##sign##int##bits in[size]) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data vec; \
 		vec.altivec = vec_perm(vec_ld(0, in), vec_ld(15, in), vec_lvsl(0, in)); \
 		return vec.vec; \
 	} \
 	\
-	static void v##sign##int##bits##x##size##_altivec_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_altivec_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
 	{ \
 		vec_st(((union v##sign##int##bits##x##size##_impl_data *)&vec)->altivec, 0, out); \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -137,7 +136,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -148,7 +147,25 @@
 	\
 	VEC_ALTIVEC_DEFINE_MUL(sign, csign, bits, size) \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_lshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_min(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union vuint##bits##x##size##_impl_data *vec2d = (union vuint##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->altivec = vec_min(vec1d->altivec, vec2d->altivec); \
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_max(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union vuint##bits##x##size##_impl_data *vec2d = (union vuint##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->altivec = vec_max(vec1d->altivec, vec2d->altivec); \
+		return vec1d->vec; \
+	} \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_lshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union vuint##bits##x##size##_impl_data *vec2d = (union vuint##bits##x##size##_impl_data *)&vec2; \
@@ -157,7 +174,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_rshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_rshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union vuint##bits##x##size##_impl_data *vec2d = (union vuint##bits##x##size##_impl_data *)&vec2; \
@@ -168,7 +185,7 @@
 	\
 	VEC_ALTIVEC_DEFINE_##sign##LRSHIFT(sign, csign, bits, size) \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_avg(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_avg(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -177,7 +194,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -186,7 +203,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -195,7 +212,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -206,7 +223,7 @@
 	\
 	VEC_ALTIVEC_DEFINE_SPLAT(sign, csign, bits, size) \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_cmplt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_cmplt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -215,7 +232,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_cmple(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_cmple(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -224,7 +241,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_cmpeq(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_cmpeq(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -233,7 +250,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_cmpge(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_cmpge(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -242,7 +259,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_cmpgt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_altivec_cmpgt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -252,28 +269,25 @@
 	} \
 	\
 	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_altivec = { \
-		VEC_ALTIVEC_STRUCT_SPLAT(sign, csign, bits, size), \
-		v##sign##int##bits##x##size##_altivec_load_aligned, \
-		v##sign##int##bits##x##size##_altivec_load, \
-		v##sign##int##bits##x##size##_altivec_store_aligned, \
-		v##sign##int##bits##x##size##_generic_store, \
-		v##sign##int##bits##x##size##_altivec_add, \
-		v##sign##int##bits##x##size##_altivec_sub, \
-		VEC_ALTIVEC_STRUCT_MUL(sign, csign, bits, size), \
-		v##sign##int##bits##x##size##_generic_div, \
-		v##sign##int##bits##x##size##_altivec_avg, \
-		v##sign##int##bits##x##size##_altivec_and, \
-		v##sign##int##bits##x##size##_altivec_or, \
-		v##sign##int##bits##x##size##_altivec_xor, \
-		v##sign##int##bits##x##size##_generic_not, \
-		v##sign##int##bits##x##size##_altivec_lshift, \
-		v##sign##int##bits##x##size##_altivec_rshift, \
-		VEC_ALTIVEC_STRUCT_##sign##LRSHIFT(sign, csign, bits, size), \
-		v##sign##int##bits##x##size##_altivec_cmplt, \
-		v##sign##int##bits##x##size##_altivec_cmple, \
-		v##sign##int##bits##x##size##_altivec_cmpeq, \
-		v##sign##int##bits##x##size##_altivec_cmpge, \
-		v##sign##int##bits##x##size##_altivec_cmpgt, \
+		VEC_ALTIVEC_STRUCT_SPLAT(sign, csign, bits, size) \
+		.load_aligned = v##sign##int##bits##x##size##_altivec_load_aligned, \
+		.load = v##sign##int##bits##x##size##_altivec_load, \
+		.store_aligned = v##sign##int##bits##x##size##_altivec_store_aligned, \
+		.add = v##sign##int##bits##x##size##_altivec_add, \
+		.sub = v##sign##int##bits##x##size##_altivec_sub, \
+		VEC_ALTIVEC_STRUCT_MUL(sign, csign, bits, size) \
+		.avg = v##sign##int##bits##x##size##_altivec_avg, \
+		.band = v##sign##int##bits##x##size##_altivec_and, \
+		.bor = v##sign##int##bits##x##size##_altivec_or, \
+		.bxor = v##sign##int##bits##x##size##_altivec_xor, \
+		.lshift = v##sign##int##bits##x##size##_altivec_lshift, \
+		.rshift = v##sign##int##bits##x##size##_altivec_rshift, \
+		VEC_ALTIVEC_STRUCT_##sign##LRSHIFT(sign, csign, bits, size) \
+		.cmplt = v##sign##int##bits##x##size##_altivec_cmplt, \
+		.cmple = v##sign##int##bits##x##size##_altivec_cmple, \
+		.cmpeq = v##sign##int##bits##x##size##_altivec_cmpeq, \
+		.cmpge = v##sign##int##bits##x##size##_altivec_cmpge, \
+		.cmpgt = v##sign##int##bits##x##size##_altivec_cmpgt, \
 	};
 
 #define VEC_DEFINE_OPERATIONS(bits, size) \
--- a/src/impl/x86/avx2.c	Mon Nov 25 00:33:02 2024 -0500
+++ b/src/impl/x86/avx2.c	Thu Apr 24 00:54:02 2025 -0400
@@ -23,70 +23,88 @@
 **/
 
 #include "vec/impl/x86/avx2.h"
-#include "vec/impl/generic.h"
 
 #include <immintrin.h>
 
-// this does NOT handle sign bits properly, use with caution
-#define VEC_AVX2_OPERATION_8x32_16x16(op, sign) \
-	do { \
-		union v##sign##int8x32_impl_data *vec1d = (union v##sign##int8x32_impl_data *)&vec1; \
-		union v##sign##int8x32_impl_data *vec2d = (union v##sign##int8x32_impl_data *)&vec2; \
+/* ------------------------------------------------------------------------ */
+/* min/max */
+
+#define VEC_AVX2_MINMAX_TEMPLATE(SIGN, BITS, SIZE, INTLSIGN, OP) \
+	VEC_FUNC_IMPL v##SIGN##int##BITS##x##SIZE v##SIGN##int##BITS##x##SIZE##_avx2_##OP(v##SIGN##int##BITS##x##SIZE vec1, v##SIGN##int##BITS##x##SIZE vec2) \
+	{ \
+		union v##SIGN##int##BITS##x##SIZE##_impl_data *vec1d = (union v##SIGN##int##BITS##x##SIZE##_impl_data *)&vec1; \
+		union v##SIGN##int##BITS##x##SIZE##_impl_data *vec2d = (union v##SIGN##int##BITS##x##SIZE##_impl_data *)&vec2; \
 	\
-		/* unpack and multiply */ \
-		__m256i dst_even = _mm256_##op##_epi16(vec1d->avx2, vec2d->avx2); \
-		__m256i dst_odd = _mm256_##op##_epi16(_mm256_srli_epi16(vec1d->avx2, 8), _mm256_srli_epi16(vec2d->avx2, 8)); \
+		vec1d->avx2 = _mm256_##OP##_ep##INTLSIGN##BITS(vec1d->avx2, vec2d->avx2); \
 	\
-		/* repack */ \
-		vec1d->avx2 = _mm256_or_si256( \
-			_mm256_slli_epi16(dst_odd, 8), \
-			_mm256_srli_epi16(_mm256_slli_epi16(dst_even, 8), 8) \
-		); \
 		return vec1d->vec; \
-	} while (0)
+	}
+
+#define VEC_AVX2_MINMAX_8x32(OP)   VEC_AVX2_MINMAX_TEMPLATE( , 8, 32, i, OP)
+#define VEC_AVX2_MINMAX_u8x32(OP)  VEC_AVX2_MINMAX_TEMPLATE(u, 8, 32, u, OP)
+#define VEC_AVX2_MINMAX_16x16(OP)  VEC_AVX2_MINMAX_TEMPLATE( , 16, 16, i, OP)
+#define VEC_AVX2_MINMAX_u16x16(OP) VEC_AVX2_MINMAX_TEMPLATE(u, 16, 16, u, OP)
+#define VEC_AVX2_MINMAX_32x8(OP)   VEC_AVX2_MINMAX_TEMPLATE( , 32, 8, i, OP)
+#define VEC_AVX2_MINMAX_u32x8(OP)  VEC_AVX2_MINMAX_TEMPLATE(u, 32, 8, u, OP)
+#define VEC_AVX2_MINMAX_64x4(OP)   /* nothing */
+#define VEC_AVX2_MINMAX_u64x4(OP)  /* nothing */
+
+#define VEC_AVX2_STRUCT_MINMAX_8x32(OP, SIGN)  v##SIGN##int8x32_avx2_##OP
+#define VEC_AVX2_STRUCT_MINMAX_16x16(OP, SIGN) v##SIGN##int16x16_avx2_##OP
+#define VEC_AVX2_STRUCT_MINMAX_32x8(OP, SIGN)  v##SIGN##int32x8_avx2_##OP
+#define VEC_AVX2_STRUCT_MINMAX_64x4(OP, SIGN)  NULL
+
+/* reused this for avg */
 
-#define VEC_AVX2_OPERATION_16x16(op, sign) \
-	do { \
-		union v##sign##int16x16_impl_data *vec1d = (union v##sign##int16x16_impl_data *)&vec1; \
-		union v##sign##int16x16_impl_data *vec2d = (union v##sign##int16x16_impl_data *)&vec2; \
-	\
-		/* unpack and multiply */ \
-		__m256i dst_even = _mm256_##op##_epi32(vec1d->avx2, vec2d->avx2); \
-		__m256i dst_odd = _mm256_##op##_epi32(_mm256_srli_epi32(vec1d->avx2, 16), _mm256_srli_epi32(vec2d->avx2, 16)); \
-	\
-		/* repack */ \
-		vec1d->avx2 = _mm256_or_si256( \
-			_mm256_slli_epi32(dst_odd, 16), \
-			_mm256_srli_epi32(_mm256_slli_epi16(dst_even, 16), 16) \
-		); \
-		return vec1d->vec; \
-	} while (0)
+#define VEC_AVX2_AVG_8x32  /* nothing */
+#define VEC_AVX2_AVG_16x16 /* nothing */
+#define VEC_AVX2_AVG_32x8  /* nothing */
+#define VEC_AVX2_AVG_64x4  /* nothing */
+
+#define VEC_AVX2_AVG_u8x32  VEC_AVX2_MINMAX_TEMPLATE(u, 8,  32, u, avg)
+#define VEC_AVX2_AVG_u16x16 VEC_AVX2_MINMAX_TEMPLATE(u, 16, 16, u, avg)
+#define VEC_AVX2_AVG_u32x8  /* nothing */
+#define VEC_AVX2_AVG_u64x4  /* nothing */
+
+#define VEC_AVX2_STRUCT_AVG_8x32  NULL
+#define VEC_AVX2_STRUCT_AVG_16x16 NULL
+#define VEC_AVX2_STRUCT_AVG_32x8  NULL
+#define VEC_AVX2_STRUCT_AVG_64x4  NULL
+
+#define VEC_AVX2_STRUCT_AVG_u8x32  vuint8x32_avx2_avg
+#define VEC_AVX2_STRUCT_AVG_u16x16 vuint16x16_avx2_avg
+#define VEC_AVX2_STRUCT_AVG_u32x8  NULL
+#define VEC_AVX2_STRUCT_AVG_u64x4  NULL
+
+/* ------------------------------------------------------------------------ */
 
 // multiplication
 
-#define VEC_AVX2_MUL_8x32(sign) \
-	VEC_AVX2_OPERATION_8x32_16x16(mullo, sign)
+#define VEC_AVX2_MUL_8x32(sign) /* nothing */
 
 #define VEC_AVX2_MUL_16x16(sign) \
-	do { \
+	VEC_FUNC_IMPL v##sign##int16x16 v##sign##int16x16_avx2_mul(v##sign##int16x16 vec1, v##sign##int16x16 vec2) \
+	{ \
 		union v##sign##int16x16_impl_data *vec1d = (union v##sign##int16x16_impl_data *)&vec1; \
 		union v##sign##int16x16_impl_data *vec2d = (union v##sign##int16x16_impl_data *)&vec2; \
 	\
 		vec1d->avx2 = _mm256_mullo_epi16(vec1d->avx2, vec2d->avx2); \
 		return vec1d->vec; \
-	} while (0)
+	}
 
 #define VEC_AVX2_MUL_32x8(sign) \
-	do { \
+	VEC_FUNC_IMPL v##sign##int32x8 v##sign##int32x8_avx2_mul(v##sign##int32x8 vec1, v##sign##int32x8 vec2) \
+	{ \
 		union v##sign##int32x8_impl_data *vec1d = (union v##sign##int32x8_impl_data *)&vec1; \
 		union v##sign##int32x8_impl_data *vec2d = (union v##sign##int32x8_impl_data *)&vec2; \
 	\
 		vec1d->avx2 = _mm256_mullo_epi32(vec1d->avx2, vec2d->avx2); \
 		return vec1d->vec; \
-	} while (0)
+	}
 
 #define VEC_AVX2_MUL_64x4(sign) \
-	do { \
+	VEC_FUNC_IMPL v##sign##int64x4 v##sign##int64x4_avx2_mul(v##sign##int64x4 vec1, v##sign##int64x4 vec2) \
+	{ \
 		union v##sign##int64x4_impl_data *vec1d = (union v##sign##int64x4_impl_data *)&vec1; \
 		union v##sign##int64x4_impl_data *vec2d = (union v##sign##int64x4_impl_data *)&vec2; \
 	\
@@ -100,7 +118,12 @@
 	\
 		vec1d->avx2 = _mm256_add_epi64(hi, ac); \
 		return vec1d->vec; \
-	} while (0)
+	}
+
+#define VEC_AVX2_STRUCT_MUL_8x32(SIGN) NULL
+#define VEC_AVX2_STRUCT_MUL_16x16(SIGN) v##SIGN##int16x16_avx2_mul
+#define VEC_AVX2_STRUCT_MUL_32x8(SIGN) v##SIGN##int32x8_avx2_mul
+#define VEC_AVX2_STRUCT_MUL_64x4(SIGN) v##SIGN##int64x4_avx2_mul
 
 // operations
 
@@ -113,31 +136,31 @@
 	VEC_STATIC_ASSERT(VEC_ALIGNOF(__m256i) <= VEC_ALIGNOF(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " alignment needs to be expanded to fit intrinsic type size"); \
 	VEC_STATIC_ASSERT(sizeof(__m256i) <= sizeof(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " needs to be expanded to fit intrinsic type size"); \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_load_aligned(const vec_##sign##int##bits in[size]) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_load_aligned(const vec_##sign##int##bits in[size]) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data vec; \
 		vec.avx2 = _mm256_load_si256((const __m256i *)in); \
 		return vec.vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_load(const vec_##sign##int##bits in[size]) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_load(const vec_##sign##int##bits in[size]) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data vec; \
 		vec.avx2 = _mm256_loadu_si256((const __m256i *)in); \
 		return vec.vec; \
 	} \
 	\
-	static void v##sign##int##bits##x##size##_avx2_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_avx2_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
 	{ \
 		_mm256_store_si256((__m256i *)out, ((union v##sign##int##bits##x##size##_impl_data*)&vec)->avx2); \
 	} \
 	\
-	static void v##sign##int##bits##x##size##_avx2_store(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_avx2_store(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
 	{ \
 		_mm256_storeu_si256((__m256i *)out, ((union v##sign##int##bits##x##size##_impl_data*)&vec)->avx2); \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -146,7 +169,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -155,12 +178,9 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
-	{ \
-		VEC_AVX2_MUL_##bits##x##size(sign); \
-	} \
+	VEC_AVX2_MUL_##bits##x##size(sign) \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -169,7 +189,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -178,7 +198,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx2_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -187,29 +207,25 @@
 		return vec1d->vec; \
 	} \
 	\
+	VEC_AVX2_AVG_##sign##bits##x##size \
+	\
+	VEC_AVX2_MINMAX_##sign##bits##x##size(min) \
+	VEC_AVX2_MINMAX_##sign##bits##x##size(max) \
+	\
 	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_avx2 = { \
-		v##sign##int##bits##x##size##_generic_splat, \
-		v##sign##int##bits##x##size##_avx2_load_aligned, \
-		v##sign##int##bits##x##size##_avx2_load, \
-		v##sign##int##bits##x##size##_avx2_store_aligned, \
-		v##sign##int##bits##x##size##_avx2_store, \
-		v##sign##int##bits##x##size##_avx2_add, \
-		v##sign##int##bits##x##size##_avx2_sub, \
-		v##sign##int##bits##x##size##_avx2_mul, \
-		v##sign##int##bits##x##size##_generic_div, \
-		v##sign##int##bits##x##size##_generic_avg, \
-		v##sign##int##bits##x##size##_avx2_and, \
-		v##sign##int##bits##x##size##_avx2_or, \
-		v##sign##int##bits##x##size##_avx2_xor, \
-		v##sign##int##bits##x##size##_generic_not, \
-		v##sign##int##bits##x##size##_generic_lshift, \
-		v##sign##int##bits##x##size##_generic_rshift, \
-		v##sign##int##bits##x##size##_generic_lrshift, \
-		v##sign##int##bits##x##size##_generic_cmplt, \
-		v##sign##int##bits##x##size##_generic_cmple, \
-		v##sign##int##bits##x##size##_generic_cmpeq, \
-		v##sign##int##bits##x##size##_generic_cmpge, \
-		v##sign##int##bits##x##size##_generic_cmpgt, \
+		.load_aligned = v##sign##int##bits##x##size##_avx2_load_aligned, \
+		.load = v##sign##int##bits##x##size##_avx2_load, \
+		.store_aligned = v##sign##int##bits##x##size##_avx2_store_aligned, \
+		.store = v##sign##int##bits##x##size##_avx2_store, \
+		.add = v##sign##int##bits##x##size##_avx2_add, \
+		.sub = v##sign##int##bits##x##size##_avx2_sub, \
+		.mul = VEC_AVX2_STRUCT_MUL_##bits##x##size(sign), \
+		.band = v##sign##int##bits##x##size##_avx2_and, \
+		.bor = v##sign##int##bits##x##size##_avx2_or, \
+		.bxor = v##sign##int##bits##x##size##_avx2_xor, \
+		.min = VEC_AVX2_STRUCT_MINMAX_##bits##x##size(min, sign), \
+		.max = VEC_AVX2_STRUCT_MINMAX_##bits##x##size(max, sign), \
+		.avg = VEC_AVX2_STRUCT_AVG_##sign##bits##x##size, \
 	};
 
 #define VEC_AVX2_DEFINE_OPERATIONS(bits, size) \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/impl/x86/avx512bw.c	Thu Apr 24 00:54:02 2025 -0400
@@ -0,0 +1,139 @@
+/**
+ * vec - a tiny SIMD vector library in C99
+ * 
+ * Copyright (c) 2024 Paper
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+**/
+
+#include "vec/impl/x86/avx512bw.h"
+
+#include <immintrin.h>
+
+/* ------------------------------------------------------------------------ */
+
+#define VEC_AVX512BW_MINMAX_TEMPLATE(SIGN, BITS, SIZE, INTLSIGN, OP) \
+	VEC_FUNC_IMPL v##SIGN##int##BITS##x##SIZE v##SIGN##int##BITS##x##SIZE##_avx512bw_##OP(v##SIGN##int##BITS##x##SIZE vec1, v##SIGN##int##BITS##x##SIZE vec2) \
+	{ \
+		union v##SIGN##int##BITS##x##SIZE##_impl_data *vec1d = (union v##SIGN##int##BITS##x##SIZE##_impl_data *)&vec1; \
+		union v##SIGN##int##BITS##x##SIZE##_impl_data *vec2d = (union v##SIGN##int##BITS##x##SIZE##_impl_data *)&vec2; \
+	\
+		vec1d->avx512bw = _mm512_##OP##_ep##INTLSIGN##BITS(vec1d->avx512bw, vec2d->avx512bw); \
+	\
+		return vec1d->vec; \
+	}
+
+#define VEC_AVX512BW_MINMAX_8x64(OP)   VEC_AVX512BW_MINMAX_TEMPLATE( , 8,  64, i, OP)
+#define VEC_AVX512BW_MINMAX_u8x64(OP)  VEC_AVX512BW_MINMAX_TEMPLATE(u, 8,  64, u, OP)
+#define VEC_AVX512BW_MINMAX_16x32(OP)  VEC_AVX512BW_MINMAX_TEMPLATE( , 16, 32, i, OP)
+#define VEC_AVX512BW_MINMAX_u16x32(OP) VEC_AVX512BW_MINMAX_TEMPLATE(u, 16, 32, u, OP)
+
+#define VEC_AVX512BW_STRUCT_MINMAX_8x64(OP, SIGN)  v##SIGN##int8x64_avx512bw_##OP
+#define VEC_AVX512BW_STRUCT_MINMAX_16x32(OP, SIGN) v##SIGN##int16x32_avx512bw_##OP
+
+/* ------------------------------------------------------------------------ */
+
+#define VEC_AVX512BW_OPERATION_EX(name, op, sign, bits, size, secondsign) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512bw_##name(v##sign##int##bits##x##size vec1, v##secondsign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##secondsign##int##bits##x##size##_impl_data *vec2d = (union v##secondsign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->avx512bw = _mm512_##op##_epi##bits(vec1d->avx512bw, vec2d->avx512bw); \
+	\
+		return vec1d->vec; \
+	}
+
+#define VEC_AVX512BW_OPERATION(name, op, sign, bits, size) \
+	VEC_AVX512BW_OPERATION_EX(name, op, sign, bits, size, sign)
+
+#define VEC_AVX512BW_OPERATION_SHIFT(name, op, sign, bits, size) \
+	VEC_AVX512BW_OPERATION_EX(name, op, sign, bits, size, u)
+
+#define VEC_AVX512BW_ADD_8x64(sign)  VEC_AVX512BW_OPERATION(add, add, sign, 8, 64)
+#define VEC_AVX512BW_ADD_16x32(sign) VEC_AVX512BW_OPERATION(add, add, sign, 16, 32)
+
+#define VEC_AVX512BW_SUB_8x64(sign)  VEC_AVX512BW_OPERATION(sub, sub, sign, 8, 64)
+#define VEC_AVX512BW_SUB_16x32(sign) VEC_AVX512BW_OPERATION(sub, sub, sign, 16, 32)
+
+#define VEC_AVX512BW_MUL_8x64(sign) /* nothing */
+#define VEC_AVX512BW_MUL_16x32(sign) VEC_AVX512BW_OPERATION(mul, mullo, sign, 16, 32)
+
+#define VEC_AVX512BW_LSHIFT_8x64(sign)  /* nothing */
+#define VEC_AVX512BW_LSHIFT_16x32(sign) VEC_AVX512BW_OPERATION_SHIFT(lshift, sllv, sign, 16, 32)
+
+#define VEC_AVX512BW_XRSHIFT(name, bits, size, sign, aORl) \
+	VEC_AVX512BW_OPERATION_SHIFT(name, sr##aORl##v, sign, bits, size)
+
+/* always the same, regardless of signedness */
+#define VEC_AVX512BW_LRSHIFT_8x64(sign)  /* nothing */
+#define VEC_AVX512BW_LRSHIFT_16x32(sign) VEC_AVX512BW_XRSHIFT(lrshift, 16, 32, sign, l)
+
+#define VEC_AVX512BW_RSHIFT_8x64(sign)  /* nothing */
+#define VEC_AVX512BW_RSHIFT_16x32(sign) VEC_AVX512BW_XRSHIFT(rshift, 16, 32, sign, a)
+
+#define VEC_AVX512BW_uRSHIFT_8x64(sign)  /* nothing */
+#define VEC_AVX512BW_uRSHIFT_16x32(sign) VEC_AVX512BW_XRSHIFT(rshift, 16, 32, sign, l)
+
+/* ok */
+#define VEC_AVX512BW_STRUCT_ADDSUB_8x64(OP, SIGN)  v##SIGN##int8x64_avx512bw_##OP
+#define VEC_AVX512BW_STRUCT_ADDSUB_16x32(OP, SIGN) v##SIGN##int16x32_avx512bw_##OP
+
+#define VEC_AVX512BW_STRUCT_OPERATION_8x64(OP, SIGN)  NULL
+#define VEC_AVX512BW_STRUCT_OPERATION_16x32(OP, SIGN) v##SIGN##int16x32_avx512bw_##OP
+
+/* ------------------------------------------------------------------------ */
+
+#define VEC_AVX512BW_DEFINE_OPERATIONS_SIGN(sign, bits, size) \
+	union v##sign##int##bits##x##size##_impl_data { \
+		v##sign##int##bits##x##size vec; \
+		__m512i avx512bw; \
+	}; \
+	\
+	VEC_STATIC_ASSERT(VEC_ALIGNOF(__m512i) <= VEC_ALIGNOF(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " alignment needs to be expanded to fit intrinsic type size"); \
+	VEC_STATIC_ASSERT(sizeof(__m512i) <= sizeof(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " needs to be expanded to fit intrinsic type size"); \
+	\
+	VEC_AVX512BW_ADD_##bits##x##size(sign) \
+	VEC_AVX512BW_SUB_##bits##x##size(sign) \
+	VEC_AVX512BW_MUL_##bits##x##size(sign) \
+	\
+	VEC_AVX512BW_LSHIFT_##bits##x##size(sign); \
+	VEC_AVX512BW_##sign##RSHIFT_##bits##x##size(sign); \
+	VEC_AVX512BW_LRSHIFT_##bits##x##size(sign); \
+	\
+	VEC_AVX512BW_MINMAX_##sign##bits##x##size(min) \
+	VEC_AVX512BW_MINMAX_##sign##bits##x##size(max) \
+	\
+	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_avx512bw = { \
+		.add = VEC_AVX512BW_STRUCT_ADDSUB_##bits##x##size(add, sign), \
+		.sub = VEC_AVX512BW_STRUCT_ADDSUB_##bits##x##size(sub, sign), \
+		.mul = VEC_AVX512BW_STRUCT_OPERATION_##bits##x##size(mul, sign), \
+		.lshift = VEC_AVX512BW_STRUCT_OPERATION_##bits##x##size(lshift, sign), \
+		.rshift = VEC_AVX512BW_STRUCT_OPERATION_##bits##x##size(rshift, sign), \
+		.lrshift = VEC_AVX512BW_STRUCT_OPERATION_##bits##x##size(lrshift, sign), \
+		.min = VEC_AVX512BW_STRUCT_MINMAX_##bits##x##size(min, sign), \
+		.max = VEC_AVX512BW_STRUCT_MINMAX_##bits##x##size(max, sign), \
+	};
+
+#define VEC_AVX512BW_DEFINE_OPERATIONS(bits, size) \
+	VEC_AVX512BW_DEFINE_OPERATIONS_SIGN(u, bits, size) \
+	VEC_AVX512BW_DEFINE_OPERATIONS_SIGN( , bits, size)
+
+VEC_AVX512BW_DEFINE_OPERATIONS(8, 64)
+VEC_AVX512BW_DEFINE_OPERATIONS(16, 32)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/impl/x86/avx512dq.c	Thu Apr 24 00:54:02 2025 -0400
@@ -0,0 +1,57 @@
+/**
+ * vec - a tiny SIMD vector library in C99
+ * 
+ * Copyright (c) 2024 Paper
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+**/
+
+#include "vec/impl/x86/avx512dq.h"
+
+#include <immintrin.h>
+
+/* ------------------------------------------------------------------------ */
+
+#define VEC_AVX512DQ_DEFINE_OPERATIONS_SIGN(sign, bits, size) \
+	union v##sign##int##bits##x##size##_impl_data { \
+		v##sign##int##bits##x##size vec; \
+		__m512i avx512dq; \
+	}; \
+	\
+	VEC_STATIC_ASSERT(VEC_ALIGNOF(__m512i) <= VEC_ALIGNOF(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " alignment needs to be expanded to fit intrinsic type size"); \
+	VEC_STATIC_ASSERT(sizeof(__m512i) <= sizeof(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " needs to be expanded to fit intrinsic type size"); \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512dq_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+	\
+		vec1d->avx512dq = _mm512_mullo_epi64(vec1d->avx512dq, vec2d->avx512dq); \
+		return vec1d->vec; \
+	} \
+	\
+	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_avx512dq = { \
+		.mul = v##sign##int##bits##x##size##_avx512dq_mul, \
+	};
+
+#define VEC_AVX512DQ_DEFINE_OPERATIONS(bits, size) \
+	VEC_AVX512DQ_DEFINE_OPERATIONS_SIGN(u, bits, size) \
+	VEC_AVX512DQ_DEFINE_OPERATIONS_SIGN( , bits, size)
+
+VEC_AVX512DQ_DEFINE_OPERATIONS(64, 8)
--- a/src/impl/x86/avx512f.c	Mon Nov 25 00:33:02 2024 -0500
+++ b/src/impl/x86/avx512f.c	Thu Apr 24 00:54:02 2025 -0400
@@ -23,202 +23,79 @@
 **/
 
 #include "vec/impl/x86/avx512f.h"
-#include "vec/impl/generic.h"
 
 #include <immintrin.h>
 
-// this is a stupid amount of work just to do these operations, is it really worth it ?
-// also same note in avx2.c applies here, these do not handle sign bits properly, which
-// isn't that big of a deal for regular arithmetic operations, but matters quite a bit
-// when doing things like arithmetic shifts.
-#define VEC_AVX512F_OPERATION_8x64(op, sign) \
-	do { \
-		union v##sign##int8x64_impl_data *vec1d = (union v##sign##int8x64_impl_data *)&vec1; \
-		union v##sign##int8x64_impl_data *vec2d = (union v##sign##int8x64_impl_data *)&vec2; \
+/* ------------------------------------------------------------------------ */
+
+#define VEC_AVX512F_MINMAX_TEMPLATE(SIGN, BITS, SIZE, INTLSIGN, OP) \
+	VEC_FUNC_IMPL v##SIGN##int##BITS##x##SIZE v##SIGN##int##BITS##x##SIZE##_avx512f_##OP(v##SIGN##int##BITS##x##SIZE vec1, v##SIGN##int##BITS##x##SIZE vec2) \
+	{ \
+		union v##SIGN##int##BITS##x##SIZE##_impl_data *vec1d = (union v##SIGN##int##BITS##x##SIZE##_impl_data *)&vec1; \
+		union v##SIGN##int##BITS##x##SIZE##_impl_data *vec2d = (union v##SIGN##int##BITS##x##SIZE##_impl_data *)&vec2; \
+	\
+		vec1d->avx512f = _mm512_##OP##_ep##INTLSIGN##BITS(vec1d->avx512f, vec2d->avx512f); \
 	\
-		/* unpack and operate */ \
-		__m512i dst_1 = _mm512_##op##_epi32(_mm512_srli_epi32(_mm512_slli_epi32(vec1d->avx512f, 24), 24), _mm512_srli_epi32(_mm512_slli_epi32(vec2d->avx512f, 24), 24)); \
-		__m512i dst_2 = _mm512_##op##_epi32(_mm512_srli_epi32(_mm512_slli_epi32(vec1d->avx512f, 16), 24), _mm512_srli_epi32(_mm512_slli_epi32(vec2d->avx512f, 16), 24)); \
-		__m512i dst_3 = _mm512_##op##_epi32(_mm512_srli_epi32(_mm512_slli_epi32(vec1d->avx512f, 8), 24), _mm512_srli_epi32(_mm512_slli_epi32(vec2d->avx512f, 8), 24)); \
-		__m512i dst_4 = _mm512_##op##_epi32(_mm512_srli_epi32(vec1d->avx512f, 24), _mm512_srli_epi32(vec2d->avx512f, 24)); \
+		return vec1d->vec; \
+	}
+
+#define VEC_AVX512F_MINMAX_32x16(OP)  VEC_AVX512F_MINMAX_TEMPLATE( , 32, 16, i, OP)
+#define VEC_AVX512F_MINMAX_u32x16(OP) VEC_AVX512F_MINMAX_TEMPLATE(u, 32, 16, u, OP)
+#define VEC_AVX512F_MINMAX_64x8(OP)   VEC_AVX512F_MINMAX_TEMPLATE( , 64, 8, i, OP)
+#define VEC_AVX512F_MINMAX_u64x8(OP)  VEC_AVX512F_MINMAX_TEMPLATE(u, 64, 8, u, OP)
+
+#define VEC_AVX512F_STRUCT_MINMAX_32x16(OP, SIGN)  v##SIGN##int32x16_avx512f_##OP
+#define VEC_AVX512F_STRUCT_MINMAX_64x8(OP, SIGN)  v##SIGN##int64x8_avx512f_##OP
+
+/* ------------------------------------------------------------------------ */
+
+#define VEC_AVX512F_OPERATION_EX(name, op, sign, bits, size, secondsign) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_##name(v##sign##int##bits##x##size vec1, v##secondsign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##secondsign##int##bits##x##size##_impl_data *vec2d = (union v##secondsign##int##bits##x##size##_impl_data *)&vec2; \
 	\
-		/* repack */ \
-		vec1d->avx512f = _mm512_or_si512( \
-			_mm512_or_si512( \
-				_mm512_srli_epi32(_mm512_slli_epi32(dst_1, 24), 24), \
-				_mm512_srli_epi32(_mm512_slli_epi32(dst_2, 24), 16) \
-			), \
-			_mm512_or_si512( \
-				_mm512_srli_epi32(_mm512_slli_epi32(dst_3, 24), 8), \
-				_mm512_slli_epi32(dst_4, 24) \
-			) \
-		); \
+		vec1d->avx512f = _mm512_##op##_epi##bits(vec1d->avx512f, vec2d->avx512f); \
 	\
 		return vec1d->vec; \
-	} while (0)
+	}
 
-#define VEC_AVX512F_OPERATION_16x32(op, sign) \
-	do { \
-		union v##sign##int16x32_impl_data *vec1d = (union v##sign##int16x32_impl_data *)&vec1; \
-		union v##sign##int16x32_impl_data *vec2d = (union v##sign##int16x32_impl_data *)&vec2; \
-	\
-		/* unpack and operate; it would be nice if we had an _m512_andi_epi32... */ \
-		__m512i dst_1 = _mm512_##op##_epi32(_mm512_srli_epi32(_mm512_slli_epi32(vec1d->avx512f, 16), 16), _mm512_srli_epi32(_mm512_slli_epi32(vec2d->avx512f, 16), 16)); \
-		__m512i dst_2 = _mm512_##op##_epi32(_mm512_srli_epi32(vec1d->avx512f, 16), _mm512_srli_epi32(vec2d->avx512f, 16)); \
-	\
-		/* repack */ \
-		vec1d->avx512f = _mm512_or_si512( \
-			_mm512_srli_epi32(_mm512_slli_epi32(dst_1, 16), 16), \
-			_mm512_slli_epi32(dst_2, 16) \
-		); \
-		return vec1d->vec; \
-	} while (0)
+#define VEC_AVX512F_OPERATION(name, op, sign, bits, size) \
+	VEC_AVX512F_OPERATION_EX(name, op, sign, bits, size, sign)
 
-#define VEC_AVX512F_ADD_8x64(sign) \
-	VEC_AVX512F_OPERATION_8x64(add, sign)
-
-#define VEC_AVX512F_ADD_16x32(sign) \
-	VEC_AVX512F_OPERATION_16x32(add, sign)
-
-#define VEC_AVX512F_ADD_32x16(sign) \
-	do { \
-		union v##sign##int32x16_impl_data *vec1d = (union v##sign##int32x16_impl_data *)&vec1; \
-		union v##sign##int32x16_impl_data *vec2d = (union v##sign##int32x16_impl_data *)&vec2; \
-	\
-		vec1d->avx512f = _mm512_add_epi32(vec1d->avx512f, vec2d->avx512f); \
-		return vec1d->vec; \
-	} while (0)
+#define VEC_AVX512F_OPERATION_SHIFT(name, op, sign, bits, size) \
+	VEC_AVX512F_OPERATION_EX(name, op, sign, bits, size, u)
 
-#define VEC_AVX512F_ADD_64x8(sign) \
-	do { \
-		union v##sign##int64x8_impl_data *vec1d = (union v##sign##int64x8_impl_data *)&vec1; \
-		union v##sign##int64x8_impl_data *vec2d = (union v##sign##int64x8_impl_data *)&vec2; \
-	\
-		vec1d->avx512f = _mm512_add_epi64(vec1d->avx512f, vec2d->avx512f); \
-		return vec1d->vec; \
-	} while (0)
-
-#define VEC_AVX512F_SUB_8x64(sign) \
-	VEC_AVX512F_OPERATION_8x64(sub, sign)
-
-#define VEC_AVX512F_SUB_16x32(sign) \
-	VEC_AVX512F_OPERATION_16x32(sub, sign)
-
-#define VEC_AVX512F_SUB_32x16(sign) \
-	do { \
-		union v##sign##int32x16_impl_data *vec1d = (union v##sign##int32x16_impl_data *)&vec1; \
-		union v##sign##int32x16_impl_data *vec2d = (union v##sign##int32x16_impl_data *)&vec2; \
-	\
-		vec1d->avx512f = _mm512_sub_epi32(vec1d->avx512f, vec2d->avx512f); \
-		return vec1d->vec; \
-	} while (0)
+#define VEC_AVX512F_ADD_32x16(sign) VEC_AVX512F_OPERATION(add, add, sign, 32, 16)
+#define VEC_AVX512F_ADD_64x8(sign) VEC_AVX512F_OPERATION(add, add, sign, 64, 8)
 
-#define VEC_AVX512F_SUB_64x8(sign) \
-	do { \
-		union v##sign##int64x8_impl_data *vec1d = (union v##sign##int64x8_impl_data *)&vec1; \
-		union v##sign##int64x8_impl_data *vec2d = (union v##sign##int64x8_impl_data *)&vec2; \
-	\
-		vec1d->avx512f = _mm512_sub_epi64(vec1d->avx512f, vec2d->avx512f); \
-		return vec1d->vec; \
-	} while (0)
-
-#define VEC_AVX512F_MUL_8x64(sign) \
-	VEC_AVX512F_OPERATION_8x64(mullo, sign)
+#define VEC_AVX512F_SUB_32x16(sign) VEC_AVX512F_OPERATION(sub, sub, sign, 32, 16)
+#define VEC_AVX512F_SUB_64x8(sign) VEC_AVX512F_OPERATION(sub, sub, sign, 64, 8)
 
-#define VEC_AVX512F_MUL_16x32(sign) \
-	VEC_AVX512F_OPERATION_16x32(mullo, sign)
-
-#define VEC_AVX512F_MUL_32x16(sign) \
-	do { \
-		union v##sign##int32x16_impl_data *vec1d = (union v##sign##int32x16_impl_data *)&vec1; \
-		union v##sign##int32x16_impl_data *vec2d = (union v##sign##int32x16_impl_data *)&vec2; \
-	\
-		vec1d->avx512f = _mm512_mullo_epi32(vec1d->avx512f, vec2d->avx512f); \
-		return vec1d->vec; \
-	} while (0)
+#define VEC_AVX512F_MUL_32x16(sign) VEC_AVX512F_OPERATION(mul, mullo, sign, 32, 16)
+#define VEC_AVX512F_MUL_64x8(sign) VEC_AVX512F_OPERATION(mul, mullox, sign, 64, 8)
 
-#define VEC_AVX512F_MUL_64x8(sign) \
-	do { \
-		union v##sign##int64x8_impl_data *vec1d = (union v##sign##int64x8_impl_data *)&vec1; \
-		union v##sign##int64x8_impl_data *vec2d = (union v##sign##int64x8_impl_data *)&vec2; \
-	\
-		__m512i ac = _mm512_mul_epu32(vec1d->avx512f, vec2d->avx512f); \
-		__m512i b  = _mm512_srli_epi64(vec1d->avx512f, 32); \
-		__m512i bc = _mm512_mul_epu32(b, vec2d->avx512f); \
-		__m512i d  = _mm512_srli_epi64(vec2d->avx512f, 32); \
-		__m512i ad = _mm512_mul_epu32(vec1d->avx512f, d); \
-		__m512i hi = _mm512_add_epi64(bc, ad); \
-		hi = _mm512_slli_epi64(hi, 32); \
-	\
-		vec1d->avx512f = _mm512_add_epi64(hi, ac); \
-		return vec1d->vec; \
-	} while (0)
-
-#define VEC_AVX512F_LSHIFT_8x64(sign) \
-	VEC_AVX512F_OPERATION_8x64(sllv, sign)
+#define VEC_AVX512F_LSHIFT_32x16(sign) VEC_AVX512F_OPERATION_SHIFT(lshift, sllv, sign, 32, 16)
+#define VEC_AVX512F_LSHIFT_64x8(sign)  VEC_AVX512F_OPERATION_SHIFT(lshift, sllv, sign, 64, 8)
 
-#define VEC_AVX512F_LSHIFT_16x32(sign) \
-	VEC_AVX512F_OPERATION_16x32(sllv, sign)
+#define VEC_AVX512F_XRSHIFT(name, bits, size, sign, aORl) \
+	VEC_AVX512F_OPERATION_SHIFT(name, sr##aORl##v, sign, bits, size)
 
-#define VEC_AVX512F_LSHIFT_32x16(sign) \
-	do { \
-		union v##sign##int32x16_impl_data *vec1d = (union v##sign##int32x16_impl_data *)&vec1; \
-		union v##sign##int32x16_impl_data *vec2d = (union v##sign##int32x16_impl_data *)&vec2; \
-	\
-		vec1d->avx512f = _mm512_sllv_epi32(vec1d->avx512f, vec2d->avx512f); \
-		return vec1d->vec; \
-	} while (0)
-
-#define VEC_AVX512F_LSHIFT_64x8(sign) \
-	do { \
-		union v##sign##int64x8_impl_data *vec1d = (union v##sign##int64x8_impl_data *)&vec1; \
-		union v##sign##int64x8_impl_data *vec2d = (union v##sign##int64x8_impl_data *)&vec2; \
-	\
-		vec1d->avx512f = _mm512_sllv_epi64(vec1d->avx512f, vec2d->avx512f); \
-		return vec1d->vec; \
-	} while (0)
+/* always the same, regardless of signedness */
+#define VEC_AVX512F_LRSHIFT_32x16(sign) VEC_AVX512F_XRSHIFT(lrshift, 32, 16, sign, l)
+#define VEC_AVX512F_LRSHIFT_64x8(sign) VEC_AVX512F_XRSHIFT(lrshift, 64, 8, sign, l)
 
-#define VEC_AVX512F_lRSHIFT_8x64(sign) \
-	VEC_AVX512F_OPERATION_8x64(srlv, sign)
-
-#define VEC_AVX512F_lRSHIFT_16x32(sign) \
-	VEC_AVX512F_OPERATION_16x32(srlv, sign)
+#define VEC_AVX512F_RSHIFT_32x16(sign) VEC_AVX512F_XRSHIFT(rshift, 32, 16, sign, a)
+#define VEC_AVX512F_RSHIFT_64x8(sign) VEC_AVX512F_XRSHIFT(rshift, 64, 8, sign, a)
 
-#define VEC_AVX512F_aRSHIFT_8x64(sign) \
-	do { \
-		return v##sign##int8x64_generic_rshift(vec1, vec2); \
-	} while (0)
-
-#define VEC_AVX512F_aRSHIFT_16x32(sign) \
-	do { \
-		return v##sign##int16x32_generic_rshift(vec1, vec2); \
-	} while (0)
-
-#define VEC_AVX512F_RSHIFT_8x64(sign, aORl) VEC_AVX512F_##aORl##RSHIFT_8x64(sign)
-#define VEC_AVX512F_RSHIFT_16x32(sign, aORl) VEC_AVX512F_##aORl##RSHIFT_16x32(sign)
+#define VEC_AVX512F_uRSHIFT_32x16(sign) VEC_AVX512F_XRSHIFT(rshift, 32, 16, sign, l)
+#define VEC_AVX512F_uRSHIFT_64x8(sign) VEC_AVX512F_XRSHIFT(rshift, 64, 8, sign, l)
 
-#define VEC_AVX512F_RSHIFT_32x16(sign, aORl) \
-	do { \
-		union v##sign##int32x16_impl_data *vec1d = (union v##sign##int32x16_impl_data *)&vec1; \
-		union v##sign##int32x16_impl_data *vec2d = (union v##sign##int32x16_impl_data *)&vec2; \
-	\
-		vec1d->avx512f = _mm512_sr##aORl##v_epi32(vec1d->avx512f, vec2d->avx512f); \
-		return vec1d->vec; \
-	} while (0)
+/* ok */
+#define VEC_AVX512F_STRUCT_OPERATION_32x16(OP, SIGN) v##SIGN##int32x16_avx512f_##OP
+#define VEC_AVX512F_STRUCT_OPERATION_64x8(OP, SIGN)  v##SIGN##int64x8_avx512f_##OP
 
-#define VEC_AVX512F_RSHIFT_64x8(sign, aORl) \
-	do { \
-		union v##sign##int64x8_impl_data *vec1d = (union v##sign##int64x8_impl_data *)&vec1; \
-		union v##sign##int64x8_impl_data *vec2d = (union v##sign##int64x8_impl_data *)&vec2; \
-	\
-		vec1d->avx512f = _mm512_sr##aORl##v_epi64(vec1d->avx512f, vec2d->avx512f); \
-		return vec1d->vec; \
-	} while (0)
-
-#define VEC_AVX512F_uRSHIFT_8x64(sign, aORl) VEC_AVX512F_RSHIFT_8x64(sign, l)
-#define VEC_AVX512F_uRSHIFT_16x32(sign, aORl) VEC_AVX512F_RSHIFT_16x32(sign, l)
-#define VEC_AVX512F_uRSHIFT_32x16(sign, aORl) VEC_AVX512F_RSHIFT_32x16(sign, l)
-#define VEC_AVX512F_uRSHIFT_64x8(sign, aORl) VEC_AVX512F_RSHIFT_64x8(sign, l)
+/* ------------------------------------------------------------------------ */
 
 #define VEC_AVX512F_DEFINE_OPERATIONS_SIGN(sign, bits, size) \
 	union v##sign##int##bits##x##size##_impl_data { \
@@ -229,46 +106,35 @@
 	VEC_STATIC_ASSERT(VEC_ALIGNOF(__m512i) <= VEC_ALIGNOF(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " alignment needs to be expanded to fit intrinsic type size"); \
 	VEC_STATIC_ASSERT(sizeof(__m512i) <= sizeof(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " needs to be expanded to fit intrinsic type size"); \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_load_aligned(const vec_##sign##int##bits in[size]) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_load_aligned(const vec_##sign##int##bits in[size]) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data vec; \
 		vec.avx512f = _mm512_load_si512((const __m512i *)in); \
 		return vec.vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_load(const vec_##sign##int##bits in[size]) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_load(const vec_##sign##int##bits in[size]) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data vec; \
 		vec.avx512f = _mm512_loadu_si512((const __m512i *)in); \
 		return vec.vec; \
 	} \
 	\
-	static void v##sign##int##bits##x##size##_avx512f_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_avx512f_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
 	{ \
 		_mm512_store_si512((__m512i *)out, ((union v##sign##int##bits##x##size##_impl_data *)&vec)->avx512f); \
 	} \
 	\
-	static void v##sign##int##bits##x##size##_avx512f_store(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_avx512f_store(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
 	{ \
 		_mm512_storeu_si512((__m512i *)out, ((union v##sign##int##bits##x##size##_impl_data *)&vec)->avx512f); \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
-	{ \
-		VEC_AVX512F_ADD_##bits##x##size(sign); \
-	} \
+	VEC_AVX512F_ADD_##bits##x##size(sign) \
+	VEC_AVX512F_SUB_##bits##x##size(sign) \
+	VEC_AVX512F_MUL_##bits##x##size(sign) \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
-	{ \
-		VEC_AVX512F_SUB_##bits##x##size(sign); \
-	} \
-	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
-	{ \
-		VEC_AVX512F_MUL_##bits##x##size(sign); \
-	} \
-	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -277,7 +143,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -286,7 +152,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -295,51 +161,34 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_lshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
-	{ \
-		VEC_AVX512F_LSHIFT_##bits##x##size(sign); \
-	} \
+	VEC_AVX512F_LSHIFT_##bits##x##size(sign); \
+	VEC_AVX512F_##sign##RSHIFT_##bits##x##size(sign); \
+	VEC_AVX512F_LRSHIFT_##bits##x##size(sign); \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_rshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
-	{ \
-		VEC_AVX512F_##sign##RSHIFT_##bits##x##size(sign, a); \
-	} \
-	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_avx512f_lrshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2) \
-	{ \
-		VEC_AVX512F_RSHIFT_##bits##x##size(sign, l); \
-	} \
+	VEC_AVX512F_MINMAX_##sign##bits##x##size(min) \
+	VEC_AVX512F_MINMAX_##sign##bits##x##size(max) \
 	\
 	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_avx512f = { \
-		v##sign##int##bits##x##size##_generic_splat, \
-		v##sign##int##bits##x##size##_avx512f_load_aligned, \
-		v##sign##int##bits##x##size##_avx512f_load, \
-		v##sign##int##bits##x##size##_avx512f_store_aligned, \
-		v##sign##int##bits##x##size##_avx512f_store, \
-		v##sign##int##bits##x##size##_avx512f_add, \
-		v##sign##int##bits##x##size##_avx512f_sub, \
-		v##sign##int##bits##x##size##_avx512f_mul, \
-		v##sign##int##bits##x##size##_generic_div, \
-		v##sign##int##bits##x##size##_generic_avg, \
-		v##sign##int##bits##x##size##_avx512f_and, \
-		v##sign##int##bits##x##size##_avx512f_or, \
-		v##sign##int##bits##x##size##_avx512f_xor, \
-		v##sign##int##bits##x##size##_generic_not, \
-		v##sign##int##bits##x##size##_avx512f_lshift, \
-		v##sign##int##bits##x##size##_avx512f_rshift, \
-		v##sign##int##bits##x##size##_avx512f_lrshift, \
-		v##sign##int##bits##x##size##_generic_cmplt, \
-		v##sign##int##bits##x##size##_generic_cmple, \
-		v##sign##int##bits##x##size##_generic_cmpeq, \
-		v##sign##int##bits##x##size##_generic_cmpge, \
-		v##sign##int##bits##x##size##_generic_cmpgt, \
+		.load_aligned = v##sign##int##bits##x##size##_avx512f_load_aligned, \
+		.load = v##sign##int##bits##x##size##_avx512f_load, \
+		.store_aligned = v##sign##int##bits##x##size##_avx512f_store_aligned, \
+		.store = v##sign##int##bits##x##size##_avx512f_store, \
+		.add = VEC_AVX512F_STRUCT_OPERATION_##bits##x##size(add, sign), \
+		.sub = VEC_AVX512F_STRUCT_OPERATION_##bits##x##size(sub, sign), \
+		.mul = VEC_AVX512F_STRUCT_OPERATION_##bits##x##size(mul, sign), \
+		.band = v##sign##int##bits##x##size##_avx512f_and, \
+		.bor = v##sign##int##bits##x##size##_avx512f_or, \
+		.bxor = v##sign##int##bits##x##size##_avx512f_xor, \
+		.lshift = VEC_AVX512F_STRUCT_OPERATION_##bits##x##size(lshift, sign), \
+		.rshift = VEC_AVX512F_STRUCT_OPERATION_##bits##x##size(rshift, sign), \
+		.lrshift = VEC_AVX512F_STRUCT_OPERATION_##bits##x##size(lrshift, sign), \
+		.min = VEC_AVX512F_STRUCT_MINMAX_##bits##x##size(min, sign), \
+		.max = VEC_AVX512F_STRUCT_MINMAX_##bits##x##size(max, sign), \
 	};
 
 #define VEC_AVX512F_DEFINE_OPERATIONS(bits, size) \
 	VEC_AVX512F_DEFINE_OPERATIONS_SIGN(u, bits, size) \
 	VEC_AVX512F_DEFINE_OPERATIONS_SIGN( , bits, size)
 
-VEC_AVX512F_DEFINE_OPERATIONS(8, 64)
-VEC_AVX512F_DEFINE_OPERATIONS(16, 32)
 VEC_AVX512F_DEFINE_OPERATIONS(32, 16)
 VEC_AVX512F_DEFINE_OPERATIONS(64, 8)
--- a/src/impl/x86/mmx.c	Mon Nov 25 00:33:02 2024 -0500
+++ b/src/impl/x86/mmx.c	Thu Apr 24 00:54:02 2025 -0400
@@ -24,46 +24,67 @@
 
 #include "vec/vec.h"
 #include "vec/impl/x86/mmx.h"
-#include "vec/impl/generic.h"
 
 #include <mmintrin.h>
 #include <string.h>
 
-#define VEC_MMX_OPERATION_8x8(op, sign) \
-	do { \
-		/* unpack and multiply */ \
-		union v##sign##int8x8_impl_data *vec1d = (union v##sign##int8x8_impl_data *)&vec1; \
-		union v##sign##int8x8_impl_data *vec2d = (union v##sign##int8x8_impl_data *)&vec2; \
-	\
-		__m64 dst_even = _mm_##op##_pi16(vec1d->mmx, vec2d->mmx); \
-		__m64 dst_odd = _mm_##op##_pi16(_mm_srli_pi16(vec1d->mmx, 8), _mm_srli_pi16(vec2d->mmx, 8)); \
-	\
-		/* repack */ \
-		vec1d->mmx = _mm_or_si64( \
-			_mm_slli_pi16(dst_odd, 8), \
-			_mm_srli_pi16(_mm_slli_pi16(dst_even, 8), 8) \
-		); \
-		return vec1d->vec; \
-	} while (0)
+/* ------------------------------------------------------------------------ */
 
-// shared between MMX variations
-#define VEC_MMX_MUL_8x8(sign) \
-	VEC_MMX_OPERATION_8x8(mullo, sign)
-
+#define VEC_MMX_MUL_8x8(sign) /* nothing */
 #define VEC_MMX_MUL_16x4(sign) \
-	do { \
+	VEC_FUNC_IMPL v##sign##int16x4 v##sign##int16x4_mmx_mul(v##sign##int16x4 vec1, v##sign##int16x4 vec2) \
+	{ \
 		union v##sign##int16x4_impl_data *vec1d = (union v##sign##int16x4_impl_data *)&vec1; \
 		union vuint16x4_impl_data *vec2d = (union vuint16x4_impl_data *)&vec2; \
 	\
 		vec1d->mmx = _mm_mullo_pi16(vec1d->mmx, vec2d->mmx); \
 		return vec1d->vec; \
-	} while (0)
+	}
+#define VEC_MMX_MUL_32x2(sign) /* nothing */
+
+#define VEC_MMX_STRUCT_MUL_8x8(sign) NULL
+#define VEC_MMX_STRUCT_MUL_16x4(sign) v##sign##int16x4_mmx_mul
+#define VEC_MMX_STRUCT_MUL_32x8(sign) NULL
+
+/* ------------------------------------------------------------------------ */
+/* comparison */
 
-#define VEC_MMX_MUL_32x2(sign) \
-	/* TODO implement this for real */ \
-	do { \
-		return v##sign##int32x2_generic_mul(vec1, vec2); \
-	} while (0)
+/* helper funcs */
+#define VEC_xMMX_CMP(name, op, sign, bits, size, first, second, VARS, TRANS1, TRANS2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_mmx_##name(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+		VARS \
+	\
+		TRANS1 \
+	\
+		vec1d->mmx = _mm_##op##_pi##bits(vec##first##d->mmx, vec##second##d->mmx); \
+	\
+		TRANS2 \
+	\
+		return vec1d->vec; \
+	}
+
+#define VEC_MMX_CMP(name, op, bits, size, first, second) \
+	VEC_xMMX_CMP(name, op, /* nothing */, bits, size, first, second, /* nothing */, /* nothing */, /* nothing */)
+
+#define VEC_uMMX_CMP(name, op, bits, size, first, second) \
+	VEC_xMMX_CMP(name, op, u, bits, size, first, second, \
+		__m64 xor_val = _mm_set1_pi##bits(1u << (bits - 1)); \
+	, { \
+		vec1d->mmx = _mm_xor_si64(vec1d->mmx, xor_val); \
+		vec2d->mmx = _mm_xor_si64(vec2d->mmx, xor_val); \
+	}, \
+	{ \
+		/* nothing */ \
+	})
+
+#define VEC_MMX_CMPEQ(sign, bits, size) VEC_xMMX_CMP(cmpeq, cmpeq, sign, bits, size, 1, 2, , ,)
+#define VEC_MMX_CMPLT(sign, bits, size) VEC_##sign##MMX_CMP(cmplt, cmpgt, bits, size, 2, 1)
+#define VEC_MMX_CMPGT(sign, bits, size) VEC_##sign##MMX_CMP(cmpgt, cmpgt, bits, size, 1, 2)
+
+/* ------------------------------------------------------------------------ */
 
 #define VEC_MMX_DEFINE_OPERATIONS_SIGN(sign, bits, size) \
 	union v##sign##int##bits##x##size##_impl_data { \
@@ -74,19 +95,19 @@
 	VEC_STATIC_ASSERT(VEC_ALIGNOF(__m64) <= VEC_ALIGNOF(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " alignment needs to be expanded to fit intrinsic type size"); \
 	VEC_STATIC_ASSERT(sizeof(__m64) <= sizeof(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " needs to be expanded to fit intrinsic type size"); \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_mmx_load_aligned(const vec_##sign##int##bits in[size]) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_mmx_load_aligned(const vec_##sign##int##bits in[size]) \
 	{ \
 		v##sign##int##bits##x##size vec; \
 		memcpy(&vec, in, sizeof(vec)); \
 		return vec; \
 	} \
 	\
-	static void v##sign##int##bits##x##size##_mmx_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_mmx_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
 	{ \
 		memcpy(out, &vec, sizeof(vec)); \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_mmx_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_mmx_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -96,7 +117,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_mmx_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_mmx_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -106,12 +127,9 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_mmx_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
-	{ \
-		VEC_MMX_MUL_##bits##x##size(sign); \
-	} \
+	VEC_MMX_MUL_##bits##x##size(sign) \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_mmx_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_mmx_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -121,7 +139,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_mmx_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_mmx_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -131,7 +149,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	static v##sign##int##bits##x##size v##sign##int##bits##x##size##_mmx_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_mmx_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -141,29 +159,22 @@
 		return vec1d->vec; \
 	} \
 	\
+	VEC_MMX_CMPEQ(sign, bits, size) \
+	VEC_MMX_CMPLT(sign, bits, size) \
+	VEC_MMX_CMPGT(sign, bits, size) \
+	\
 	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_mmx = { \
-		v##sign##int##bits##x##size##_generic_splat, \
-		v##sign##int##bits##x##size##_mmx_load_aligned, \
-		v##sign##int##bits##x##size##_mmx_load_aligned, \
-		v##sign##int##bits##x##size##_mmx_store_aligned, \
-		v##sign##int##bits##x##size##_mmx_store_aligned, \
-		v##sign##int##bits##x##size##_mmx_add, \
-		v##sign##int##bits##x##size##_mmx_sub, \
-		v##sign##int##bits##x##size##_mmx_mul, \
-		v##sign##int##bits##x##size##_generic_div, \
-		v##sign##int##bits##x##size##_generic_avg, \
-		v##sign##int##bits##x##size##_mmx_and, \
-		v##sign##int##bits##x##size##_mmx_or, \
-		v##sign##int##bits##x##size##_mmx_xor, \
-		v##sign##int##bits##x##size##_generic_not, \
-		v##sign##int##bits##x##size##_generic_lshift, \
-		v##sign##int##bits##x##size##_generic_rshift, \
-		v##sign##int##bits##x##size##_generic_lrshift, \
-		v##sign##int##bits##x##size##_generic_cmplt, \
-		v##sign##int##bits##x##size##_generic_cmple, \
-		v##sign##int##bits##x##size##_generic_cmpeq, \
-		v##sign##int##bits##x##size##_generic_cmpge, \
-		v##sign##int##bits##x##size##_generic_cmpgt, \
+		.load_aligned = v##sign##int##bits##x##size##_mmx_load_aligned, \
+		.load = v##sign##int##bits##x##size##_mmx_load_aligned, \
+		.store_aligned = v##sign##int##bits##x##size##_mmx_store_aligned, \
+		.store = v##sign##int##bits##x##size##_mmx_store_aligned, \
+		.add = v##sign##int##bits##x##size##_mmx_add, \
+		.sub = v##sign##int##bits##x##size##_mmx_sub, \
+		.mul = VEC_MMX_STRUCT_MUL_8x8(sign), \
+		.band = v##sign##int##bits##x##size##_mmx_and, \
+		.bor = v##sign##int##bits##x##size##_mmx_or, \
+		.bxor = v##sign##int##bits##x##size##_mmx_xor, \
+		.cmpeq = v##sign##int##bits##x##size##_mmx_cmpeq, \
 	};
 
 #define VEC_MMX_DEFINE_OPERATIONS(bits, size) \
--- a/src/impl/x86/sse2.c	Mon Nov 25 00:33:02 2024 -0500
+++ b/src/impl/x86/sse2.c	Thu Apr 24 00:54:02 2025 -0400
@@ -23,10 +23,110 @@
 **/
 
 #include "vec/impl/x86/sse2.h"
-#include "vec/impl/generic.h"
 
 #include <emmintrin.h>
 
+#define VEC_SSE2_DEFINE_IMPL_DATA(sign, bits, size) \
+	union v##sign##int##bits##x##size##_impl_data { \
+		v##sign##int##bits##x##size vec; \
+		__m128i sse; \
+	}; \
+	\
+	VEC_STATIC_ASSERT(VEC_ALIGNOF(__m128i) <= VEC_ALIGNOF(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " alignment needs to be expanded to fit intrinsic type size"); \
+	VEC_STATIC_ASSERT(sizeof(__m128i) <= sizeof(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " needs to be expanded to fit intrinsic type size");
+
+VEC_SSE2_DEFINE_IMPL_DATA( , 8,  16)
+VEC_SSE2_DEFINE_IMPL_DATA(u, 8,  16)
+VEC_SSE2_DEFINE_IMPL_DATA( , 16, 8)
+VEC_SSE2_DEFINE_IMPL_DATA(u, 16, 8)
+VEC_SSE2_DEFINE_IMPL_DATA( , 32, 4)
+VEC_SSE2_DEFINE_IMPL_DATA(u, 32, 4)
+VEC_SSE2_DEFINE_IMPL_DATA( , 64, 2)
+VEC_SSE2_DEFINE_IMPL_DATA(u, 64, 2)
+
+/* eh */
+#define MM_SET1_8(x)  _mm_set1_epi8(x)
+#define MM_SET1_16(x) _mm_set1_epi16(x)
+#define MM_SET1_32(x) _mm_set1_epi32(x)
+#define MM_SET1_64(x) _mm_set1_epi64x(x)
+
+/* ------------------------------------------------------------------------ */
+
+/* unfortunately doing this for SSE2 is PREPROCESSOR HELL */
+#define VEC_SSE2_MINMAX_8x16_TEMPLATE(SIGN, OP, VALS, ADDITIONAL1, ADDITIONAL2) \
+	VEC_FUNC_IMPL v##SIGN##int8x16 v##SIGN##int8x16_sse2_##OP(v##SIGN##int8x16 vec1, v##SIGN##int8x16 vec2) \
+	{ \
+		union v##SIGN##int8x16_impl_data *vec1d = (union v##SIGN##int8x16_impl_data *)&vec1; \
+		union v##SIGN##int8x16_impl_data *vec2d = (union v##SIGN##int8x16_impl_data *)&vec2; \
+		VALS \
+	\
+		ADDITIONAL1 \
+	\
+		vec1d->sse = _mm_##OP##_epu8(vec1d->sse, vec2d->sse); \
+	\
+		ADDITIONAL2 \
+	\
+		return vec1d->vec; \
+	}
+
+/* conveniently, this looks like K&R C ;) */
+#define VEC_SSE2_MINMAX_8x16(OP) \
+	VEC_SSE2_MINMAX_8x16_TEMPLATE(/* nothing */, OP, \
+		__m128i xor_val = _mm_set1_epi8(0x80u); \
+	, { \
+		vec1d->sse = _mm_xor_si128(vec1d->sse, xor_val); \
+		vec2d->sse = _mm_xor_si128(vec2d->sse, xor_val); \
+	}, \
+	{ \
+		vec1d->sse = _mm_xor_si128(vec1d->sse, xor_val); \
+	})
+
+#define VEC_SSE2_MINMAX_u8x16(OP) \
+	VEC_SSE2_MINMAX_8x16_TEMPLATE(u, OP, /* nothing */, /* nothing */, /* nothing */)
+
+#define VEC_SSE2_MINMAX_16x8_TEMPLATE(SIGN, OP, VALS, ADDITIONAL1, ADDITIONAL2) \
+	VEC_FUNC_IMPL v##SIGN##int16x8 v##SIGN##int16x8_sse2_##OP(v##SIGN##int16x8 vec1, v##SIGN##int16x8 vec2) \
+	{ \
+		union v##SIGN##int16x8_impl_data *vec1d = (union v##SIGN##int16x8_impl_data *)&vec1; \
+		union v##SIGN##int16x8_impl_data *vec2d = (union v##SIGN##int16x8_impl_data *)&vec2; \
+		VALS \
+	\
+		ADDITIONAL1 \
+	\
+		vec1d->sse = _mm_##OP##_epi16(vec1d->sse, vec2d->sse); \
+	\
+		ADDITIONAL2 \
+	\
+		return vec1d->vec; \
+	}
+
+#define VEC_SSE2_MINMAX_16x8(OP) \
+	VEC_SSE2_MINMAX_16x8_TEMPLATE(/* nothing */, OP, /* nothing */, /* nothing */, /* nothing */)
+
+#define VEC_SSE2_MINMAX_u16x8(OP) \
+	VEC_SSE2_MINMAX_16x8_TEMPLATE(u, OP, \
+		__m128i xor_val = _mm_set1_epi16(0x8000u); \
+	, { \
+		vec1d->sse = _mm_xor_si128(vec1d->sse, xor_val); \
+		vec2d->sse = _mm_xor_si128(vec2d->sse, xor_val); \
+	}, \
+	{ \
+		vec1d->sse = _mm_xor_si128(vec1d->sse, xor_val); \
+	})
+
+#define VEC_SSE2_MINMAX_32x4(OP) /* none */
+#define VEC_SSE2_MINMAX_64x2(OP) /* none */
+#define VEC_SSE2_MINMAX_u32x4(OP) /* none */
+#define VEC_SSE2_MINMAX_u64x2(OP) /* none */
+
+#define VEC_SSE2_STRUCT_MINMAX_8x16(OP, SIGN) v##SIGN##int8x16_sse2_##OP
+#define VEC_SSE2_STRUCT_MINMAX_16x8(OP, SIGN) v##SIGN##int16x8_sse2_##OP
+#define VEC_SSE2_STRUCT_MINMAX_32x4(OP, SIGN) NULL
+#define VEC_SSE2_STRUCT_MINMAX_64x2(OP, SIGN) NULL
+
+/* ------------------------------------------------------------------------ */
+/* multiplication */
+
 #define VEC_SSE2_OPERATION_8x16(op, sign) \
 	do { \
 		/* unpack and multiply */ \
@@ -92,45 +192,58 @@
 		return vec1d->vec; \
 	} while (0)
 
-#define VEC_SSE2_CMPEQ_8x16(sign) \
-	do { \
-		union v##sign##int8x16_impl_data *vec1d = (union v##sign##int8x16_impl_data *)&vec1; \
-		union v##sign##int8x16_impl_data *vec2d = (union v##sign##int8x16_impl_data *)&vec2; \
-	\
-		vec1d->sse = _mm_cmpeq_epi8(vec1d->sse, vec2d->sse); \
-		return vec1d->vec; \
-	} while (0)
+/* ------------------------------------------------------------------------ */
+/* comparison */
 
-#define VEC_SSE2_CMPEQ_16x8(sign) \
-	do { \
-		union v##sign##int16x8_impl_data *vec1d = (union v##sign##int16x8_impl_data *)&vec1; \
-		union v##sign##int16x8_impl_data *vec2d = (union v##sign##int16x8_impl_data *)&vec2; \
+/* helper funcs */
+#define VEC_xSSE2_CMP(name, op, sign, bits, size, first, second, VARS, TRANS1, TRANS2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_##name(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+		VARS \
+	\
+		TRANS1 \
 	\
-		vec1d->sse = _mm_cmpeq_epi16(vec1d->sse, vec2d->sse); \
+		vec1d->sse = _mm_##op##_epi##bits(vec##first##d->sse, vec##second##d->sse); \
+	\
+		TRANS2 \
+	\
 		return vec1d->vec; \
-	} while (0)
+	}
+
+#define VEC_SSE2_CMP(name, op, bits, size, first, second) \
+	VEC_xSSE2_CMP(name, op, /* nothing */, bits, size, first, second, /* nothing */, /* nothing */, /* nothing */)
 
-#define VEC_SSE2_CMPEQ_32x4(sign) \
-	do { \
-		union v##sign##int32x4_impl_data *vec1d = (union v##sign##int32x4_impl_data *)&vec1; \
-		union v##sign##int32x4_impl_data *vec2d = (union v##sign##int32x4_impl_data *)&vec2; \
-	\
-		vec1d->sse = _mm_cmpeq_epi32(vec1d->sse, vec2d->sse); \
-		return vec1d->vec; \
-	} while (0)
+#define VEC_uSSE2_CMP(name, op, bits, size, first, second) \
+	VEC_xSSE2_CMP(name, op, u, bits, size, first, second, \
+		__m128i xor_val = MM_SET1_##bits(1u << (bits - 1)); \
+	, { \
+		vec1d->sse = _mm_xor_si128(vec1d->sse, xor_val); \
+		vec2d->sse = _mm_xor_si128(vec2d->sse, xor_val); \
+	}, \
+	{ \
+		/* nothing */ \
+	})
 
-// SSE2 doesn't have an intrinsic for 64x2 equality comparison,
-// so how can we take a 32x4 comparison result and turn it into
-// a 64x2 comparison result?
-//
-// well, Intel conveniently provided an operation where we can
-// shuffle around 32-bit integers (_mm_shuffle_epi32).
-//
-// this means all we have to do is simply do the 32-bit operation,
-// shuffle the parts, and then return a bitwise AND of the result.
+/* these are the same for unsigned and signed, for obvious reasons. */
+#define VEC_SSE2_CMPEQ_8x16(sign) VEC_xSSE2_CMP(cmpeq, cmpeq, sign, 8, 16, 1, 2, , ,)
+#define VEC_SSE2_CMPEQ_16x8(sign) VEC_xSSE2_CMP(cmpeq, cmpeq, sign, 16, 8, 1, 2, , ,)
+#define VEC_SSE2_CMPEQ_32x4(sign) VEC_xSSE2_CMP(cmpeq, cmpeq, sign, 32, 4, 1, 2, , ,)
+
+/* SSE2 doesn't have an intrinsic for 64x2 equality comparison,
+ * so how can we take a 32x4 comparison result and turn it into
+ * a 64x2 comparison result?
+ *
+ * well, Intel conveniently provided an operation where we can
+ * shuffle around 32-bit integers (_mm_shuffle_epi32).
+ *
+ * this means all we have to do is simply do the 32-bit operation,
+ * shuffle the parts, and then return a bitwise AND of the result. */
 
 #define VEC_SSE2_CMPEQ_64x2(sign) \
-	do { \
+	VEC_FUNC_IMPL v##sign##int64x2 v##sign##int64x2_sse2_cmpeq(v##sign##int64x2 vec1, v##sign##int64x2 vec2) \
+	{ \
 		union v##sign##int64x2_impl_data *vec1d = (union v##sign##int64x2_impl_data *)&vec1; \
 		union v##sign##int64x2_impl_data *vec2d = (union v##sign##int64x2_impl_data *)&vec2; \
 	\
@@ -140,42 +253,60 @@
 		vec1d->sse = _mm_and_si128(vec1d->sse, vec2d->sse); \
 	\
 		return vec1d->vec; \
-	} while (0)
+	}
+
+/* ------------------------------------------------------------------------ */
+
+#define VEC_SSE2_CMPLT_8x16(sign) VEC_##sign##SSE2_CMP(cmplt, cmpgt, 8, 16, 2, 1)
+#define VEC_SSE2_CMPLT_16x8(sign) VEC_##sign##SSE2_CMP(cmplt, cmpgt, 16, 8, 2, 1)
+#define VEC_SSE2_CMPLT_32x4(sign) VEC_##sign##SSE2_CMP(cmplt, cmpgt, 32, 4, 2, 1)
+#define VEC_SSE2_CMPLT_64x2(sign) /* nothing */
+
+#define VEC_SSE2_CMPGT_8x16(sign) VEC_##sign##SSE2_CMP(cmpgt, cmpgt, 8, 16, 1, 2)
+#define VEC_SSE2_CMPGT_16x8(sign) VEC_##sign##SSE2_CMP(cmpgt, cmpgt, 16, 8, 1, 2)
+#define VEC_SSE2_CMPGT_32x4(sign) VEC_##sign##SSE2_CMP(cmpgt, cmpgt, 32, 4, 1, 2)
+#define VEC_SSE2_CMPGT_64x2(sign) /* nothing */
+
+#define VEC_SSE2_STRUCT_CMP_8x16(name, sign) v##sign##int8x16_sse2_##name
+#define VEC_SSE2_STRUCT_CMP_16x8(name, sign) v##sign##int16x8_sse2_##name
+#define VEC_SSE2_STRUCT_CMP_32x4(name, sign) v##sign##int32x4_sse2_##name
+#define VEC_SSE2_STRUCT_CMP_64x2(name, sign) NULL
+
+/* ------------------------------------------------------------------------ */
 
 #define VEC_SSE2_DEFINE_OPERATIONS_SIGN(sign, bits, size) \
-	union v##sign##int##bits##x##size##_impl_data { \
-		v##sign##int##bits##x##size vec; \
-		__m128i sse; \
-	}; \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_splat(vec_##sign##int##bits x) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data vec; \
+		vec.sse = MM_SET1_##bits(x); \
+		return vec.vec; \
+	} \
 	\
-	VEC_STATIC_ASSERT(VEC_ALIGNOF(__m128i) <= VEC_ALIGNOF(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " alignment needs to be expanded to fit intrinsic type size"); \
-	VEC_STATIC_ASSERT(sizeof(__m128i) <= sizeof(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " needs to be expanded to fit intrinsic type size"); \
-	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_load_aligned(const vec_##sign##int##bits in[size]) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_load_aligned(const vec_##sign##int##bits in[size]) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data vec; \
 		vec.sse = _mm_load_si128((const __m128i *)in); \
 		return vec.vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_load(const vec_##sign##int##bits in[size]) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_load(const vec_##sign##int##bits in[size]) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data vec; \
 		vec.sse = _mm_loadu_si128((const __m128i *)in); \
 		return vec.vec; \
 	} \
 	\
-	void v##sign##int##bits##x##size##_sse2_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_sse2_store_aligned(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
 	{ \
 		_mm_store_si128((__m128i *)out, ((union v##sign##int##bits##x##size##_impl_data *)&vec)->sse); \
 	} \
 	\
-	void v##sign##int##bits##x##size##_sse2_store(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
+	VEC_FUNC_IMPL void v##sign##int##bits##x##size##_sse2_store(v##sign##int##bits##x##size vec, vec_##sign##int##bits out[size]) \
 	{ \
 		_mm_storeu_si128((__m128i *)out, ((union v##sign##int##bits##x##size##_impl_data *)&vec)->sse); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_add(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -184,7 +315,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_sub(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -193,12 +324,12 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_mul(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		VEC_SSE2_MUL_##bits##x##size(sign); \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -207,7 +338,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -216,7 +347,7 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
 	{ \
 		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
 		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
@@ -225,34 +356,30 @@
 		return vec1d->vec; \
 	} \
 	\
-	v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_cmpeq(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
-	{ \
-		VEC_SSE2_CMPEQ_##bits##x##size(sign); \
-	} \
+	VEC_SSE2_CMPEQ_##bits##x##size(sign); \
+	VEC_SSE2_CMPLT_##bits##x##size(sign); \
+	VEC_SSE2_CMPGT_##bits##x##size(sign); \
+	\
+	VEC_SSE2_MINMAX_##sign##bits##x##size(min) \
+	VEC_SSE2_MINMAX_##sign##bits##x##size(max) \
 	\
 	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_sse2 = { \
-		v##sign##int##bits##x##size##_generic_splat, \
-		v##sign##int##bits##x##size##_sse2_load_aligned, \
-		v##sign##int##bits##x##size##_sse2_load, \
-		v##sign##int##bits##x##size##_sse2_store_aligned, \
-		v##sign##int##bits##x##size##_sse2_store, \
-		v##sign##int##bits##x##size##_sse2_add, \
-		v##sign##int##bits##x##size##_sse2_sub, \
-		v##sign##int##bits##x##size##_sse2_mul, \
-		v##sign##int##bits##x##size##_generic_div, \
-		v##sign##int##bits##x##size##_generic_avg, \
-		v##sign##int##bits##x##size##_sse2_and, \
-		v##sign##int##bits##x##size##_sse2_or, \
-		v##sign##int##bits##x##size##_sse2_xor, \
-		v##sign##int##bits##x##size##_generic_not, \
-		v##sign##int##bits##x##size##_generic_lshift, \
-		v##sign##int##bits##x##size##_generic_rshift, \
-		v##sign##int##bits##x##size##_generic_lrshift, \
-		v##sign##int##bits##x##size##_generic_cmplt, \
-		v##sign##int##bits##x##size##_generic_cmple, \
-		v##sign##int##bits##x##size##_sse2_cmpeq, \
-		v##sign##int##bits##x##size##_generic_cmpge, \
-		v##sign##int##bits##x##size##_generic_cmpgt, \
+		.splat = v##sign##int##bits##x##size##_sse2_splat, \
+		.load_aligned = v##sign##int##bits##x##size##_sse2_load_aligned, \
+		.load = v##sign##int##bits##x##size##_sse2_load, \
+		.store_aligned = v##sign##int##bits##x##size##_sse2_store_aligned, \
+		.store = v##sign##int##bits##x##size##_sse2_store, \
+		.add = v##sign##int##bits##x##size##_sse2_add, \
+		.sub = v##sign##int##bits##x##size##_sse2_sub, \
+		.mul = v##sign##int##bits##x##size##_sse2_mul, \
+		.band = v##sign##int##bits##x##size##_sse2_and, \
+		.bor = v##sign##int##bits##x##size##_sse2_or, \
+		.bxor = v##sign##int##bits##x##size##_sse2_xor, \
+		.cmpeq = v##sign##int##bits##x##size##_sse2_cmpeq, \
+		.cmplt = VEC_SSE2_STRUCT_CMP_##bits##x##size(cmplt, sign), \
+		.cmpgt = VEC_SSE2_STRUCT_CMP_##bits##x##size(cmpgt, sign), \
+		.min = VEC_SSE2_STRUCT_MINMAX_##bits##x##size(min, sign), \
+		.max = VEC_SSE2_STRUCT_MINMAX_##bits##x##size(max, sign), \
 	};
 
 #define VEC_SSE2_DEFINE_OPERATIONS(bits, size) \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/impl/x86/sse3.c	Thu Apr 24 00:54:02 2025 -0400
@@ -0,0 +1,58 @@
+/**
+ * vec - a tiny SIMD vector library in C99
+ * 
+ * Copyright (c) 2024 Paper
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+**/
+
+#include "vec/impl/x86/sse3.h"
+
+#include <pmmintrin.h>
+
+/* SSE3 has a slightly more optimized load function */
+
+#define VEC_SSE2_DEFINE_OPERATIONS_SIGN(sign, bits, size) \
+	union v##sign##int##bits##x##size##_impl_data { \
+		v##sign##int##bits##x##size vec; \
+		__m128i sse; \
+	}; \
+	\
+	VEC_STATIC_ASSERT(VEC_ALIGNOF(__m128i) <= VEC_ALIGNOF(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " alignment needs to be expanded to fit intrinsic type size"); \
+	VEC_STATIC_ASSERT(sizeof(__m128i) <= sizeof(v##sign##int##bits##x##size), "vec: v" #sign "int" #bits "x" #size " needs to be expanded to fit intrinsic type size"); \
+	\
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse2_load(const vec_##sign##int##bits in[size]) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data vec; \
+		vec.sse = _mm_lddqu_si128((const __m128i *)in); \
+		return vec.vec; \
+	} \
+	\
+	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_sse3 = { \
+		.load = v##sign##int##bits##x##size##_sse2_load, \
+	};
+
+#define VEC_SSE2_DEFINE_OPERATIONS(bits, size) \
+	VEC_SSE2_DEFINE_OPERATIONS_SIGN(u, bits, size) \
+	VEC_SSE2_DEFINE_OPERATIONS_SIGN( , bits, size)
+
+VEC_SSE2_DEFINE_OPERATIONS(8, 16)
+VEC_SSE2_DEFINE_OPERATIONS(16, 8)
+VEC_SSE2_DEFINE_OPERATIONS(32, 4)
+VEC_SSE2_DEFINE_OPERATIONS(64, 2)
--- a/src/impl/x86/sse41.c	Mon Nov 25 00:33:02 2024 -0500
+++ b/src/impl/x86/sse41.c	Thu Apr 24 00:54:02 2025 -0400
@@ -23,54 +23,138 @@
 **/
 
 #include "vec/impl/x86/sse41.h"
-#include "vec/impl/x86/sse2.h"
 #include "vec/impl/generic.h"
 
 #include <immintrin.h>
 
-// SSE 4.1 provides a real _mm_mullo_epi32
-#define VEC_SSE41_DEFINE_OPERATIONS(sign) \
-	union v##sign##int32x4_impl_data { \
-		v##sign##int32x4 vec; \
-		__m128i sse; \
-	}; \
+/* ------------------------------------------------------------------------ */
+
+#define VEC_SSE41_MINMAX_TEMPLATE(SIGN, BITS, SIZE, INTLSIGN, OP) \
+	VEC_FUNC_IMPL v##SIGN##int##BITS##x##SIZE v##SIGN##int##BITS##x##SIZE##_sse41_##OP(v##SIGN##int##BITS##x##SIZE vec1, v##SIGN##int##BITS##x##SIZE vec2) \
+	{ \
+		union v##SIGN##int##BITS##x##SIZE##_impl_data *vec1d = (union v##SIGN##int##BITS##x##SIZE##_impl_data *)&vec1; \
+		union v##SIGN##int##BITS##x##SIZE##_impl_data *vec2d = (union v##SIGN##int##BITS##x##SIZE##_impl_data *)&vec2; \
+	\
+		vec1d->sse = _mm_##OP##_ep##INTLSIGN##BITS(vec1d->sse, vec2d->sse); \
 	\
-	VEC_STATIC_ASSERT(VEC_ALIGNOF(__m128i) <= VEC_ALIGNOF(v##sign##int32x4), "vec: v" #sign "int32x4 alignment needs to be expanded to fit intrinsic type size"); \
-	VEC_STATIC_ASSERT(sizeof(__m128i) <= sizeof(v##sign##int32x4), "vec: v" #sign "int32x4 needs to be expanded to fit intrinsic type size"); \
-	\
-	static v##sign##int32x4 v##sign##int32x4_sse41_mul(v##sign##int32x4 vec1, v##sign##int32x4 vec2) \
+		return vec1d->vec; \
+	}
+
+#define VEC_SSE41_MINMAX_8x16(OP)  VEC_SSE41_MINMAX_TEMPLATE( , 8, 16, i, OP)
+#define VEC_SSE41_MINMAX_u8x16(OP) VEC_SSE41_MINMAX_TEMPLATE(u, 8, 16, u, OP)
+#define VEC_SSE41_MINMAX_16x8(OP)  VEC_SSE41_MINMAX_TEMPLATE( , 16, 8, i, OP)
+#define VEC_SSE41_MINMAX_u16x8(OP) VEC_SSE41_MINMAX_TEMPLATE(u, 16, 8, u, OP)
+#define VEC_SSE41_MINMAX_32x4(OP)  VEC_SSE41_MINMAX_TEMPLATE( , 32, 4, i, OP)
+#define VEC_SSE41_MINMAX_u32x4(OP) VEC_SSE41_MINMAX_TEMPLATE(u, 32, 4, u, OP)
+#define VEC_SSE41_MINMAX_64x2(OP)  /* nothing */
+#define VEC_SSE41_MINMAX_u64x2(OP) /* nothing */
+
+#define VEC_SSE41_STRUCT_MINMAX_8x16(OP, SIGN) v##SIGN##int8x16_sse41_##OP
+#define VEC_SSE41_STRUCT_MINMAX_16x8(OP, SIGN) v##SIGN##int16x8_sse41_##OP
+#define VEC_SSE41_STRUCT_MINMAX_32x4(OP, SIGN) v##SIGN##int32x4_sse41_##OP
+#define VEC_SSE41_STRUCT_MINMAX_64x2(OP, SIGN) NULL
+
+/* ------------------------------------------------------------------------ */
+/* multiplication */
+
+#define VEC_SSE41_MUL_8x16(sign)
+#define VEC_SSE41_MUL_16x8(sign)
+#define VEC_SSE41_MUL_32x4(sign) \
+	VEC_FUNC_IMPL v##sign##int32x4 v##sign##int32x4_sse41_mul(v##sign##int32x4 vec1, v##sign##int32x4 vec2) \
 	{ \
 		union v##sign##int32x4_impl_data *vec1d = (union v##sign##int32x4_impl_data *)&vec1; \
 		union v##sign##int32x4_impl_data *vec2d = (union v##sign##int32x4_impl_data *)&vec2; \
 	\
 		vec1d->sse = _mm_mullo_epi32(vec1d->sse, vec2d->sse); \
 		return vec1d->vec; \
-	} \
+	}
+#define VEC_SSE41_MUL_64x2(sign)
+
+#define VEC_SSE41_STRUCT_MUL_8x16(SIGN) NULL
+#define VEC_SSE41_STRUCT_MUL_16x8(SIGN) NULL
+#define VEC_SSE41_STRUCT_MUL_32x4(SIGN) v##SIGN##int32x4_sse41_mul
+#define VEC_SSE41_STRUCT_MUL_64x2(SIGN) NULL
+
+/* ------------------------------------------------------------------------ */
+/* comparison */
+
+#define MM_SET1_64(x) _mm_set1_epi64x(x)
+
+/* helper funcs */
+#define VEC_xSSE41_CMP(name, op, sign, bits, size, first, second, VARS, TRANS1, TRANS2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse41_##name(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+		VARS \
+	\
+		TRANS1 \
+	\
+		vec1d->sse = _mm_##op##_epi##bits(vec##first##d->sse, vec##second##d->sse); \
+	\
+		TRANS2 \
 	\
-	const v##sign##int32x4_impl v##sign##int32x4_impl_sse41 = { \
-		v##sign##int32x4_generic_splat, \
-		v##sign##int32x4_sse2_load_aligned, \
-		v##sign##int32x4_sse2_load, \
-		v##sign##int32x4_sse2_store_aligned, \
-		v##sign##int32x4_sse2_store, \
-		v##sign##int32x4_sse2_add, \
-		v##sign##int32x4_sse2_sub, \
-		v##sign##int32x4_sse41_mul, \
-		v##sign##int32x4_generic_div, \
-		v##sign##int32x4_generic_avg, \
-		v##sign##int32x4_sse2_and, \
-		v##sign##int32x4_sse2_or, \
-		v##sign##int32x4_sse2_xor, \
-		v##sign##int32x4_generic_not, \
-		v##sign##int32x4_generic_lshift, \
-		v##sign##int32x4_generic_rshift, \
-		v##sign##int32x4_generic_lrshift, \
-		v##sign##int32x4_generic_cmplt, \
-		v##sign##int32x4_generic_cmple, \
-		v##sign##int32x4_sse2_cmpeq, \
-		v##sign##int32x4_generic_cmpge, \
-		v##sign##int32x4_generic_cmpgt, \
+		return vec1d->vec; \
+	}
+
+#define VEC_SSE41_CMP(name, op, bits, size, first, second) \
+	VEC_xSSE41_CMP(name, op, /* nothing */, bits, size, first, second, /* nothing */, /* nothing */, /* nothing */)
+
+#define VEC_uSSE41_CMP(name, op, bits, size, first, second) \
+	VEC_xSSE41_CMP(name, op, u, bits, size, first, second, \
+		__m128i xor_val = MM_SET1_##bits(UINT64_C(1) << (bits - 1)); \
+	, { \
+		vec1d->sse = _mm_xor_si128(vec1d->sse, xor_val); \
+		vec2d->sse = _mm_xor_si128(vec2d->sse, xor_val); \
+	}, \
+	{ \
+		/* nothing */ \
+	})
+
+/* these are the same for unsigned and signed, for obvious reasons. */
+#define VEC_SSE41_CMPEQ_8x16(sign) /* nothing */
+#define VEC_SSE41_CMPEQ_16x8(sign) /* nothing */
+#define VEC_SSE41_CMPEQ_32x4(sign) /* nothing */
+#define VEC_SSE41_CMPEQ_64x2(sign) VEC_xSSE41_CMP(cmpeq, cmpeq, sign, 64, 2, 1, 2, , ,)
+
+/* ------------------------------------------------------------------------ */
+
+#define VEC_SSE41_STRUCT_CMP_8x16(name, sign) NULL
+#define VEC_SSE41_STRUCT_CMP_16x8(name, sign) NULL
+#define VEC_SSE41_STRUCT_CMP_32x4(name, sign) NULL
+#define VEC_SSE41_STRUCT_CMP_64x2(name, sign) v##sign##int64x2_sse41_##name
+
+/* ------------------------------------------------------------------------ */
+
+// SSE 4.1 provides a real _mm_mullo_epi32
+#define VEC_SSE41_DEFINE_OPERATIONS_SIGN(sign, bits, size) \
+	union v##sign##int##bits##x##size##_impl_data { \
+		v##sign##int##bits##x##size vec; \
+		__m128i sse; \
+	}; \
+	\
+	VEC_STATIC_ASSERT(VEC_ALIGNOF(__m128i) <= VEC_ALIGNOF(v##sign##int##bits##x##size), "vec: v" #sign "int32x4 alignment needs to be expanded to fit intrinsic type size"); \
+	VEC_STATIC_ASSERT(sizeof(__m128i) <= sizeof(v##sign##int##bits##x##size), "vec: v" #sign "int32x4 needs to be expanded to fit intrinsic type size"); \
+	\
+	VEC_SSE41_MUL_##bits##x##size(sign) \
+	\
+	VEC_SSE41_MINMAX_##sign##bits##x##size(min) \
+	VEC_SSE41_MINMAX_##sign##bits##x##size(max) \
+	\
+	VEC_SSE41_CMPEQ_##bits##x##size(sign); \
+	\
+	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_sse41 = { \
+		.mul = VEC_SSE41_STRUCT_MUL_##bits##x##size(sign), \
+		.min = VEC_SSE41_STRUCT_MINMAX_##bits##x##size(min, sign), \
+		.max = VEC_SSE41_STRUCT_MINMAX_##bits##x##size(max, sign), \
+		.cmpeq = VEC_SSE41_STRUCT_CMP_##bits##x##size(cmpeq, sign), \
 	};
 
-VEC_SSE41_DEFINE_OPERATIONS()
-VEC_SSE41_DEFINE_OPERATIONS(u)
+#define VEC_SSE41_DEFINE_OPERATIONS(bits, size) \
+	VEC_SSE41_DEFINE_OPERATIONS_SIGN(u, bits, size) \
+	VEC_SSE41_DEFINE_OPERATIONS_SIGN( , bits, size)
+
+VEC_SSE41_DEFINE_OPERATIONS(8, 16)
+VEC_SSE41_DEFINE_OPERATIONS(16, 8)
+VEC_SSE41_DEFINE_OPERATIONS(32, 4)
+VEC_SSE41_DEFINE_OPERATIONS(64, 2)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/impl/x86/sse42.c	Thu Apr 24 00:54:02 2025 -0400
@@ -0,0 +1,100 @@
+/**
+ * vec - a tiny SIMD vector library in C99
+ * 
+ * Copyright (c) 2024 Paper
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+**/
+
+#include "vec/impl/x86/sse42.h"
+#include "vec/impl/generic.h"
+
+#include <immintrin.h>
+
+/* ------------------------------------------------------------------------ */
+/* comparison */
+
+#define MM_SET1_64(x) _mm_set1_epi64x(x)
+
+/* helper funcs */
+#define VEC_xSSE42_CMP(name, op, sign, bits, size, first, second, VARS, TRANS1, TRANS2) \
+	VEC_FUNC_IMPL v##sign##int##bits##x##size v##sign##int##bits##x##size##_sse42_##name(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2) \
+	{ \
+		union v##sign##int##bits##x##size##_impl_data *vec1d = (union v##sign##int##bits##x##size##_impl_data *)&vec1; \
+		union v##sign##int##bits##x##size##_impl_data *vec2d = (union v##sign##int##bits##x##size##_impl_data *)&vec2; \
+		VARS \
+	\
+		TRANS1 \
+	\
+		vec1d->sse = _mm_##op##_epi##bits(vec##first##d->sse, vec##second##d->sse); \
+	\
+		TRANS2 \
+	\
+		return vec1d->vec; \
+	}
+
+#define VEC_SSE42_CMP(name, op, bits, size, first, second) \
+	VEC_xSSE42_CMP(name, op, /* nothing */, bits, size, first, second, /* nothing */, /* nothing */, /* nothing */)
+
+#define VEC_uSSE42_CMP(name, op, bits, size, first, second) \
+	VEC_xSSE42_CMP(name, op, u, bits, size, first, second, \
+		__m128i xor_val = MM_SET1_##bits(UINT64_C(1) << (bits - 1)); \
+	, { \
+		vec1d->sse = _mm_xor_si128(vec1d->sse, xor_val); \
+		vec2d->sse = _mm_xor_si128(vec2d->sse, xor_val); \
+	}, \
+	{ \
+		/* nothing */ \
+	})
+
+/* these are the same for unsigned and signed, for obvious reasons. */
+#define VEC_SSE42_CMPEQ_64x2(sign) VEC_xSSE42_CMP(cmpeq, cmpeq, sign, 64, 2, 1, 2, , ,)
+
+/* ------------------------------------------------------------------------ */
+
+#define VEC_SSE42_CMPLT_64x2(sign) VEC_##sign##SSE42_CMP(cmplt, cmpgt, 64, 2, 2, 1)
+#define VEC_SSE42_CMPGT_64x2(sign) VEC_##sign##SSE42_CMP(cmpgt, cmpgt, 64, 2, 1, 2)
+
+#define VEC_SSE42_STRUCT_CMP_64x2(name, sign) v##sign##int64x2_sse42_##name
+
+/* ------------------------------------------------------------------------ */
+
+// SSE 4.1 provides a real _mm_mullo_epi32
+#define VEC_SSE42_DEFINE_OPERATIONS_SIGN(sign, bits, size) \
+	union v##sign##int##bits##x##size##_impl_data { \
+		v##sign##int##bits##x##size vec; \
+		__m128i sse; \
+	}; \
+	\
+	VEC_STATIC_ASSERT(VEC_ALIGNOF(__m128i) <= VEC_ALIGNOF(v##sign##int##bits##x##size), "vec: v" #sign "int32x4 alignment needs to be expanded to fit intrinsic type size"); \
+	VEC_STATIC_ASSERT(sizeof(__m128i) <= sizeof(v##sign##int##bits##x##size), "vec: v" #sign "int32x4 needs to be expanded to fit intrinsic type size"); \
+	\
+	VEC_SSE42_CMPLT_##bits##x##size(sign); \
+	VEC_SSE42_CMPGT_##bits##x##size(sign); \
+	\
+	const v##sign##int##bits##x##size##_impl v##sign##int##bits##x##size##_impl_sse42 = { \
+		.cmplt = VEC_SSE42_STRUCT_CMP_##bits##x##size(cmplt, sign), \
+		.cmpgt = VEC_SSE42_STRUCT_CMP_##bits##x##size(cmpgt, sign), \
+	};
+
+#define VEC_SSE42_DEFINE_OPERATIONS(bits, size) \
+	VEC_SSE42_DEFINE_OPERATIONS_SIGN(u, bits, size) \
+	VEC_SSE42_DEFINE_OPERATIONS_SIGN( , bits, size)
+
+VEC_SSE42_DEFINE_OPERATIONS(64, 2)
--- a/src/vec.c	Mon Nov 25 00:33:02 2024 -0500
+++ b/src/vec.c	Thu Apr 24 00:54:02 2025 -0400
@@ -32,15 +32,27 @@
 #ifdef VEC_COMPILER_HAS_SSE2
 # include "vec/impl/x86/sse2.h"
 #endif
+#ifdef VEC_COMPILER_HAS_SSE3
+# include "vec/impl/x86/sse3.h"
+#endif
 #ifdef VEC_COMPILER_HAS_SSE41
 # include "vec/impl/x86/sse41.h"
 #endif
+#ifdef VEC_COMPILER_HAS_SSE42
+# include "vec/impl/x86/sse42.h"
+#endif
 #ifdef VEC_COMPILER_HAS_AVX2
 # include "vec/impl/x86/avx2.h"
 #endif
 #ifdef VEC_COMPILER_HAS_AVX512F
 # include "vec/impl/x86/avx512f.h"
 #endif
+#ifdef VEC_COMPILER_HAS_AVX512BW
+# include "vec/impl/x86/avx512bw.h"
+#endif
+#ifdef VEC_COMPILER_HAS_AVX512DQ
+# include "vec/impl/x86/avx512dq.h"
+#endif
 #ifdef VEC_COMPILER_HAS_ALTIVEC
 # include "vec/impl/ppc/altivec.h"
 #endif
@@ -59,166 +71,284 @@
 extern inline vec_uintmax vec_uavg(vec_uintmax x, vec_uintmax y);
 
 // 16-bit
-const vint8x2_impl   *vint8x2_impl_cpu   = &vint8x2_impl_generic;
-const vuint8x2_impl  *vuint8x2_impl_cpu  = &vuint8x2_impl_generic;
+vint8x2_impl   vint8x2_impl_cpu   = {0};
+vuint8x2_impl  vuint8x2_impl_cpu  = {0};
 
 // 32-bit
-const vint8x4_impl   *vint8x4_impl_cpu   = &vint8x4_impl_generic;
-const vuint8x4_impl  *vuint8x4_impl_cpu  = &vuint8x4_impl_generic;
-const vint16x2_impl  *vint16x2_impl_cpu  = &vint16x2_impl_generic;
-const vuint16x2_impl *vuint16x2_impl_cpu = &vuint16x2_impl_generic;
+vint8x4_impl   vint8x4_impl_cpu   = {0};
+vuint8x4_impl  vuint8x4_impl_cpu  = {0};
+vint16x2_impl  vint16x2_impl_cpu  = {0};
+vuint16x2_impl vuint16x2_impl_cpu = {0};
 
 // 64-bit
-const vint8x8_impl   *vint8x8_impl_cpu   = &vint8x8_impl_generic;
-const vuint8x8_impl  *vuint8x8_impl_cpu  = &vuint8x8_impl_generic;
-const vint16x4_impl  *vint16x4_impl_cpu  = &vint16x4_impl_generic;
-const vuint16x4_impl *vuint16x4_impl_cpu = &vuint16x4_impl_generic;
-const vint32x2_impl  *vint32x2_impl_cpu  = &vint32x2_impl_generic;
-const vuint32x2_impl *vuint32x2_impl_cpu = &vuint32x2_impl_generic;
+vint8x8_impl   vint8x8_impl_cpu   = {0};
+vuint8x8_impl  vuint8x8_impl_cpu  = {0};
+vint16x4_impl  vint16x4_impl_cpu  = {0};
+vuint16x4_impl vuint16x4_impl_cpu = {0};
+vint32x2_impl  vint32x2_impl_cpu  = {0};
+vuint32x2_impl vuint32x2_impl_cpu = {0};
 
 // 128-bit
-const vint8x16_impl  *vint8x16_impl_cpu  = &vint8x16_impl_generic;
-const vuint8x16_impl *vuint8x16_impl_cpu = &vuint8x16_impl_generic;
-const vint16x8_impl  *vint16x8_impl_cpu  = &vint16x8_impl_generic;
-const vuint16x8_impl *vuint16x8_impl_cpu = &vuint16x8_impl_generic;
-const vint32x4_impl  *vint32x4_impl_cpu  = &vint32x4_impl_generic;
-const vuint32x4_impl *vuint32x4_impl_cpu = &vuint32x4_impl_generic;
-const vint64x2_impl  *vint64x2_impl_cpu  = &vint64x2_impl_generic;
-const vuint64x2_impl *vuint64x2_impl_cpu = &vuint64x2_impl_generic;
+vint8x16_impl  vint8x16_impl_cpu  = {0};
+vuint8x16_impl vuint8x16_impl_cpu = {0};
+vint16x8_impl  vint16x8_impl_cpu  = {0};
+vuint16x8_impl vuint16x8_impl_cpu = {0};
+vint32x4_impl  vint32x4_impl_cpu  = {0};
+vuint32x4_impl vuint32x4_impl_cpu = {0};
+vint64x2_impl  vint64x2_impl_cpu  = {0};
+vuint64x2_impl vuint64x2_impl_cpu = {0};
 
 // 256-bit
-const vint8x32_impl   *vint8x32_impl_cpu   = &vint8x32_impl_generic;
-const vuint8x32_impl  *vuint8x32_impl_cpu  = &vuint8x32_impl_generic;
-const vint16x16_impl  *vint16x16_impl_cpu  = &vint16x16_impl_generic;
-const vuint16x16_impl *vuint16x16_impl_cpu = &vuint16x16_impl_generic;
-const vint32x8_impl   *vint32x8_impl_cpu   = &vint32x8_impl_generic;
-const vuint32x8_impl  *vuint32x8_impl_cpu  = &vuint32x8_impl_generic;
-const vint64x4_impl   *vint64x4_impl_cpu   = &vint64x4_impl_generic;
-const vuint64x4_impl  *vuint64x4_impl_cpu  = &vuint64x4_impl_generic;
+vint8x32_impl   vint8x32_impl_cpu   = {0};
+vuint8x32_impl  vuint8x32_impl_cpu  = {0};
+vint16x16_impl  vint16x16_impl_cpu  = {0};
+vuint16x16_impl vuint16x16_impl_cpu = {0};
+vint32x8_impl   vint32x8_impl_cpu   = {0};
+vuint32x8_impl  vuint32x8_impl_cpu  = {0};
+vint64x4_impl   vint64x4_impl_cpu   = {0};
+vuint64x4_impl  vuint64x4_impl_cpu  = {0};
 
 // 512-bit
-const vint8x64_impl   *vint8x64_impl_cpu   = &vint8x64_impl_generic;
-const vuint8x64_impl  *vuint8x64_impl_cpu  = &vuint8x64_impl_generic;
-const vint16x32_impl  *vint16x32_impl_cpu  = &vint16x32_impl_generic;
-const vuint16x32_impl *vuint16x32_impl_cpu = &vuint16x32_impl_generic;
-const vint32x16_impl  *vint32x16_impl_cpu  = &vint32x16_impl_generic;
-const vuint32x16_impl *vuint32x16_impl_cpu = &vuint32x16_impl_generic;
-const vint64x8_impl   *vint64x8_impl_cpu   = &vint64x8_impl_generic;
-const vuint64x8_impl  *vuint64x8_impl_cpu  = &vuint64x8_impl_generic;
+vint8x64_impl   vint8x64_impl_cpu   = {0};
+vuint8x64_impl  vuint8x64_impl_cpu  = {0};
+vint16x32_impl  vint16x32_impl_cpu  = {0};
+vuint16x32_impl vuint16x32_impl_cpu = {0};
+vint32x16_impl  vint32x16_impl_cpu  = {0};
+vuint32x16_impl vuint32x16_impl_cpu = {0};
+vint64x8_impl   vint64x8_impl_cpu   = {0};
+vuint64x8_impl  vuint64x8_impl_cpu  = {0};
 
 static int vec_init_spinner = 0;
 
+#define FILL_GIVEN_FUNC_PTR(cpu, impl, func) \
+	do { \
+		if (!(cpu).func && (impl).func) \
+			(cpu).func = (impl).func; \
+	} while (0)
+
+#define FILL_GIVEN_FUNC_PTRS_EX(cpu, impl) \
+	do { \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, splat); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, load_aligned); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, load); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, store_aligned); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, store); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, add); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, sub); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, mul); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, div); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, avg); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, band); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, bor); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, bxor); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, lshift); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, rshift); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, lrshift); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, cmplt); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, cmple); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, cmpeq); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, cmpge); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, cmpgt); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, min); \
+		FILL_GIVEN_FUNC_PTR(cpu, impl, max); \
+	} while (0)
+
+#define FILL_GIVEN_FUNC_PTRS(sign, bits, size, impl) \
+	FILL_GIVEN_FUNC_PTRS_EX(v##sign##int##bits##x##size##_impl_cpu, v##sign##int##bits##x##size##_impl_##impl)
+
 // returns 0 or a negative error code on failure
 int vec_init(void)
 {
 	// This function is NOT thread safe. However, once vec
 	// is initialized, all of the vector functions are thread-safe.
-	//
-	// In fact, it's possible to use vec without calling
-	// vec_init() at all, but it would be completely useless since
-	// it would just use a generic implementation without any
-	// vectorization whatsoever (unless maybe the compiler is
-	// smart enough to optimize it into vectors)
 
 	if (vec_init_spinner)
 		return 0; // already initialized, do nothing
 
 	vec_uint32 cpu = vec_get_CPU_features();
 
-#ifdef VEC_COMPILER_HAS_ALTIVEC
-	if (cpu & VEC_CPU_HAS_ALTIVEC) {
-		vint8x16_impl_cpu  = &vint8x16_impl_altivec;
-		vuint8x16_impl_cpu = &vuint8x16_impl_altivec;
-		vint16x8_impl_cpu  = &vint16x8_impl_altivec;
-		vuint16x8_impl_cpu = &vuint16x8_impl_altivec;
-		vint32x4_impl_cpu  = &vint32x4_impl_altivec;
-		vuint32x4_impl_cpu = &vuint32x4_impl_altivec;
-#ifdef VEC_COMPILER_HAS_ALTIVEC_VSX
-		if (cpu & VEC_CPU_HAS_ALTIVEC_VSX) {
-			vint64x2_impl_cpu  = &vint64x2_impl_altivec;
-			vuint64x2_impl_cpu = &vuint64x2_impl_altivec;
-		}
+	/* Okay, this might be a little confusing:
+	 * The way we do this is because of x86. For weird reasons,
+	 * Intel decided to extend their prior CPU extensions to
+	 * where SSE4.1 has some extended features of SSE2, AVX2
+	 * has some extended features that should've been in SSE
+	 * in general, etc.
+	 *
+	 * For this, I've just decided to keep the function
+	 * definitions private, and fill in as we go, with newer
+	 * intrinsics preferred. Others are arbitrary and are
+	 * mutually exclusive (i.e. Altivec vs NEON). This is simply
+	 * the easiest way to go about it :) */
+
+	/* --- 512-bit */
+#ifdef VEC_COMPILER_HAS_AVX512DQ
+	if (cpu & VEC_CPU_HAS_AVX512DQ) {
+		/* these give us native multiply instructions */
+		FILL_GIVEN_FUNC_PTRS( , 64, 8, avx512dq);
+		FILL_GIVEN_FUNC_PTRS(u, 64, 8, avx512dq);
+	}
 #endif
+#ifdef VEC_COMPILER_HAS_AVX512BW
+	if (cpu & VEC_CPU_HAS_AVX512BW) {
+		FILL_GIVEN_FUNC_PTRS( , 8,  64, avx512bw);
+		FILL_GIVEN_FUNC_PTRS(u, 8,  64, avx512bw);
+		FILL_GIVEN_FUNC_PTRS( , 16, 32, avx512bw);
+		FILL_GIVEN_FUNC_PTRS(u, 16, 32, avx512bw);
 	}
 #endif
 #ifdef VEC_COMPILER_HAS_AVX512F
 	if (cpu & VEC_CPU_HAS_AVX512F) {
-		vint8x64_impl_cpu  = &vint8x64_impl_avx512f;
-		vuint8x64_impl_cpu = &vuint8x64_impl_avx512f;
-		vint16x32_impl_cpu  = &vint16x32_impl_avx512f;
-		vuint16x32_impl_cpu = &vuint16x32_impl_avx512f;
-		vint32x16_impl_cpu  = &vint32x16_impl_avx512f;
-		vuint32x16_impl_cpu = &vuint32x16_impl_avx512f;
-		vint64x8_impl_cpu  = &vint64x8_impl_avx512f;
-		vuint64x8_impl_cpu = &vuint64x8_impl_avx512f;
+		FILL_GIVEN_FUNC_PTRS( , 32, 16, avx512f);
+		FILL_GIVEN_FUNC_PTRS(u, 32, 16, avx512f);
+		FILL_GIVEN_FUNC_PTRS( , 64, 8,  avx512f);
+		FILL_GIVEN_FUNC_PTRS(u, 64, 8,  avx512f);
+	}
+#endif
+
+	/* --- 256-bit */
+#ifdef VEC_COMPILER_HAS_AVX2
+	if (cpu & VEC_CPU_HAS_AVX2) {
+		FILL_GIVEN_FUNC_PTRS( , 8, 32,  avx2);
+		FILL_GIVEN_FUNC_PTRS(u, 8, 32,  avx2);
+		FILL_GIVEN_FUNC_PTRS( , 16, 16, avx2);
+		FILL_GIVEN_FUNC_PTRS(u, 16, 16, avx2);
+		FILL_GIVEN_FUNC_PTRS( , 32, 8,  avx2);
+		FILL_GIVEN_FUNC_PTRS(u, 32, 8,  avx2);
+		FILL_GIVEN_FUNC_PTRS( , 64, 4,  avx2);
+		FILL_GIVEN_FUNC_PTRS(u, 64, 4,  avx2);
 	}
 #endif
-#ifdef VEC_COMPILER_HAS_AVX2
-	if (cpu & VEC_CPU_HAS_AVX2) {
-		vint8x32_impl_cpu  = &vint8x32_impl_avx2;
-		vuint8x32_impl_cpu = &vuint8x32_impl_avx2;
-		vint16x16_impl_cpu  = &vint16x16_impl_avx2;
-		vuint16x16_impl_cpu = &vuint16x16_impl_avx2;
-		vint32x8_impl_cpu  = &vint32x8_impl_avx2;
-		vuint32x8_impl_cpu = &vuint32x8_impl_avx2;
-		vint64x4_impl_cpu  = &vint64x4_impl_avx2;
-		vuint64x4_impl_cpu = &vuint64x4_impl_avx2;
+
+	/* --- 128-bit */
+#ifdef VEC_COMPILER_HAS_SSE42
+	if (cpu & VEC_CPU_HAS_SSE41) {
+		FILL_GIVEN_FUNC_PTRS( , 64, 2, sse42);
+		FILL_GIVEN_FUNC_PTRS(u, 64, 2, sse42);
+	}
+#endif
+#ifdef VEC_COMPILER_HAS_SSE41
+	if (cpu & VEC_CPU_HAS_SSE41) {
+		FILL_GIVEN_FUNC_PTRS( , 8, 16, sse41);
+		FILL_GIVEN_FUNC_PTRS(u, 8, 16, sse41);
+		FILL_GIVEN_FUNC_PTRS( , 16, 8, sse41);
+		FILL_GIVEN_FUNC_PTRS(u, 16, 8, sse41);
+		FILL_GIVEN_FUNC_PTRS( , 32, 4, sse41);
+		FILL_GIVEN_FUNC_PTRS(u, 32, 4, sse41);
+		FILL_GIVEN_FUNC_PTRS( , 64, 2, sse41);
+		FILL_GIVEN_FUNC_PTRS(u, 64, 2, sse41);
+	}
+#endif
+#ifdef VEC_COMPILER_HAS_SSE3
+	if (cpu & VEC_CPU_HAS_SSE3) {
+		FILL_GIVEN_FUNC_PTRS( , 8, 16, sse3);
+		FILL_GIVEN_FUNC_PTRS(u, 8, 16, sse3);
+		FILL_GIVEN_FUNC_PTRS( , 16, 8, sse3);
+		FILL_GIVEN_FUNC_PTRS(u, 16, 8, sse3);
+		FILL_GIVEN_FUNC_PTRS( , 32, 4, sse3);
+		FILL_GIVEN_FUNC_PTRS(u, 32, 4, sse3);
+		FILL_GIVEN_FUNC_PTRS( , 64, 2, sse3);
+		FILL_GIVEN_FUNC_PTRS(u, 64, 2, sse3);
 	}
 #endif
 #ifdef VEC_COMPILER_HAS_SSE2
 	if (cpu & VEC_CPU_HAS_SSE2) {
-		vint8x16_impl_cpu  = &vint8x16_impl_sse2;
-		vuint8x16_impl_cpu = &vuint8x16_impl_sse2;
-		vint16x8_impl_cpu  = &vint16x8_impl_sse2;
-		vuint16x8_impl_cpu = &vuint16x8_impl_sse2;
-# ifdef VEC_COMPILER_HAS_SSE41
-		if (cpu & VEC_CPU_HAS_SSE41) {
-			vint32x4_impl_cpu  = &vint32x4_impl_sse41;
-			vuint32x4_impl_cpu = &vuint32x4_impl_sse41;
-		} else
-# endif
-		{
-			vint32x4_impl_cpu  = &vint32x4_impl_sse2;
-			vuint32x4_impl_cpu = &vuint32x4_impl_sse2;
-		}
-		vint64x2_impl_cpu  = &vint64x2_impl_sse2;
-		vuint64x2_impl_cpu = &vuint64x2_impl_sse2;
+		FILL_GIVEN_FUNC_PTRS( , 8, 16, sse2);
+		FILL_GIVEN_FUNC_PTRS(u, 8, 16, sse2);
+		FILL_GIVEN_FUNC_PTRS( , 16, 8, sse2);
+		FILL_GIVEN_FUNC_PTRS(u, 16, 8, sse2);
+		FILL_GIVEN_FUNC_PTRS( , 32, 4, sse2);
+		FILL_GIVEN_FUNC_PTRS(u, 32, 4, sse2);
+		FILL_GIVEN_FUNC_PTRS( , 64, 2, sse2);
+		FILL_GIVEN_FUNC_PTRS(u, 64, 2, sse2);
 	}
 #endif
+#ifdef VEC_COMPILER_HAS_NEON
+	if (cpu & VEC_CPU_HAS_NEON) {
+		FILL_GIVEN_FUNC_PTRS( , 8, 16, neon);
+		FILL_GIVEN_FUNC_PTRS(u, 8, 16, neon);
+		FILL_GIVEN_FUNC_PTRS( , 16, 8, neon);
+		FILL_GIVEN_FUNC_PTRS(u, 16, 8, neon);
+		FILL_GIVEN_FUNC_PTRS( , 32, 4, neon);
+		FILL_GIVEN_FUNC_PTRS(u, 32, 4, neon);
+		FILL_GIVEN_FUNC_PTRS( , 64, 2, neon);
+		FILL_GIVEN_FUNC_PTRS(u, 64, 2, neon);
+	}
+#endif
+#ifdef VEC_COMPILER_HAS_ALTIVEC
+	if (cpu & VEC_CPU_HAS_ALTIVEC) {
+		FILL_GIVEN_FUNC_PTRS( , 8, 16, altivec);
+		FILL_GIVEN_FUNC_PTRS(u, 8, 16, altivec);
+		FILL_GIVEN_FUNC_PTRS( , 16, 8, altivec);
+		FILL_GIVEN_FUNC_PTRS(u, 16, 8, altivec);
+		FILL_GIVEN_FUNC_PTRS( , 32, 4, altivec);
+		FILL_GIVEN_FUNC_PTRS(u, 32, 4, altivec);
+	}
+#endif
+
+	/* --- 64-bit */
 #ifdef VEC_COMPILER_HAS_MMX
 	if (cpu & VEC_CPU_HAS_MMX) {
-		vint8x8_impl_cpu  = &vint8x8_impl_mmx;
-		vuint8x8_impl_cpu = &vuint8x8_impl_mmx;
-		vint16x4_impl_cpu  = &vint16x4_impl_mmx;
-		vuint16x4_impl_cpu = &vuint16x4_impl_mmx;
-		vint32x2_impl_cpu  = &vint32x2_impl_mmx;
-		vuint32x2_impl_cpu = &vuint32x2_impl_mmx;
+		FILL_GIVEN_FUNC_PTRS( , 8, 8, mmx);
+		FILL_GIVEN_FUNC_PTRS(u, 8, 8, mmx);
+		FILL_GIVEN_FUNC_PTRS( , 16, 4, mmx);
+		FILL_GIVEN_FUNC_PTRS(u, 16, 4, mmx);
+		FILL_GIVEN_FUNC_PTRS( , 32, 2, mmx);
+		FILL_GIVEN_FUNC_PTRS(u, 32, 2, mmx);
 	}
 #endif
 #ifdef VEC_COMPILER_HAS_NEON
 	if (cpu & VEC_CPU_HAS_NEON) {
-		// 64-bit
-		vint8x8_impl_cpu  = &vint8x8_impl_neon;
-		vuint8x8_impl_cpu = &vuint8x8_impl_neon;
-		vint16x4_impl_cpu  = &vint16x4_impl_neon;
-		vuint16x4_impl_cpu = &vuint16x4_impl_neon;
-		vint32x2_impl_cpu  = &vint32x2_impl_neon;
-		vuint32x2_impl_cpu = &vuint32x2_impl_neon;
-
-		// 128-bit
-		vint8x16_impl_cpu  = &vint8x16_impl_neon;
-		vuint8x16_impl_cpu = &vuint8x16_impl_neon;
-		vint16x8_impl_cpu  = &vint16x8_impl_neon;
-		vuint16x8_impl_cpu = &vuint16x8_impl_neon;
-		vint32x4_impl_cpu  = &vint32x4_impl_neon;
-		vuint32x4_impl_cpu = &vuint32x4_impl_neon;
-		vint64x2_impl_cpu  = &vint64x2_impl_neon;
-		vuint64x2_impl_cpu = &vuint64x2_impl_neon;
+		FILL_GIVEN_FUNC_PTRS( , 8, 8, neon);
+		FILL_GIVEN_FUNC_PTRS(u, 8, 8, neon);
+		FILL_GIVEN_FUNC_PTRS( , 16, 4, neon);
+		FILL_GIVEN_FUNC_PTRS(u, 16, 4, neon);
+		FILL_GIVEN_FUNC_PTRS( , 32, 2, neon);
+		FILL_GIVEN_FUNC_PTRS(u, 32, 2, neon);
 	}
 #endif
-	{
-		// do nothing, they're already set to generics
-	}
+
+	/* fill any remaining function pointers with generics */
+	FILL_GIVEN_FUNC_PTRS( , 8, 64,  generic);
+	FILL_GIVEN_FUNC_PTRS(u, 8, 64,  generic);
+	FILL_GIVEN_FUNC_PTRS( , 16, 32, generic);
+	FILL_GIVEN_FUNC_PTRS(u, 16, 32, generic);
+	FILL_GIVEN_FUNC_PTRS( , 32, 16, generic);
+	FILL_GIVEN_FUNC_PTRS(u, 32, 16, generic);
+	FILL_GIVEN_FUNC_PTRS( , 64, 8,  generic);
+	FILL_GIVEN_FUNC_PTRS(u, 64, 8,  generic);
+
+	FILL_GIVEN_FUNC_PTRS( , 8, 32,  generic);
+	FILL_GIVEN_FUNC_PTRS(u, 8, 32,  generic);
+	FILL_GIVEN_FUNC_PTRS( , 16, 16, generic);
+	FILL_GIVEN_FUNC_PTRS(u, 16, 16, generic);
+	FILL_GIVEN_FUNC_PTRS( , 32, 8,  generic);
+	FILL_GIVEN_FUNC_PTRS(u, 32, 8,  generic);
+	FILL_GIVEN_FUNC_PTRS( , 64, 4,  generic);
+	FILL_GIVEN_FUNC_PTRS(u, 64, 4,  generic);
+
+	FILL_GIVEN_FUNC_PTRS( , 8, 16, generic);
+	FILL_GIVEN_FUNC_PTRS(u, 8, 16, generic);
+	FILL_GIVEN_FUNC_PTRS( , 16, 8, generic);
+	FILL_GIVEN_FUNC_PTRS(u, 16, 8, generic);
+	FILL_GIVEN_FUNC_PTRS( , 32, 4, generic);
+	FILL_GIVEN_FUNC_PTRS(u, 32, 4, generic);
+	FILL_GIVEN_FUNC_PTRS( , 64, 2, generic);
+	FILL_GIVEN_FUNC_PTRS(u, 64, 2, generic);
+
+	FILL_GIVEN_FUNC_PTRS( , 8, 8, generic);
+	FILL_GIVEN_FUNC_PTRS(u, 8, 8, generic);
+	FILL_GIVEN_FUNC_PTRS( , 16, 4, generic);
+	FILL_GIVEN_FUNC_PTRS(u, 16, 4, generic);
+	FILL_GIVEN_FUNC_PTRS( , 32, 2, generic);
+	FILL_GIVEN_FUNC_PTRS(u, 32, 2, generic);
+
+	FILL_GIVEN_FUNC_PTRS( , 8, 4, generic);
+	FILL_GIVEN_FUNC_PTRS(u, 8, 4, generic);
+	FILL_GIVEN_FUNC_PTRS( , 16, 2, generic);
+	FILL_GIVEN_FUNC_PTRS(u, 16, 2, generic);
+
+	FILL_GIVEN_FUNC_PTRS( , 8, 2, generic);
+	FILL_GIVEN_FUNC_PTRS(u, 8, 2, generic);
 
 	vec_init_spinner++;
 
@@ -241,7 +371,6 @@
 	extern inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_and(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
 	extern inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_or(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
 	extern inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_xor(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
-	extern inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_not(v##sign##int##bits##x##size vec); \
 	extern inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_cmplt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
 	extern inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_cmple(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
 	extern inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_cmpeq(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
@@ -249,7 +378,9 @@
 	extern inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_cmpgt(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
 	extern inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_lshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2); \
 	extern inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_rshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2); \
-	extern inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_lrshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2);
+	extern inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_lrshift(v##sign##int##bits##x##size vec1, vuint##bits##x##size vec2); \
+	extern inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_min(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2); \
+	extern inline v##sign##int##bits##x##size v##sign##int##bits##x##size##_max(v##sign##int##bits##x##size vec1, v##sign##int##bits##x##size vec2);
 
 #define VEC_DEFINE_OPERATIONS(bits, size) \
 	VEC_DEFINE_OPERATIONS_SIGN( , bits, size) \
--- a/test/test_arith.h	Mon Nov 25 00:33:02 2024 -0500
+++ b/test/test_arith.h	Thu Apr 24 00:54:02 2025 -0400
@@ -65,7 +65,9 @@
 	CREATE_TEST(sign, psign, csign, bits, size, avg, vec_##sign##avg(orig_a[i], orig_b[i])) \
 	CREATE_TEST_SHIFT(sign, psign, csign, bits, size, rshift, vec_##sign##rshift(orig_a[i], orig_b[i])) \
 	CREATE_TEST_SHIFT(sign, psign, csign, bits, size, lshift, vec_##sign##lshift(orig_a[i], orig_b[i])) \
-	CREATE_TEST_SHIFT(sign, psign, csign, bits, size, lrshift, vec_lrshift((vec_uint##bits)orig_a[i], orig_b[i]))
+	CREATE_TEST_SHIFT(sign, psign, csign, bits, size, lrshift, vec_lrshift((vec_uint##bits)orig_a[i], orig_b[i])) \
+	CREATE_TEST(sign, psign, csign, bits, size, min, (orig_a[i] < orig_b[i]) ? (orig_a[i]) : (orig_b[i])) \
+	CREATE_TEST(sign, psign, csign, bits, size, max, (orig_a[i] > orig_b[i]) ? (orig_a[i]) : (orig_b[i]))
 
 #define CREATE_TESTS(bits, size) \
 	CREATE_TESTS_SIGN(, d, , bits, size) \
@@ -117,6 +119,8 @@
 			ret |= test_arith_v##sign##int##bits##x##size##_or(a, b); \
 			ret |= test_arith_v##sign##int##bits##x##size##_xor(a, b); \
 			ret |= test_arith_v##sign##int##bits##x##size##_avg(a, b); \
+			ret |= test_arith_v##sign##int##bits##x##size##_min(a, b); \
+			ret |= test_arith_v##sign##int##bits##x##size##_max(a, b); \
 		} \
 	} \
 	\