Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
utils.h
Go to the documentation of this file.
1/* -*- C -*-
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <[email protected]>
4 *
5 * This library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19#pragma once
20
21// Global master switch for local debugging (default off).
22// Read more on this further down
23
24#include <limits.h>
25#include <stdarg.h>
26#include <stdatomic.h>
27#include <stddef.h>
28#include <stdint.h>
29#include <stdio.h>
30
31#ifndef SERENE_DEBUG
32# define SERENE_DEBUG 0
33#endif
34
35// SSE2 intrinsic instructions.
36// while this is not portable, if __SSE2__ is defined then the compiler
37// and platform already supports it
38#if defined(__SSE2__)
39# include <emmintrin.h>
40#endif
41
42typedef struct srn_context_t srn_context_t;
43typedef struct srn_mm_t srn_mm_t;
44
45#define UNUSED(x) (void)(x)
46
47// -----------------------------------------------------------------------------
48// Panic Helpers
49// -----------------------------------------------------------------------------
50[[noreturn]]
51void srn_panic(const char *msg, const char *file, size_t line, const char *fn);
52
53#define PANIC(msg) srn_panic(msg, __FILE__, __LINE__, __func__)
54#define TODO(msg) PANIC(msg)
55#define PANIC_WITH_CTX(ctx, msg) \
56 srn_context_release(ctx); \
57 PANIC(msg)
58
59#define PANIC_IF(cond, msg) \
60 do { \
61 if (cond) { \
62 PANIC(msg); \
63 } \
64 } while (0)
65
66#define PANIC_IF_NULL(ptr) PANIC_IF((ptr) == nullptr, "Null pointer")
67#define RETURN_IF_NULL(ptr) \
68 if ((ptr) == nullptr) { \
69 return; \
70 }
71
72#define SNPRINTF(buf, s, f, ...) \
73 do { \
74 int i = snprintf(buf, (s) + 1, f, __VA_ARGS__); \
75 if (i < 0) { \
76 PANIC("[snprintf] A negative return value"); \
77 } \
78 if (i >= (int)(s)) { \
79 PANIC("[snprintf] Written bytes are more than expected"); \
80 } \
81 } while (0)
82
83#define SHOULD_NOT_HAPPEN PANIC("This should not happen")
84#define PANIC_ON_ERR(interr) \
85 if ((interr) < 0) { \
86 SHOULD_NOT_HAPPEN; \
87 }
88
89// -----------------------------------------------------------------------------
90// Development Debugging helpers
91// These helpers are for local development only and can be turned on by setting
92// the `SERENE_DEBUG` flag to `1`.
93//
94// These helpers are pretty much manual, meaning that developers should edit
95// the values if they want different behaviour.
96//
97// Each module should define their own debug wrapper macro for example, in gc.h:
98//
99// #define GC_LOG(FMT, ...) DBG("GC", FMT __VA_OPT__(, ) __VA_ARGS__)
100//
101// IMPORTANT NOTES:
102// - DO NOT USE THE FUNCTIONS DIRECTLY, instead use the debugging macros.
103// - These functions are NOT thread safe, just because we don't need them to.
104// -----------------------------------------------------------------------------
105#if defined(__clang__) || defined(__GNUC__)
106# define SRN_ATTR_COLD [[gnu::cold]]
107# define SRN_ATTR_FMT(a, b) [[gnu::format(__printf__, a, b)]]
108#else
109# define SRN_ATTR_COLD
110# define SRN_ATTR_FMT(a, b)
111#endif
112
113// These helpers are available only in debug mode
114#if SERENE_DEBUG
115
116/// default output
117# define SRN_DBG_OUT stdout
118
119SRN_ATTR_COLD static inline void
120srn_dbg_prefix(FILE *out, const char *prefix, const char *file, int line, const char *func) {
121
122 int res = fprintf(out, "[%s]%s:%d:(%s): ", prefix == nullptr ? "SRN" : prefix, file, line, func);
123 if (res < 0) {
125 }
126}
127
128SRN_ATTR_COLD static inline void srn_dbg_vlog(
129 FILE *out, const char *prefix, const char *file, int line, const char *func, const char *fmt,
130 va_list ap
131) {
132 // atomic line writes
133 srn_dbg_prefix(out, prefix, file, line, func);
134 PANIC_ON_ERR(vfprintf(out, fmt, ap));
135 PANIC_ON_ERR(fputc('\n', out));
136}
137
138SRN_ATTR_COLD SRN_ATTR_FMT(6, 7) static inline void srn_dbg_log(
139 FILE *out, const char *prefix, const char *file, int line, const char *func, const char *fmt, ...
140) {
141
142 va_list ap;
143 va_start(ap, fmt);
144 srn_dbg_vlog(out, prefix, file, line, func, fmt, ap);
145 va_end(ap);
146}
147
148SRN_ATTR_COLD static inline void srn_dbg_hexdump_here(
149 FILE *out, const char *file, int line, const char *func, const void *data, size_t len
150) {
151 const unsigned char *p = (const unsigned char *)data;
152 srn_dbg_prefix(out, "Hexdump", file, line, func);
153 UNUSED(fprintf(out, "hexdump %zu bytes\n", len));
154 for (size_t i = 0; i < len; i += 16) {
155 UNUSED(fprintf(out, " %08zx ", i));
156 for (size_t j = 0; j < 16; ++j) {
157 if (i + j < len) {
158 UNUSED(fprintf(out, "%02x ", p[i + j]));
159 } else {
160 UNUSED(fputs(" ", out));
161 }
162 }
163
164 UNUSED(fputs(" ", out));
165 for (size_t j = 0; j < 16 && i + j < len; ++j) {
166 unsigned char c = p[i + j];
167 UNUSED(fputc((c >= 32 && c < 127) ? c : '.', out));
168 }
169 UNUSED(fputc('\n', out));
170 }
171}
172
173# define DBG(PREFIX, FMT, ...) \
174 srn_dbg_log(SRN_DBG_OUT, PREFIX, __FILE__, __LINE__, __func__, FMT __VA_OPT__(, ) __VA_ARGS__)
175# define DBG_HEX(PTR, LEN) \
176 srn_dbg_hexdump_here(SRN_DBG_OUT, __FILE__, __LINE__, __func__, (PTR), (LEN))
177
178#else // if SERENE_DEBUG
179# define DBG(...) \
180 do { \
181 } while (0)
182# define DBG_HEX(...) \
183 do { \
184 } while (0)
185# define DBG_ASSERT(x) \
186 do { \
187 (void)sizeof(x); \
188 } while (0)
189#endif // if SERENE_DEBUG
190
191// -----------------------------------------------------------------------------
192// Popcount helpers
193// -----------------------------------------------------------------------------
194static inline uint32_t srn_popcnt32(uint32_t x) {
195#if defined(__clang__) || defined(__GNUC__)
196 return (uint32_t)__builtin_popcount(x);
197#else
198 x = x - ((x >> 1) & 0x55555555u);
199 x = (x & 0x33333333u) + ((x >> 2) & 0x33333333u);
200 return (((x + (x >> 4)) & 0x0F0F0F0Fu) * 0x01010101u) >> 24;
201#endif
202}
203
204static inline uint64_t srn_popcnt64(uint64_t x) {
205#if defined(__GNUC__) || defined(__clang__)
206 return (uint64_t)__builtin_popcountll(x);
207#else
208 unsigned c = 0;
209 while (x) {
210 x &= x - 1;
211 ++c;
212 }
213 return c;
214#endif
215}
216
217// -----------------------------------------------------------------------------
218// Generic Helpers
219// -----------------------------------------------------------------------------
220/// A portable way to use pasue instructions.
221/// The long list of preprocessors is there thanks to:
222/// https://github.com/goodcleanfun/cpu_relax
223static inline void cpu_relax(void) {
224#if defined(_M_IX86) || defined(__I86__) || defined(i686) || defined(__i686) || \
225 defined(__i686__) || defined(i586) || defined(__i586) || defined(__i586__) || defined(i486) || \
226 defined(__i486) || defined(__i486__) || defined(i386) || defined(__i386) || defined(__i386__) || \
227 defined(_X86_) || defined(__X86__) || defined(__THW_INTEL__)
228# if defined(__SSE2__)
229 _mm_pause();
230# elif defined(_MSC_VER)
231 __asm pause;
232# else
233 __asm__ __volatile__("pause");
234# endif
235#elif defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \
236 defined(_M_X64) || defined(_M_AMD64)
237# if defined(__SSE2__)
238 _mm_pause();
239# endif
240#elif defined(__aarch64__) || defined(_M_ARM64)
241# if defined(_MSC_VER)
242 __isb(_ARM64_BARRIER_SY);
243# else
244 __asm__ __volatile__("isb\n");
245# endif
246#elif defined(__ARM_ARCH) || defined(_M_ARM) || defined(__arm__) || defined(__thumb__) || \
247 defined(__TARGET_ARCH_ARM) || defined(_ARM)
248# if defined(_MSC_VER)
249 __yield();
250# else
251 __asm__ __volatile__("yield");
252# endif
253#elif defined(__powerpc) || defined(__powerpc__) || defined(__POWERPC__) || defined(__ppc__) || \
254 defined(__PPC__) || defined(_ARCH_PPC) || defined(__ppc)
255 __asm__ __volatile__("or 27,27,27" ::: "memory");
256#elif defined(__riscv) || defined(__riscv__)
257# if defined(__riscv_pause)
258 __builtin_riscv_pause();
259# else
260 __asm__ __volatile__("nop");
261# endif
262#elif defined(__loongarch32) || defined(__loongarch64)
263 __asm__ __volatile__("nop");
264#elif defined(__wasm__)
265 __asm__ __volatile__("nop");
266#endif
267}
268
269// -----------------------------------------------------------------------------
270// Simple spinlock
271// -----------------------------------------------------------------------------
272typedef struct srn_spinlock_t {
273 atomic_flag flag;
275
276static inline void srn_spinlock_unlock(srn_spinlock_t *lock) {
277 atomic_flag_clear_explicit(&lock->flag, memory_order_release);
278}
279
280static inline void srn_spinlock_init(srn_spinlock_t *lock) {
281 lock->flag = (atomic_flag)ATOMIC_FLAG_INIT;
283}
284
285static inline void srn_spinlock_lock(srn_spinlock_t *lock) {
286 while (atomic_flag_test_and_set_explicit(&lock->flag, memory_order_acquire)) {
287 // Keep busy for the lock to get unlocked
288 cpu_relax();
289 }
290}
291
292// -----------------------------------------------------------------------------
293// Strings and buffers related helpers
294// -----------------------------------------------------------------------------
295char *srn_copy_bytes(const srn_context_t *ctx, const char *src, size_t len, size_t alignment);
296
297/// Copy a null terminated C string into a fresh allocation made through
298/// `mm`. Returns nullptr on allocation failure.
299[[gnu::nonnull(1, 2)]]
300char *srn_dup_str(srn_mm_t *mm, const char *s);
301
302// -----------------------------------------------------------------------------
303// Time helpers
304// -----------------------------------------------------------------------------
305/// nanoseconds in one second
306#define SRN_NS_PER_SEC 1000000000ULL
307/// nanoseconds in one millisecond
308#define SRN_NS_PER_MS 1000000ULL
309
310/// The current time on the monotonic clock, in nanoseconds. Panics if the clock
311/// read fails -- `CLOCK_MONOTONIC` with a valid buffer cannot fail on supported
312/// targets, so a failure means something is deeply wrong.
313uint64_t srn_now_ns(void);
314
315// -----------------------------------------------------------------------------
316// Limit finder helpers
317// -----------------------------------------------------------------------------
318
319#define TYPE_BITS(T) (sizeof(T) * CHAR_BIT)
320#define MAX_POW2_EXP_FOR_UNSIGNED(T) (TYPE_BITS(T) - 1)
static int const char * fmt
Definition acutest.h:522
int const char * file
Definition acutest.h:779
va_end(args)
void int line
Definition acutest.h:732
va_start(args, fmt)
Main memory manager structure that will own all the allocated blocks and data.
Definition interface.h:107
atomic_flag flag
Definition utils.h:273
static void srn_spinlock_lock(srn_spinlock_t *lock)
Definition utils.h:285
uint64_t srn_now_ns(void)
The current time on the monotonic clock, in nanoseconds.
Definition utils.c:46
#define SRN_ATTR_FMT(a, b)
Definition utils.h:110
#define SHOULD_NOT_HAPPEN
Definition utils.h:83
static void srn_spinlock_unlock(srn_spinlock_t *lock)
Definition utils.h:276
#define SRN_ATTR_COLD
Definition utils.h:109
char * srn_copy_bytes(const srn_context_t *ctx, const char *src, size_t len, size_t alignment)
Definition utils.c:56
#define UNUSED(x)
Definition utils.h:45
static uint32_t srn_popcnt32(uint32_t x)
Definition utils.h:194
static void srn_spinlock_init(srn_spinlock_t *lock)
Definition utils.h:280
static uint64_t srn_popcnt64(uint64_t x)
Definition utils.h:204
static void cpu_relax(void)
A portable way to use pasue instructions.
Definition utils.h:223
char * srn_dup_str(srn_mm_t *mm, const char *s)
Copy a null terminated C string into a fresh allocation made through mm.
Definition utils.c:70
void srn_panic(const char *msg, const char *file, size_t line, const char *fn)
TODO(lxsameer): unwind the stack.
Definition utils.c:31
#define PANIC_ON_ERR(interr)
Definition utils.h:84