From 4384435941b8c7658856b381d071aee2e0035e89 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Mon, 12 Oct 2026 17:49:34 +0300 Subject: 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:. - 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 --- src/compat.h | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 src/compat.h (limited to 'src/compat.h') diff --git a/src/compat.h b/src/compat.h new file mode 100644 index 0000000..d1264fe --- /dev/null +++ b/src/compat.h @@ -0,0 +1,171 @@ +#ifndef TERM_COMPAT_H +#define TERM_COMPAT_H + +/* + * Compiler and libc differences, kept in one place. GCC and Clang use their + * builtins. MSVC uses its intrinsics. Any other compiler, and any build with + * TERM_NO_COMPILER_BUILTINS, uses plain C. Plain C gives the same results, + * only slower. + */ + +#include +#include +#include +#include +#include + +#if defined(__GNUC__) || defined(__clang__) +#define TERM_COMPILER_GNU 1 +#elif defined(_MSC_VER) +#define TERM_COMPILER_MSVC 1 +#include +#endif + +#if !defined(TERM_NO_COMPILER_BUILTINS) && defined(TERM_COMPILER_GNU) +#define TERM_HAVE_BUILTINS 1 +#elif !defined(TERM_NO_COMPILER_BUILTINS) && defined(TERM_COMPILER_MSVC) +#define TERM_HAVE_MSVC_BITOPS 1 +#endif + +#if defined(TERM_COMPILER_GNU) +#define TERM_HOT __attribute__((hot)) +#define TERM_NOINLINE __attribute__((noinline)) +#define TERM_ALWAYS_INLINE __attribute__((always_inline)) +#define TERM_MAYBE_UNUSED __attribute__((unused)) +#elif defined(TERM_COMPILER_MSVC) +#define TERM_HOT +#define TERM_NOINLINE __declspec(noinline) +#define TERM_ALWAYS_INLINE __forceinline +#define TERM_MAYBE_UNUSED +#else +#define TERM_HOT +#define TERM_NOINLINE +#define TERM_ALWAYS_INLINE +#define TERM_MAYBE_UNUSED +#endif + +#if defined(TERM_HAVE_BUILTINS) +#define TERM_EXPECT(x, v) __builtin_expect((x), (v)) +#else +#define TERM_EXPECT(x, v) (x) +#endif + +/* term_ctz32(0) is 32 and term_ctz64(0) is 64 on every path. */ +#if defined(TERM_HAVE_BUILTINS) + +static inline int term_ctz32(uint32_t x) { return x ? __builtin_ctz(x) : 32; } +static inline int term_popcount32(uint32_t x) { return __builtin_popcount(x); } +static inline int term_ctz64(uint64_t x) { return x ? __builtin_ctzll(x) : 64; } +static inline int term_popcount64(uint64_t x) { return __builtin_popcountll(x); } + +#else + +#if defined(TERM_HAVE_MSVC_BITOPS) +static inline int term_ctz32(uint32_t x) +{ + unsigned long i; + return _BitScanForward(&i, x) ? (int)i : 32; +} +static inline int term_popcount32(uint32_t x) { return (int)__popcnt(x); } +#else +static inline int term_ctz32(uint32_t x) +{ + if (!x) + return 32; + int n = 0; + while (!(x & 1u)) { + x >>= 1; + n++; + } + return n; +} +static inline int term_popcount32(uint32_t x) +{ + int n = 0; + for (; x; x &= x - 1) + n++; + return n; +} +#endif + +/* Without builtins, the 64-bit forms are built from the 32-bit ones. */ +static inline int term_ctz64(uint64_t x) +{ + uint32_t lo = (uint32_t)x; + return lo ? term_ctz32(lo) : 32 + term_ctz32((uint32_t)(x >> 32)); +} + +static inline int term_popcount64(uint64_t x) +{ + return term_popcount32((uint32_t)x) + term_popcount32((uint32_t)(x >> 32)); +} + +#endif + +/* A spin-wait hint. Compilers without one get a no-op. */ +static inline void term_cpu_relax(void) +{ +#if defined(TERM_COMPILER_GNU) +#if defined(TERM_HAVE_BUILTINS) && defined(__x86_64__) + __builtin_ia32_pause(); +#elif defined(__aarch64__) + __asm__ __volatile__("yield"); +#endif +#elif defined(TERM_COMPILER_MSVC) +#if defined(_M_X64) || defined(_M_IX86) + _mm_pause(); +#elif defined(_M_ARM64) + __yield(); +#endif +#endif +} + +/* strdup is POSIX, so the core copies strings itself. */ +static inline char *term_strdup(const char *s) +{ + size_t n = strlen(s) + 1; + char *copy = malloc(n); + if (copy) + memcpy(copy, s, n); + return copy; +} + +/* ASCII-only prefix test, as strncasecmp() does in the C locale. */ +static inline bool term_has_prefix_ci(const char *s, const char *prefix) +{ + for (; *prefix; prefix++, s++) { + unsigned char a = (unsigned char)*s; + unsigned char b = (unsigned char)*prefix; + if (a >= 'A' && a <= 'Z') + a += 'a' - 'A'; + if (b >= 'A' && b <= 'Z') + b += 'a' - 'A'; + if (a != b) + return false; + } + return true; +} + +/* Allocates size bytes that start on an align-byte boundary. align must be a + * power of two. The raw pointer is stored just before the block, so + * term_aligned_free() can release it. C11 aligned_alloc() and POSIX + * posix_memalign() are not on every toolchain. */ +static inline void *term_aligned_alloc(size_t align, size_t size) +{ + if (size > SIZE_MAX - align - sizeof(void *)) + return NULL; + void *raw = malloc(size + align + sizeof(void *)); + if (!raw) + return NULL; + uintptr_t p = ((uintptr_t)raw + sizeof(void *) + align - 1) & ~((uintptr_t)align - 1); + ((void **)p)[-1] = raw; + return (void *)p; +} + +static inline void term_aligned_free(void *p) +{ + if (p) + free(((void **)p)[-1]); +} + +#endif -- cgit v1.2.3