summaryrefslogtreecommitdiff
path: root/src/simd.h
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-12 17:49:34 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-12 17:49:34 +0300
commit4384435941b8c7658856b381d071aee2e0035e89 (patch)
tree5ece99c3dd1decb8ec3fd404c0ece2b5865a552a /src/simd.h
term: move the term core to subprojects/term
The core now builds, tests, and benchmarks alone, as a Meson subproject with no dependency on the main tree. - include/term/ holds term.h and the header-only unicode/. src/ holds the core and its private headers. The main tree gets only include/term through term_dep, so it cannot reach a private header. - The term tests, the term benchmarks, the Kitty parser suite, and tools/amalgamate.py and tools/generate_unicode.py move with it. The tests run as term:<name>. - The main build passes its -march (arch_flag) and b_ndebug to the subproject. A directory configured before this commit needs meson configure -Dterm:arch_flag=-march=... once. - The term tests compile with -UNDEBUG. They do work inside assert. - The subproject exports term_test_dep with term_api.h, for the main tree's render, thumbnail, color, and selection tests. - The Kitty bridge used struct vector2i and has not compiled since the public API redesign. It builds again. 2 of its 14 tests fail: REP after a tab, and a cursor report after a resize. - dirgraph.py drops the term and unicode layers. Its self-test uses pty. - The symbols test ignores the __odr_asan symbols that ASan adds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/simd.h')
-rw-r--r--src/simd.h117
1 files changed, 117 insertions, 0 deletions
diff --git a/src/simd.h b/src/simd.h
new file mode 100644
index 0000000..0238500
--- /dev/null
+++ b/src/simd.h
@@ -0,0 +1,117 @@
+#ifndef TERM_SIMD_H
+#define TERM_SIMD_H
+
+#include "compat.h"
+
+#include <stdalign.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#if defined(__x86_64__) || defined(_M_X64)
+#include <immintrin.h>
+#endif
+#if defined(__aarch64__)
+#include <arm_neon.h>
+#endif
+
+/*
+ * x86-64 kernels come in three levels: base (SSE2), AVX2 (x86-64-v3), and
+ * AVX-512 (x86-64-v4 with VBMI and VBMI2). GCC and Clang compile every
+ * level with target attributes, and term_isa_init() picks one at run time.
+ * Other compilers compile the levels that their flags enable.
+ *
+ * A kernel at a level is a function marked TERM_TARGET_AVX2 or
+ * TERM_TARGET_AVX512. It exists when TERM_KERNEL_AVX2 or TERM_KERNEL_AVX512
+ * is defined. Call it only when term_use_avx2() or term_use_avx512() is
+ * true. When the compiler flags already enable a level, these checks are
+ * constants and the kernels inline into their callers.
+ */
+#if defined(__AVX512F__) && defined(__AVX512BW__) && defined(__AVX512CD__) && \
+ defined(__AVX512DQ__) && defined(__AVX512VL__) && defined(__AVX512VBMI__) && \
+ defined(__AVX512VBMI2__) && defined(__AVX2__) && defined(__BMI2__)
+#define TERM_ISA_FLOOR 2
+#elif defined(__AVX2__) && defined(__BMI__) && defined(__BMI2__) && defined(__FMA__) && \
+ defined(__LZCNT__)
+#define TERM_ISA_FLOOR 1
+#else
+#define TERM_ISA_FLOOR 0
+#endif
+
+/* Clang 18 to 21 also need evex512 for 512-bit vectors. GCC and Clang 22
+ * reject the name. Apple Clang 17 is LLVM 19. */
+#if defined(__clang__) && ((defined(__apple_build_version__) && __clang_major__ >= 17) || \
+ (!defined(__apple_build_version__) && __clang_major__ >= 18 && __clang_major__ <= 21))
+#define TERM_EVEX512 ",evex512"
+#else
+#define TERM_EVEX512 ""
+#endif
+
+#if (defined(__x86_64__) || defined(_M_X64)) && defined(TERM_COMPILER_GNU)
+#define TERM_TARGET_AVX2 \
+ __attribute__((target("avx2,bmi,bmi2,fma,lzcnt,popcnt,movbe,f16c")))
+#define TERM_TARGET_AVX512 \
+ __attribute__((target("avx2,bmi,bmi2,fma,lzcnt,popcnt,movbe,f16c,avx512f,avx512bw," \
+ "avx512cd,avx512dq,avx512vl,avx512vbmi,avx512vbmi2" TERM_EVEX512)))
+#define TERM_KERNEL_AVX2 1
+#define TERM_KERNEL_AVX512 1
+#define TERM_ISA_RUNTIME 1
+#else
+#define TERM_TARGET_AVX2
+#define TERM_TARGET_AVX512
+#if TERM_ISA_FLOOR >= 1
+#define TERM_KERNEL_AVX2 1
+#endif
+#if TERM_ISA_FLOOR >= 2
+#define TERM_KERNEL_AVX512 1
+#endif
+#endif
+
+enum { TERM_ISA_BASE, TERM_ISA_AVX2, TERM_ISA_AVX512 };
+
+/* The level in use. Zero, the base level, until term_isa_init() runs. It
+ * is a plain int, so the compiler can hoist the checks out of loops: it is
+ * written once, before the release that publishes it, and read only after
+ * that. */
+extern int term_isa_level;
+
+/* Pick the level once. Any thread can call it. term_new() calls it before
+ * the first terminal exists. */
+void term_isa_init(void);
+
+static inline bool term_use_avx2(void)
+{
+#if TERM_ISA_FLOOR >= 1
+ return true;
+#elif defined(TERM_ISA_RUNTIME)
+ return term_isa_level >= TERM_ISA_AVX2;
+#else
+ return false;
+#endif
+}
+
+static inline bool term_use_avx512(void)
+{
+#if TERM_ISA_FLOOR >= 2
+ return true;
+#elif defined(TERM_ISA_RUNTIME)
+ return term_isa_level >= TERM_ISA_AVX512;
+#else
+ return false;
+#endif
+}
+
+#if defined(__aarch64__)
+static inline uint16_t term_lane_bits(uint8x16_t m)
+{
+ const uint8x16_t idx = {1, 2, 4, 8, 16, 32, 64, 128,
+ 1, 2, 4, 8, 16, 32, 64, 128};
+ uint8x16_t s = vandq_u8(m, idx);
+ s = vpaddq_u8(s, s);
+ s = vpaddq_u8(s, s);
+ s = vpaddq_u8(s, s);
+ return (uint16_t)(vgetq_lane_u8(s, 0) | (vgetq_lane_u8(s, 1) << 8));
+}
+#endif
+
+#endif