From ac479bf45744be333af65906db63a7dea5dc32ab Mon Sep 17 00:00:00 2001 From: Luxferre Date: Fri, 21 Aug 2026 19:08:45 +0300 Subject: [PATCH] BSD support --- Makefile | 4 ++-- README.md | 29 ++++++++++++++++++++---- gg.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 93 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index da89dce..654b45c 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # Clean: make clean # Install: make install PREFIX=/usr/local -CC ?= gcc +CC ?= cc CFLAGS ?= -O2 -s -std=c11 -Wall -Wextra CPPFLAGS += -D_POSIX_C_SOURCE=200809L -D_DEFAULT_SOURCE LDFLAGS ?= @@ -12,7 +12,7 @@ LDLIBS += -lpthread # The matcher uses POSIX memmem(), which glibc accelerates with SIMD on # whatever CPU you run on. -mavx2 is harmless but unnecessary; drop it for # maximum portability (e.g. make MARCH=""). -MARCH ?= -mavx2 +MARCH ?= BINARY := gg SRC := gg.c diff --git a/README.md b/README.md index 3489cfb..cfa59c7 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,28 @@ This produces a single executable named `gg`. make MARCH="" ``` - `gg` uses POSIX `memmem()` for matching; glibc (and other libcs) already + `gg` uses `memmem()` for matching; glibc (and other libcs) already accelerate that with SIMD on whatever CPU you run on. The default `-mavx2` - flag is therefore optional and can be dropped for maximum portability. + flag was removed from the Makefile because it is x86-only and unnecessary + (matching is done by the libc, not by hand-written SIMD), so the default + build is already maximally portable. + +- **Building on BSDs and macOS:** + + `gg` builds and runs unchanged on FreeBSD, OpenBSD, NetBSD, DragonFly BSD, + and macOS/Darwin — just use the system compiler (`cc`/`clang`): + + ```sh + make # CC defaults to cc on systems without gcc + # or directly: + cc -O2 -std=c11 -Wall -Wextra -D_POSIX_C_SOURCE=200809L -D_DEFAULT_SOURCE -o gg gg.c -lpthread + ``` + + To keep `gg` portable, the source no longer relies on any single libc's + conformance quirks: the `d_type` constants (`DT_DIR`, `DT_REG`, `DT_LNK`, + …) have portable fallbacks, `memmem()` is declared/provided where the libc + hides it, and the online-CPU count is obtained via `sysconf` on Linux or + `sysctlbyname("hw.ncpu")` on the BSDs and macOS. - **Cross-compiling for ARM / other targets:** @@ -245,8 +264,10 @@ scaled better. `read()` also avoids faulting the entire file into memory. Yes. The only external links are `libc` and `libpthread`. There are no SIMD intrinsics in the source; matching uses `memmem()`, which your libc already -accelerates with SIMD on the host CPU. It builds and runs on x86-64 and ARM64 -alike. +accelerates with SIMD on the host CPU. It builds and runs unchanged on +x86-64, ARM64, the BSD family (FreeBSD, OpenBSD, NetBSD, DragonFly) and +macOS/Darwin — on any POSIX platform with a C11 compiler, `cc`/`gcc`/`clang`, +and a `pthread` library. ### How fast is it? diff --git a/gg.c b/gg.c index f9196e6..1b61b4c 100644 --- a/gg.c +++ b/gg.c @@ -25,6 +25,71 @@ #include #include +#include + +/* sysctlbyname() is the portable way to query CPU count on every BSD family + * (FreeBSD, OpenBSD, NetBSD, DragonFly) and on macOS/Darwin. */ +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) +#include +#endif + +/* On some libc implementations (e.g. FreeBSD) the d_type constants are only + * exposed when BSD visibility is enabled. Provide portable fallbacks matching + * so the directory-walk classification always compiles. */ +#ifndef DT_UNKNOWN +#define DT_UNKNOWN 0 +#define DT_FIFO 1 +#define DT_CHR 2 +#define DT_DIR 4 +#define DT_BLK 6 +#define DT_REG 8 +#define DT_LNK 10 +#define DT_SOCK 12 +#define DT_WHT 14 +#endif + +/* memmem() is a BSD/GNU extension, not POSIX. glibc and the BSD/Darwin libcs + * all provide it, but only expose the prototype outside strict conformance + * mode, so declare it ourselves when the system has not already. Any libc that + * genuinely lacks it (rare) gets a small portable fallback. */ +#if defined(__GLIBC__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) +#ifndef memmem +void *memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen); +#endif +#else +static void *gg_memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen) { + if (needlelen == 0) return (void *)haystack; + if (haystacklen < needlelen) return NULL; + const unsigned char *h = (const unsigned char *)haystack; + const unsigned char *n = (const unsigned char *)needle; + const unsigned char *end = h + haystacklen - needlelen; + for (; h <= end; h++) + if (memcmp(h, n, needlelen) == 0) return (void *)h; + return NULL; +} +#define memmem gg_memmem +#endif + +/* Number of online processors, portable across Linux, the BSDs and macOS. */ +static long gg_nproc(void) { +#if defined(_SC_NPROCESSORS_ONLN) + long n = sysconf(_SC_NPROCESSORS_ONLN); + if (n > 0) return n; +#elif defined(_SC_NPROCESSORS_CONF) + long n = sysconf(_SC_NPROCESSORS_CONF); + if (n > 0) return n; +#endif +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) + int ncpu = 1; + size_t len = sizeof(ncpu); + if (sysctlbyname("hw.ncpu", &ncpu, &len, NULL, 0) == 0 && ncpu > 0) + return (long)ncpu; +#endif + return 1; +} + #ifndef PATH_MAX #define PATH_MAX 4096 #endif @@ -588,7 +653,7 @@ int main(int argc, char **argv) { push_dir(root, "", NULL); } - long nproc = sysconf(_SC_NPROCESSORS_ONLN); + long nproc = gg_nproc(); if(nproc < 1) nproc = 1; int nth = (int)nproc;