solidc
Robust collection of general-purpose cross-platform C libraries and data structures designed for rapid and safe development in C
Loading...
Searching...
No Matches
macros.h
Go to the documentation of this file.
1
21#ifndef SOLIDC_MACROS_H
22#define SOLIDC_MACROS_H
23
24#include <stddef.h> /* size_t, ptrdiff_t */
25#include <stdint.h> /* uint64_t, uintptr_t, intptr_t, ... */
26#include <stdio.h> /* printf, fprintf, stderr */
27#include <stdlib.h> /* exit */
28#include <string.h> /* strcmp, strncmp, memset */
29#include <time.h> /* time_t, struct tm, clock_gettime */
30
31#if defined(__cplusplus)
32extern "C" {
33#endif
34
35/* =========================================================================
36 * COMPILER DETECTION
37 * ========================================================================= */
38
39#if defined(_MSC_VER)
40#define SOLIDC_MSVC 1
41#define SOLIDC_GCC 0
42#define SOLIDC_CLANG 0
43#elif defined(__clang__)
44#define SOLIDC_MSVC 0
45#define SOLIDC_GCC 0
46#define SOLIDC_CLANG 1
47#elif defined(__GNUC__)
48#define SOLIDC_MSVC 0
49#define SOLIDC_GCC 1
50#define SOLIDC_CLANG 0
51#else
52#define SOLIDC_MSVC 0
53#define SOLIDC_GCC 0
54#define SOLIDC_CLANG 0
55#endif
56
57/* =========================================================================
58 * COMPILER HINTS AND ATTRIBUTES
59 * ========================================================================= */
60
62#if SOLIDC_GCC || SOLIDC_CLANG
63#define LIKELY(x) __builtin_expect(!!(x), 1)
64#define UNLIKELY(x) __builtin_expect(!!(x), 0)
65#else
66#define LIKELY(x) (x)
67#define UNLIKELY(x) (x)
68#endif
69
74#if SOLIDC_GCC || SOLIDC_CLANG
75#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
76#elif SOLIDC_MSVC
77#define WARN_UNUSED_RESULT _Check_return_
78#else
79#define WARN_UNUSED_RESULT
80#endif
81
87#if SOLIDC_GCC || SOLIDC_CLANG
88#define PRINTF_FORMAT(fmt, va) __attribute__((format(printf, fmt, va)))
89#else
90#define PRINTF_FORMAT(fmt, va)
91#endif
92
94#if SOLIDC_GCC || SOLIDC_CLANG
95#define NORETURN __attribute__((noreturn))
96#elif SOLIDC_MSVC
97#define NORETURN __declspec(noreturn)
98#else
99#define NORETURN
100#endif
101
103#define UNUSED(x) ((void)(x))
104
105#if SOLIDC_MSVC
106#define THREAD_LOCAL __declspec(thread)
107#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
108#define THREAD_LOCAL _Thread_local
109#else
110/* GCC/Clang pre-C11 extension fallback */
111#define THREAD_LOCAL __thread
112#endif
113
114/* =========================================================================
115 * STATIC ASSERTIONS
116 * ========================================================================= */
117
119#if defined(__cplusplus)
120#define STATIC_ASSERT(cond, msg) static_assert(cond, msg)
121#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
122#define STATIC_ASSERT(cond, msg) _Static_assert(cond, msg)
123#else
124/* Pre-C11 fallback: causes an array-size error if cond is false. */
125#define STATIC_ASSERT(cond, msg) typedef char _static_assert_##__LINE__[(cond) ? 1 : -1]
126#endif
127
128/* =========================================================================
129 * RUNTIME ASSERTION MACROS
130 *
131 * All assertions print file, line, and function name, then call exit(1).
132 * Define NDEBUG to compile out assertions (same convention as <assert.h>).
133 * ========================================================================= */
134
135#ifndef NDEBUG
136
138#define ASSERT(cond) \
139 do { \
140 if (UNLIKELY(!(cond))) { \
141 fprintf(stderr, "%s:%d [%s]: Assertion '%s' failed.\n", __FILE__, __LINE__, __func__, #cond); \
142 exit(1); \
143 } \
144 } while (0)
145
147#define ASSERT_TRUE(cond) ASSERT(cond)
148
150#define ASSERT_NULL(ptr) \
151 do { \
152 if (UNLIKELY((ptr) != NULL)) { \
153 fprintf(stderr, "%s:%d [%s]: Expected '%s' to be NULL.\n", __FILE__, __LINE__, __func__, #ptr); \
154 exit(1); \
155 } \
156 } while (0)
157
159#define ASSERT_NOT_NULL(ptr) \
160 do { \
161 if (UNLIKELY((ptr) == NULL)) { \
162 fprintf(stderr, "%s:%d [%s]: Expected '%s' to not be NULL.\n", __FILE__, __LINE__, __func__, #ptr); \
163 exit(1); \
164 } \
165 } while (0)
166
171#define ASSERT_STR_EQ(a, b) \
172 do { \
173 const char* _ase_a = (a); \
174 const char* _ase_b = (b); \
175 if (_ase_a == NULL || _ase_b == NULL) { \
176 if (_ase_a != _ase_b) { \
177 fprintf(stderr, "%s:%d [%s]: '%s == %s' failed (one is NULL).\n", __FILE__, __LINE__, __func__, #a, \
178 #b); \
179 exit(1); \
180 } \
181 } else if (strcmp(_ase_a, _ase_b) != 0) { \
182 fprintf(stderr, "%s:%d [%s]: '%s == %s' failed (\"%s\" != \"%s\").\n", __FILE__, __LINE__, __func__, #a, \
183 #b, _ase_a, _ase_b); \
184 exit(1); \
185 } \
186 } while (0)
187
192#define ASSERT_FLOAT_EQ(a, b, epsilon) \
193 do { \
194 double _afe_a = (double)(a); \
195 double _afe_b = (double)(b); \
196 double _afe_eps = (double)(epsilon); \
197 double _afe_d = _afe_a - _afe_b; \
198 /* Use manual abs to avoid pulling in <math.h> for fabs. */ \
199 if ((_afe_d > _afe_eps) || (-_afe_d > _afe_eps)) { \
200 fprintf(stderr, \
201 "%s:%d [%s]: Float '%s == %s' failed" \
202 " (%.6f != %.6f, eps=%.6f).\n", \
203 __FILE__, __LINE__, __func__, #a, #b, _afe_a, _afe_b, _afe_eps); \
204 exit(1); \
205 } \
206 } while (0)
207
208/*
209 * ASSERT_EQ / ASSERT_NE / ASSERT_RANGE
210 *
211 * These macros compare two values using temporary variables to avoid
212 * double evaluation. Under GCC/Clang we use __typeof__ to preserve the
213 * exact type. Under MSVC, which lacks typeof in C mode, we fall back to
214 * intptr_t — wide enough for any scalar and pointer coerced to integer.
215 * Floating-point comparisons should use ASSERT_FLOAT_EQ instead.
216 */
217#if SOLIDC_GCC || SOLIDC_CLANG
218
220#define ASSERT_EQ(a, b) \
221 do { \
222 __typeof__(a) _aeq_a = (a); \
223 __typeof__(b) _aeq_b = (b); \
224 if (UNLIKELY(_aeq_a != _aeq_b)) { \
225 fprintf(stderr, \
226 "%s:%d [%s]: '%s == %s' failed" \
227 " (%td != %td).\n", \
228 __FILE__, __LINE__, __func__, #a, #b, (ptrdiff_t)_aeq_a, (ptrdiff_t)_aeq_b); \
229 exit(1); \
230 } \
231 } while (0)
232
234#define ASSERT_NE(a, b) \
235 do { \
236 __typeof__(a) _ane_a = (a); \
237 __typeof__(b) _ane_b = (b); \
238 if (UNLIKELY(_ane_a == _ane_b)) { \
239 fprintf(stderr, \
240 "%s:%d [%s]: '%s != %s' failed" \
241 " (both %td).\n", \
242 __FILE__, __LINE__, __func__, #a, #b, (ptrdiff_t)_ane_a); \
243 exit(1); \
244 } \
245 } while (0)
246
248#define ASSERT_RANGE(val, min, max) \
249 do { \
250 __typeof__(val) _ar_v = (val); \
251 __typeof__(min) _ar_min = (min); \
252 __typeof__(max) _ar_max = (max); \
253 if (UNLIKELY(_ar_v < _ar_min || _ar_v > _ar_max)) { \
254 fprintf(stderr, "%s:%d [%s]: %s=%td not in [%td, %td].\n", __FILE__, __LINE__, __func__, #val, \
255 (ptrdiff_t)_ar_v, (ptrdiff_t)_ar_min, (ptrdiff_t)_ar_max); \
256 exit(1); \
257 } \
258 } while (0)
259
260#else /* MSVC or unknown — typeof unavailable */
261
262#define ASSERT_EQ(a, b) \
263 do { \
264 intptr_t _aeq_a = (intptr_t)(a); \
265 intptr_t _aeq_b = (intptr_t)(b); \
266 if (UNLIKELY(_aeq_a != _aeq_b)) { \
267 fprintf(stderr, \
268 "%s:%d [%s]: '%s == %s' failed" \
269 " (%td != %td).\n", \
270 __FILE__, __LINE__, __func__, #a, #b, _aeq_a, _aeq_b); \
271 exit(1); \
272 } \
273 } while (0)
274
275#define ASSERT_NE(a, b) \
276 do { \
277 intptr_t _ane_a = (intptr_t)(a); \
278 intptr_t _ane_b = (intptr_t)(b); \
279 if (UNLIKELY(_ane_a == _ane_b)) { \
280 fprintf(stderr, \
281 "%s:%d [%s]: '%s != %s' failed" \
282 " (both %td).\n", \
283 __FILE__, __LINE__, __func__, #a, #b, _ane_a); \
284 exit(1); \
285 } \
286 } while (0)
287
288#define ASSERT_RANGE(val, min, max) \
289 do { \
290 intptr_t _ar_v = (intptr_t)(val); \
291 intptr_t _ar_min = (intptr_t)(min); \
292 intptr_t _ar_max = (intptr_t)(max); \
293 if (UNLIKELY(_ar_v < _ar_min || _ar_v > _ar_max)) { \
294 fprintf(stderr, "%s:%d [%s]: %s=%td not in [%td, %td].\n", __FILE__, __LINE__, __func__, #val, _ar_v, \
295 _ar_min, _ar_max); \
296 exit(1); \
297 } \
298 } while (0)
299
300#endif /* SOLIDC_GCC || SOLIDC_CLANG */
301
302#else /* NDEBUG — strip all runtime assertions */
303
304#define ASSERT(cond) UNUSED(cond)
305#define ASSERT_TRUE(cond) UNUSED(cond)
306#define ASSERT_NULL(ptr) UNUSED(ptr)
307#define ASSERT_NOT_NULL(ptr) UNUSED(ptr)
308#define ASSERT_STR_EQ(a, b) (UNUSED(a), UNUSED(b))
309#define ASSERT_FLOAT_EQ(a, b, e) (UNUSED(a), UNUSED(b), UNUSED(e))
310#define ASSERT_EQ(a, b) (UNUSED(a), UNUSED(b))
311#define ASSERT_NE(a, b) (UNUSED(a), UNUSED(b))
312#define ASSERT_RANGE(v, lo, hi) (UNUSED(v), UNUSED(lo), UNUSED(hi))
313
314#endif /* NDEBUG */
315
316/* =========================================================================
317 * POWER-OF-2 UTILITIES
318 * ========================================================================= */
319
321#define IS_POWER_OF_2(n) ((n) > 0 && (((n) & ((n) - 1)) == 0))
322
324#define STATIC_CHECK_POWER_OF_2(n) STATIC_ASSERT(IS_POWER_OF_2(n), #n " must be a power of 2")
325
333static inline uint64_t round_up_pow2_u64(uint64_t x) {
334 if (x == 0) return 1;
335 x--;
336 x |= x >> 1;
337 x |= x >> 2;
338 x |= x >> 4;
339 x |= x >> 8;
340 x |= x >> 16;
341 x |= x >> 32;
342 return x + 1;
343}
344
350static inline uint32_t round_up_pow2_u32(uint32_t x) {
351 if (x == 0) return 1;
352 x--;
353 x |= x >> 1;
354 x |= x >> 2;
355 x |= x >> 4;
356 x |= x >> 8;
357 x |= x >> 16;
358 return x + 1;
359}
360
361/* =========================================================================
362 * MEMORY AND ARRAY UTILITIES
363 * ========================================================================= */
364
366#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
367
369#define ZERO_MEMORY(ptr, size) memset((ptr), 0, (size))
370
372#define ZERO_STRUCT(s) memset(&(s), 0, sizeof(s))
373
375#define ZERO_ARRAY(arr) memset((arr), 0, sizeof(arr))
376
377/* =========================================================================
378 * MATHEMATICAL UTILITIES
379 * ========================================================================= */
380
382#define MIN(a, b) ((a) < (b) ? (a) : (b))
383
385#define MAX(a, b) ((a) > (b) ? (a) : (b))
386
388#define ABS(x) ((x) < 0 ? -(x) : (x))
389
391#define CLAMP(x, lo, hi) (MIN(MAX((x), (lo)), (hi)))
392
393/* =========================================================================
394 * BIT MANIPULATION
395 * ========================================================================= */
396
398#define SET_BIT(v, pos) ((v) |= (1ULL << (pos)))
399
401#define CLEAR_BIT(v, pos) ((v) &= ~(1ULL << (pos)))
402
404#define TOGGLE_BIT(v, pos) ((v) ^= (1ULL << (pos)))
405
407#define CHECK_BIT(v, pos) (((v) >> (pos)) & 1U)
408
409/* =========================================================================
410 * ALIGNMENT UTILITIES
411 *
412 * `align` must be a power of two for all three macros.
413 * ========================================================================= */
414
416#define ALIGN_UP(x, align) (((x) + (align) - 1) & ~((align) - 1))
417
419#define ALIGN_DOWN(x, align) ((x) & ~((align) - 1))
420
422#define IS_ALIGNED(x, align) (((x) & ((align) - 1)) == 0)
423
424/* =========================================================================
425 * STRING UTILITIES
426 * ========================================================================= */
427
429#define STREQ(a, b) (strcmp((a), (b)) == 0)
430
432#define STRNEQ(a, b, n) (strncmp((a), (b), (n)) == 0)
433
435#define STR_EMPTY(s) ((s) == NULL || (s)[0] == '\0')
436
438#define STR_NOT_EMPTY(s) ((s) != NULL && (s)[0] != '\0')
439
440/* =========================================================================
441 * DEBUGGING AND LOGGING
442 *
443 * DEBUG_* macros compile to no-ops when NDEBUG is defined.
444 * LOG_* macros are always active.
445 * ========================================================================= */
446
447#ifndef NDEBUG
448
450#define DEBUG_PRINT(fmt, ...) printf("[DEBUG] %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
451
453#define DEBUG_VAR(var) printf("[DEBUG] %s:%d: %s = %td\n", __FILE__, __LINE__, #var, (ptrdiff_t)(var))
454
456#define DEBUG_STR(str) printf("[DEBUG] %s:%d: %s = \"%s\"\n", __FILE__, __LINE__, #str, (str) ? (str) : "(null)")
457
458#else /* NDEBUG */
459
460#define DEBUG_PRINT(fmt, ...) ((void)0)
461#define DEBUG_VAR(var) UNUSED(var)
462#define DEBUG_STR(str) UNUSED(str)
463
464#endif /* NDEBUG */
465
467#define LOG_ERROR(fmt, ...) \
468 fprintf(stderr, "[ERROR] %s:%d [%s]: " fmt "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
469
471#define LOG_WARN(fmt, ...) fprintf(stderr, "[WARN] %s:%d [%s]: " fmt "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
472
474#define LOG_INFO(fmt, ...) printf("[INFO] %s:%d [%s]: " fmt "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
475
476/* =========================================================================
477 * CONTAINER ITERATION
478 *
479 * FOR_EACH_ARRAY and FOR_EACH_RANGE use __typeof__ under GCC/Clang for full
480 * type safety. Under MSVC they fall back to explicit pointer/index types;
481 * callers must ensure the array element type is compatible.
482 * ========================================================================= */
483
484#if SOLIDC_GCC || SOLIDC_CLANG
485
494#define FOR_EACH_ARRAY(item, array) \
495 for (__typeof__(&(array)[0]) item = (array), _fea_end = (array) + ARRAY_SIZE(array); item < _fea_end; ++item)
496
503#define FOR_EACH_RANGE(var, start, end) for (__typeof__(start) var = (start); var < (end); ++var)
504
505#else /* MSVC — typeof unavailable */
506
516#define FOR_EACH_ARRAY(item, array) \
517 for (void *item = (void*)(array), *_fea_end = (void*)((char*)(array) + sizeof(array)); item < _fea_end; \
518 item = (char*)item + (sizeof((array)[0])))
519
520#define FOR_EACH_RANGE(var, start, end) for (ptrdiff_t var = (ptrdiff_t)(start); var < (ptrdiff_t)(end); ++var)
521
522#endif /* SOLIDC_GCC || SOLIDC_CLANG */
523
524/* =========================================================================
525 * TIMING AND PERFORMANCE
526 *
527 * Two families of macros:
528 *
529 * TIME_BLOCK(name, block) — measure a block and print elapsed seconds.
530 * TIME_BLOCK_MS(name, block) — measure a block and print elapsed milliseconds.
531 *
532 * `block` is an unquoted sequence of statements placed verbatim before the
533 * stop-time call, e.g.:
534 *
535 * TIME_BLOCK("sort", sort(arr, n);)
536 *
537 * get_time_ns() — returns current monotonic time in nanoseconds as uint64_t.
538 * TIME_DIFF / TIME_DIFF_MS — compute elapsed seconds / milliseconds between
539 * two timestamps captured with the platform-specific API.
540 * ========================================================================= */
541
542#if defined(_WIN32)
543
544#include <windows.h> /* LARGE_INTEGER, QueryPerformanceCounter, etc. */
545
550#define TIME_DIFF(start, end, freq) ((double)((end).QuadPart - (start).QuadPart) / (double)(freq).QuadPart)
551
556#define TIME_DIFF_MS(start, end, freq) ((double)((end).QuadPart - (start).QuadPart) * 1000.0 / (double)(freq).QuadPart)
557
558#define TIME_BLOCK(name, block) \
559 do { \
560 LARGE_INTEGER _freq, _t0, _t1; \
561 QueryPerformanceFrequency(&_freq); \
562 QueryPerformanceCounter(&_t0); \
563 block QueryPerformanceCounter(&_t1); \
564 printf("Time[%s]: %.6f s\n", (name), TIME_DIFF(_t0, _t1, _freq)); \
565 } while (0)
566
567#define TIME_BLOCK_MS(name, block) \
568 do { \
569 LARGE_INTEGER _freq, _t0, _t1; \
570 QueryPerformanceFrequency(&_freq); \
571 QueryPerformanceCounter(&_t0); \
572 block QueryPerformanceCounter(&_t1); \
573 printf("Time[%s]: %.3f ms\n", (name), TIME_DIFF_MS(_t0, _t1, _freq)); \
574 } while (0)
575
576#else /* POSIX — use clock_gettime(CLOCK_MONOTONIC) */
577
581#define TIME_DIFF(start, end) \
582 ((double)((end).tv_sec - (start).tv_sec) + (double)((end).tv_nsec - (start).tv_nsec) / 1.0e9)
583
588#define TIME_DIFF_MS(start, end) \
589 ((double)((end).tv_sec - (start).tv_sec) * 1000.0 + (double)((end).tv_nsec - (start).tv_nsec) / 1.0e6)
590
591#define TIME_BLOCK(name, block) \
592 do { \
593 struct timespec _t0, _t1; \
594 clock_gettime(CLOCK_MONOTONIC, &_t0); \
595 block clock_gettime(CLOCK_MONOTONIC, &_t1); \
596 printf("Time[%s]: %.6f s\n", (name), TIME_DIFF(_t0, _t1)); \
597 } while (0)
598
599#define TIME_BLOCK_MS(name, block) \
600 do { \
601 struct timespec _t0, _t1; \
602 clock_gettime(CLOCK_MONOTONIC, &_t0); \
603 block clock_gettime(CLOCK_MONOTONIC, &_t1); \
604 printf("Time[%s]: %.3f ms\n", (name), TIME_DIFF_MS(_t0, _t1)); \
605 } while (0)
606
607#endif /* _WIN32 */
608
618static inline uint64_t get_time_ns(void) {
619#if defined(_WIN32)
620 LARGE_INTEGER freq, counter;
621 QueryPerformanceFrequency(&freq);
622 QueryPerformanceCounter(&counter);
623 /* Split into whole seconds and remainder to avoid 64-bit overflow. */
624 uint64_t seconds = (uint64_t)counter.QuadPart / (uint64_t)freq.QuadPart;
625 uint64_t remainder = (uint64_t)counter.QuadPart % (uint64_t)freq.QuadPart;
626 return seconds * UINT64_C(1000000000) + (remainder * UINT64_C(1000000000)) / (uint64_t)freq.QuadPart;
627#else
628 struct timespec ts;
629 clock_gettime(CLOCK_MONOTONIC, &ts);
630 return (uint64_t)ts.tv_sec * UINT64_C(1000000000) + (uint64_t)ts.tv_nsec;
631#endif
632}
633
634/* =========================================================================
635 * POSIX COMPATIBILITY SHIMS (MSVC)
636 *
637 * strtok_r, gmtime_r, localtime_r are POSIX functions absent on Windows.
638 * Their MSVC equivalents exist but use a reversed argument order for the
639 * reentrant time functions.
640 * ========================================================================= */
641
642#if SOLIDC_MSVC
643
645#define strtok_r(str, delim, saveptr) strtok_s((str), (delim), (saveptr))
646
653static inline struct tm* gmtime_r(const time_t* timep, struct tm* result) {
654 return gmtime_s(result, timep) == 0 ? result : NULL;
655}
656
663static inline struct tm* localtime_r(const time_t* timep, struct tm* result) {
664 return localtime_s(result, timep) == 0 ? result : NULL;
665}
666
667#endif /* SOLIDC_MSVC */
668
669#if defined(__cplusplus)
670}
671#endif
672
673#endif /* SOLIDC_MACROS_H */