Skip to content

Commit 85fc74f

Browse files
committed
Full Windows portability: proper implementations, not stubs
- handle_processes: GetProcessMemoryInfo + GetProcessTimes (Windows), getrusage (POSIX) — real process metrics on both platforms - handle_process_kill: TerminateProcess via OpenProcess (Windows), kill(SIGTERM) (POSIX) - index_thread_fn: CreateProcess + WaitForSingleObject (Windows), fork+exec+waitpid (POSIX) - cbm_clock_gettime: QueryPerformanceCounter (Windows) — replaces all bare clock_gettime calls across 6 files - cbm_nanosleep, cbm_strcasestr, cbm_mkdir: compat shims - CBM_TLS: _Thread_local (C11 standard, works on all compilers) - Replace bare strndup with cbm_strndup, guard POSIX headers - Add mingw-w64 cross-compile to Docker test infrastructure
1 parent 26a7814 commit 85fc74f

22 files changed

Lines changed: 1194 additions & 125 deletions

File tree

Makefile.cbm

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,13 @@ CFLAGS_TSAN = $(CFLAGS_COMMON) -g -O1 \
7575
CXXFLAGS_TSAN = $(CXXFLAGS_COMMON) -g -O1 \
7676
-fsanitize=thread -fno-omit-frame-pointer
7777

78-
LDFLAGS = -lm -lstdc++ -lpthread -lz $(LIBGIT2_LIBS)
79-
LDFLAGS_TEST = -lm -lstdc++ -lpthread -lz -fsanitize=address,undefined $(LIBGIT2_LIBS)
80-
LDFLAGS_TSAN = -lm -lstdc++ -lpthread -lz -fsanitize=thread $(LIBGIT2_LIBS)
78+
# Windows needs ws2_32 (Winsock) and psapi (GetProcessMemoryInfo).
79+
# Override via: make WIN32_LIBS="-lws2_32 -lpsapi" for cross-compilation.
80+
WIN32_LIBS ?=
81+
82+
LDFLAGS = -lm -lstdc++ -lpthread -lz $(LIBGIT2_LIBS) $(WIN32_LIBS)
83+
LDFLAGS_TEST = -lm -lstdc++ -lpthread -lz -fsanitize=address,undefined $(LIBGIT2_LIBS) $(WIN32_LIBS)
84+
LDFLAGS_TSAN = -lm -lstdc++ -lpthread -lz -fsanitize=thread $(LIBGIT2_LIBS) $(WIN32_LIBS)
8185

8286
# ── Source files ─────────────────────────────────────────────────
8387

internal/cbm/cbm.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ static _Atomic uint64_t total_files = 0;
2828
static uint64_t now_ns(void) {
2929
struct timespec ts;
3030
// NOLINTNEXTLINE(misc-include-cleaner) — clock_gettime provided by standard header
31-
clock_gettime(CLOCK_MONOTONIC, &ts);
31+
cbm_clock_gettime(CLOCK_MONOTONIC, &ts);
3232
return ((uint64_t)ts.tv_sec * NSEC_PER_SEC) + (uint64_t)ts.tv_nsec;
3333
}
3434

src/discover/discover.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,14 +251,20 @@ static void walk_dir(const char *dir_path, const char *rel_prefix, const cbm_dis
251251
}
252252

253253
struct stat st;
254+
#ifdef _WIN32
255+
if (stat(abs_path, &st) != 0) {
256+
continue;
257+
}
258+
/* Windows: no symlink detection via stat */
259+
#else
254260
if (lstat(abs_path, &st) != 0) {
255261
continue;
256262
}
257-
258263
/* Skip symlinks */
259264
if (S_ISLNK(st.st_mode)) {
260265
continue;
261266
}
267+
#endif
262268

263269
if (S_ISDIR(st.st_mode)) {
264270
/* Check hardcoded directory skip */

src/foundation/compat.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,35 @@ char *cbm_strndup(const char *s, size_t n) {
2929
}
3030
#endif
3131

32+
/* ── strcasestr (Windows lacks it) ────────────────────────────── */
33+
34+
#ifdef _WIN32
35+
char *cbm_strcasestr(const char *haystack, const char *needle) {
36+
if (!needle[0])
37+
return (char *)haystack;
38+
size_t nlen = strlen(needle);
39+
for (; *haystack; haystack++) {
40+
if (_strnicmp(haystack, needle, nlen) == 0)
41+
return (char *)haystack;
42+
}
43+
return NULL;
44+
}
45+
#endif
46+
47+
/* ── clock_gettime (Windows lacks it) ─────────────────────────── */
48+
49+
#ifdef _WIN32
50+
int cbm_clock_gettime(int clk_id, struct timespec *tp) {
51+
(void)clk_id;
52+
LARGE_INTEGER freq, count;
53+
QueryPerformanceFrequency(&freq);
54+
QueryPerformanceCounter(&count);
55+
tp->tv_sec = (time_t)(count.QuadPart / freq.QuadPart);
56+
tp->tv_nsec = (long)((count.QuadPart % freq.QuadPart) * 1000000000LL / freq.QuadPart);
57+
return 0;
58+
}
59+
#endif
60+
3261
/* ── getline (Windows lacks it) ───────────────────────────────── */
3362

3463
#ifdef _WIN32

src/foundation/compat.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,41 @@ ssize_t cbm_getline(char **lineptr, size_t *n, FILE *stream);
5353
#define cbm_fileno fileno
5454
#endif
5555

56+
/* ── strcasestr (Windows lacks it) ────────────────────────────── */
57+
#ifdef _WIN32
58+
/* Implemented in compat.c */
59+
char *cbm_strcasestr(const char *haystack, const char *needle);
60+
#else
61+
#define cbm_strcasestr strcasestr
62+
#endif
63+
64+
/* ── mkdir portability ───────────────────────────────────────── */
65+
#ifdef _WIN32
66+
#include <direct.h>
67+
#define cbm_mkdir(path) _mkdir(path)
68+
#else
69+
#include <sys/stat.h>
70+
#define cbm_mkdir(path) mkdir(path, 0755)
71+
#endif
72+
73+
/* ── clock_gettime / nanosleep (Windows lacks them) ──────────── */
74+
#include <time.h>
75+
#ifdef _WIN32
76+
#ifndef CLOCK_MONOTONIC
77+
#define CLOCK_MONOTONIC 1
78+
#endif
79+
/* Implemented in compat.c */
80+
int cbm_clock_gettime(int clk_id, struct timespec *tp);
81+
static inline int cbm_nanosleep(const struct timespec *req, struct timespec *rem) {
82+
(void)rem;
83+
Sleep((DWORD)(req->tv_sec * 1000 + req->tv_nsec / 1000000));
84+
return 0;
85+
}
86+
#else
87+
#define cbm_clock_gettime clock_gettime
88+
#define cbm_nanosleep nanosleep
89+
#endif
90+
5691
/* ── Signal handling ──────────────────────────────────────────── */
5792
/* Windows doesn't have sigaction; provide macro to select signal API. */
5893
#ifdef _WIN32

src/foundation/platform.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* macOS, Linux, and Windows. Platform-specific code behind #ifdef guards.
55
*/
66
#include "platform.h"
7+
#include "compat.h"
78

89
#include <stdint.h> // uint64_t, int64_t
910

@@ -166,7 +167,7 @@ uint64_t cbm_now_ns(void) {
166167
#else
167168
uint64_t cbm_now_ns(void) {
168169
struct timespec ts;
169-
clock_gettime(CLOCK_MONOTONIC, &ts);
170+
cbm_clock_gettime(CLOCK_MONOTONIC, &ts);
170171
return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
171172
}
172173
#endif

src/foundation/system_info.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include <windows.h>
1919
#elif defined(__APPLE__)
2020
#include <sys/sysctl.h>
21-
#else /* Linux */
21+
#elif !defined(_WIN32) /* Linux */
2222
#include <unistd.h>
2323
#include <sys/sysinfo.h>
2424
#endif
@@ -68,7 +68,7 @@ static cbm_system_info_t detect_system_macos(void) {
6868
return info;
6969
}
7070

71-
#else /* Linux */
71+
#elif !defined(_WIN32) /* Linux */
7272

7373
static cbm_system_info_t detect_system_linux(void) {
7474
cbm_system_info_t info;

src/foundation/vmem.c

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/*
2+
* vmem.c — Budget-tracked virtual memory allocator.
3+
*
4+
* Allocates via mmap (POSIX) or VirtualAlloc (Windows) to bypass
5+
* ptmalloc2's per-thread arena fragmentation. All allocations are
6+
* page-aligned, zeroed by the OS, and tracked against a configurable
7+
* budget (fraction of physical RAM).
8+
*
9+
* Pressure events are logged with hysteresis to avoid log storms
10+
* near the budget boundary.
11+
*/
12+
#include "vmem.h"
13+
#include "platform.h"
14+
#include "log.h"
15+
16+
#include <stdatomic.h>
17+
#include <stdio.h> /* snprintf */
18+
#include <string.h> /* memset */
19+
20+
#ifdef _WIN32
21+
#ifndef WIN32_LEAN_AND_MEAN
22+
#define WIN32_LEAN_AND_MEAN
23+
#endif
24+
#include <windows.h>
25+
#else
26+
#include <sys/mman.h>
27+
#include <unistd.h> /* sysconf, _SC_PAGESIZE */
28+
#endif
29+
30+
/* ── Static state (initialized once) ──────────────────────────── */
31+
32+
static atomic_size_t g_allocated = 0; /* current total allocated */
33+
static atomic_size_t g_peak = 0; /* high-water mark */
34+
static size_t g_budget = 0; /* budget in bytes */
35+
static atomic_int g_was_over = 0; /* hysteresis: was over budget? */
36+
static atomic_int g_initialized = 0; /* init guard */
37+
38+
/* ── Page size ─────────────────────────────────────────────────── */
39+
40+
static size_t page_size(void) {
41+
#ifdef _WIN32
42+
SYSTEM_INFO si;
43+
GetSystemInfo(&si);
44+
return (size_t)si.dwPageSize;
45+
#else
46+
long ps = sysconf(_SC_PAGESIZE);
47+
return ps > 0 ? (size_t)ps : 4096;
48+
#endif
49+
}
50+
51+
/* Round up to page boundary. */
52+
static size_t round_to_page(size_t size) {
53+
size_t ps = page_size();
54+
return (size + ps - 1) & ~(ps - 1);
55+
}
56+
57+
/* ── Pressure logging ──────────────────────────────────────────── */
58+
59+
#define MB_DIVISOR ((size_t)(1024 * 1024))
60+
61+
static void check_pressure(size_t allocated) {
62+
if (g_budget == 0) {
63+
return;
64+
}
65+
66+
bool over = allocated > g_budget;
67+
int was = atomic_load(&g_was_over);
68+
69+
if (over && !was) {
70+
/* Transition: under → over */
71+
atomic_store(&g_was_over, 1);
72+
char alloc_mb[32];
73+
char budget_mb[32];
74+
char pct_str[16];
75+
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
76+
snprintf(alloc_mb, sizeof(alloc_mb), "%zu", allocated / MB_DIVISOR);
77+
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
78+
snprintf(budget_mb, sizeof(budget_mb), "%zu", g_budget / MB_DIVISOR);
79+
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
80+
snprintf(pct_str, sizeof(pct_str), "%zu",
81+
g_budget > 0 ? (allocated * 100) / g_budget : 0);
82+
cbm_log_warn("mem.pressure.warn", "allocated_mb", alloc_mb, "budget_mb", budget_mb, "pct",
83+
pct_str);
84+
} else if (!over && was) {
85+
/* Transition: over → under */
86+
atomic_store(&g_was_over, 0);
87+
char alloc_mb[32];
88+
char budget_mb[32];
89+
char pct_str[16];
90+
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
91+
snprintf(alloc_mb, sizeof(alloc_mb), "%zu", allocated / MB_DIVISOR);
92+
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
93+
snprintf(budget_mb, sizeof(budget_mb), "%zu", g_budget / MB_DIVISOR);
94+
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
95+
snprintf(pct_str, sizeof(pct_str), "%zu",
96+
g_budget > 0 ? (allocated * 100) / g_budget : 0);
97+
cbm_log_info("mem.pressure.ok", "allocated_mb", alloc_mb, "budget_mb", budget_mb, "pct",
98+
pct_str);
99+
}
100+
}
101+
102+
/* ── Update peak ───────────────────────────────────────────────── */
103+
104+
static void update_peak(size_t allocated) {
105+
size_t old_peak = atomic_load(&g_peak);
106+
while (allocated > old_peak) {
107+
if (atomic_compare_exchange_weak(&g_peak, &old_peak, allocated)) {
108+
break;
109+
}
110+
}
111+
}
112+
113+
/* ── Public API ────────────────────────────────────────────────── */
114+
115+
void cbm_vmem_init(double ram_fraction) {
116+
/* Only first call takes effect */
117+
int expected = 0;
118+
if (!atomic_compare_exchange_strong(&g_initialized, &expected, 1)) {
119+
return;
120+
}
121+
122+
if (ram_fraction <= 0.0 || ram_fraction > 1.0) {
123+
ram_fraction = 0.5;
124+
}
125+
126+
cbm_system_info_t info = cbm_system_info();
127+
g_budget = (size_t)((double)info.total_ram * ram_fraction);
128+
129+
char budget_mb[32];
130+
char ram_mb[32];
131+
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
132+
snprintf(budget_mb, sizeof(budget_mb), "%zu", g_budget / MB_DIVISOR);
133+
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
134+
snprintf(ram_mb, sizeof(ram_mb), "%zu", info.total_ram / MB_DIVISOR);
135+
cbm_log_info("vmem.init", "budget_mb", budget_mb, "total_ram_mb", ram_mb);
136+
}
137+
138+
void *cbm_vmem_alloc(size_t size) {
139+
if (size == 0) {
140+
return NULL;
141+
}
142+
143+
size_t alloc_size = round_to_page(size);
144+
145+
#ifdef _WIN32
146+
void *ptr = VirtualAlloc(NULL, alloc_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
147+
#else
148+
void *ptr = mmap(NULL, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
149+
if (ptr == MAP_FAILED) {
150+
ptr = NULL;
151+
}
152+
#endif
153+
154+
if (!ptr) {
155+
cbm_log_error("vmem.alloc.fail", "size_mb",
156+
size > MB_DIVISOR ? "large" : "small");
157+
return NULL;
158+
}
159+
160+
size_t new_total = atomic_fetch_add(&g_allocated, alloc_size) + alloc_size;
161+
update_peak(new_total);
162+
check_pressure(new_total);
163+
164+
return ptr;
165+
}
166+
167+
void cbm_vmem_free(void *ptr, size_t size) {
168+
if (!ptr || size == 0) {
169+
return;
170+
}
171+
172+
size_t free_size = round_to_page(size);
173+
174+
#ifdef _WIN32
175+
VirtualFree(ptr, 0, MEM_RELEASE);
176+
#else
177+
munmap(ptr, free_size);
178+
#endif
179+
180+
size_t new_total = atomic_fetch_sub(&g_allocated, free_size) - free_size;
181+
check_pressure(new_total);
182+
}
183+
184+
size_t cbm_vmem_allocated(void) {
185+
return atomic_load(&g_allocated);
186+
}
187+
188+
size_t cbm_vmem_peak(void) {
189+
return atomic_load(&g_peak);
190+
}
191+
192+
size_t cbm_vmem_budget(void) {
193+
return g_budget;
194+
}
195+
196+
bool cbm_vmem_over_budget(void) {
197+
return atomic_load(&g_allocated) > g_budget;
198+
}
199+
200+
size_t cbm_vmem_worker_budget(int num_workers) {
201+
if (num_workers <= 0) {
202+
num_workers = 1;
203+
}
204+
return g_budget / (size_t)num_workers;
205+
}

0 commit comments

Comments
 (0)