Serene Runtime 1.0.0
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#include <stdatomic.h>
24#include <stdint.h>
25#ifndef SERENE_DEBUG
26# define SERENE_DEBUG 0
27#endif
28
29#include <stdarg.h>
30#include <stddef.h>
31#include <stdio.h>
32
33// SSE2 intrinsic instructions.
34// while this is not portable, if __SSE2__ is defined then the compiler
35// and platform already supports it
36#if defined(__SSE2__)
37# include <emmintrin.h>
38#endif
39
40typedef struct srn_context_t srn_context_t;
41typedef struct srn_mm_t srn_mm_t;
42
43#define UNUSED(x) (void)(x)
44
45// -----------------------------------------------------------------------------
46// Panic Helpers
47// -----------------------------------------------------------------------------
48[[noreturn]] void srn_panic(const char *msg, const char *file, size_t line,
49 const char *fn);
50
51#define PANIC(msg) srn_panic(msg, __FILE__, __LINE__, __func__)
52#define TODO(msg) PANIC(msg)
53#define PANIC_WITH_CTX(ctx, msg) \
54 srn_context_release(ctx); \
55 PANIC(msg)
56
57#define PANIC_IF(cond, msg) \
58 do { \
59 if (cond) { \
60 PANIC(msg); \
61 } \
62 } while (0)
63
64#define PANIC_IF_NULL(ptr) PANIC_IF((ptr) == nullptr, "Null pointer")
65
66#define SNPRINTF(buf, s, f, ...) \
67 do { \
68 int i = snprintf(buf, (s) + 1, f, __VA_ARGS__); \
69 if (i < 0) { \
70 PANIC("[snprintf] A negative return value"); \
71 } \
72 if (i >= (int)(s)) { \
73 PANIC("[snprintf] Written bytes are more than expected"); \
74 } \
75 } while (0)
76
77#define SHOULD_NOT_HAPPEN PANIC("This should not happen")
78#define PANIC_ON_ERR(interr) \
79 if ((interr) < 0) { \
80 SHOULD_NOT_HAPPEN; \
81 }
82
83// -----------------------------------------------------------------------------
84// Development Debugging helpers
85// These helpers are for local development only and can be turned on by setting
86// the `SERENE_DEBUG` flag to `1`.
87//
88// These helpers are pretty much manual, meaning that developers should edit
89// the values if they want different behaviour.
90//
91// Each module should define their own debug wrapper macro for example, in gc.h:
92//
93// #define GC_LOG(FMT, ...) DBG("GC", FMT __VA_OPT__(, ) __VA_ARGS__)
94//
95// IMPORTANT NOTES:
96// - DO NOT USE THE FUNCTIONS DIRECTLY, instead use the debugging macros.
97// - These functions are NOT thread safe, just because we don't need them to.
98// -----------------------------------------------------------------------------
99#if defined(__clang__) || defined(__GNUC__)
100# define SRN_ATTR_COLD [[gnu::cold]]
101# define SRN_ATTR_FMT(a, b) [[gnu::format(__printf__, a, b)]]
102#else
103# define SRN_ATTR_COLD
104# define SRN_ATTR_FMT(a, b)
105#endif
106
107// These helpers are available only in debug mode
108#if SERENE_DEBUG
109
110/// default output
111# define SRN_DBG_OUT stdout
112
113SRN_ATTR_COLD static inline void srn_dbg_prefix(FILE *out, const char *prefix,
114 const char *file, int line,
115 const char *func) {
116
117 int res = fprintf(out, "[%s]%s:%d:(%s): ", prefix == nullptr ? "SRN" : prefix,
118 file, line, func);
119 if (res < 0) {
121 }
122}
123
124SRN_ATTR_COLD static inline void srn_dbg_vlog(FILE *out, const char *prefix,
125 const char *file, int line,
126 const char *func, const char *fmt,
127 va_list ap) {
128 // atomic line writes
129 srn_dbg_prefix(out, prefix, file, line, func);
130 PANIC_ON_ERR(vfprintf(out, fmt, ap));
131 PANIC_ON_ERR(fputc('\n', out));
132}
133
134SRN_ATTR_COLD SRN_ATTR_FMT(6, 7) static inline void srn_dbg_log(
135 FILE *out, const char *prefix, const char *file, int line, const char *func,
136 const char *fmt, ...) {
137
138 va_list ap;
139 va_start(ap, fmt);
140 srn_dbg_vlog(out, prefix, file, line, func, fmt, ap);
141 va_end(ap);
142}
143
144SRN_ATTR_COLD static inline void
145srn_dbg_hexdump_here(FILE *out, const char *file, int line, const char *func,
146 const void *data, size_t len) {
147 const unsigned char *p = (const unsigned char *)data;
148 srn_dbg_prefix(out, "Hexdump", file, line, func);
149 UNUSED(fprintf(out, "hexdump %zu bytes\n", len));
150 for (size_t i = 0; i < len; i += 16) {
151 UNUSED(fprintf(out, " %08zx ", i));
152 for (size_t j = 0; j < 16; ++j) {
153 if (i + j < len) {
154 UNUSED(fprintf(out, "%02x ", p[i + j]));
155 } else {
156 UNUSED(fputs(" ", out));
157 }
158 }
159
160 UNUSED(fputs(" ", out));
161 for (size_t j = 0; j < 16 && i + j < len; ++j) {
162 unsigned char c = p[i + j];
163 UNUSED(fputc((c >= 32 && c < 127) ? c : '.', out));
164 }
165 UNUSED(fputc('\n', out));
166 }
167}
168
169# define DBG(PREFIX, FMT, ...) \
170 srn_dbg_log(SRN_DBG_OUT, PREFIX, __FILE__, __LINE__, __func__, \
171 FMT __VA_OPT__(, ) __VA_ARGS__)
172# define DBG_HEX(PTR, LEN) \
173 srn_dbg_hexdump_here(SRN_DBG_OUT, __FILE__, __LINE__, __func__, (PTR), \
174 (LEN))
175
176#else // if SERENE_DEBUG
177# define DBG(...) \
178 do { \
179 } while (0)
180# define DBG_HEX(...) \
181 do { \
182 } while (0)
183# define DBG_ASSERT(x) \
184 do { \
185 (void)sizeof(x); \
186 } while (0)
187#endif // if SERENE_DEBUG
188
189// -----------------------------------------------------------------------------
190// Popcount helpers
191// -----------------------------------------------------------------------------
192static inline uint32_t srn_popcnt32(uint32_t x) {
193#if defined(__clang__) || defined(__GNUC__)
194 return (uint32_t)__builtin_popcount(x);
195#else
196 x = x - ((x >> 1) & 0x55555555u);
197 x = (x & 0x33333333u) + ((x >> 2) & 0x33333333u);
198 return (((x + (x >> 4)) & 0x0F0F0F0Fu) * 0x01010101u) >> 24;
199#endif
200}
201
202static inline uint64_t srn_popcnt64(uint64_t x) {
203#if defined(__GNUC__) || defined(__clang__)
204 return (uint64_t)__builtin_popcountll(x);
205#else
206 unsigned c = 0;
207 while (x) {
208 x &= x - 1;
209 ++c;
210 }
211 return c;
212#endif
213}
214
215// -----------------------------------------------------------------------------
216// Generic Helpers
217// -----------------------------------------------------------------------------
218/// A portable way to use pasue instructions.
219/// The long list of preprocessors is there thanks to:
220/// https://github.com/goodcleanfun/cpu_relax
221static inline void cpu_relax(void) {
222#if defined(_M_IX86) || defined(__I86__) || defined(i686) || \
223 defined(__i686) || defined(__i686__) || defined(i586) || \
224 defined(__i586) || defined(__i586__) || defined(i486) || \
225 defined(__i486) || defined(__i486__) || defined(i386) || \
226 defined(__i386) || defined(__i386__) || defined(_X86_) || \
227 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__) || \
236 defined(__x86_64) || 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__) || \
247 defined(__thumb__) || 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__) || \
254 defined(__ppc__) || defined(__PPC__) || defined(_ARCH_PPC) || \
255 defined(__ppc)
256 __asm__ __volatile__("or 27,27,27" ::: "memory");
257#elif defined(__riscv) || defined(__riscv__)
258# if defined(__riscv_pause)
259 __builtin_riscv_pause();
260# else
261 __asm__ __volatile__("nop");
262# endif
263#elif defined(__loongarch32) || defined(__loongarch64)
264 __asm__ __volatile__("nop");
265#elif defined(__wasm__)
266 __asm__ __volatile__("nop");
267#endif
268}
269
270// -----------------------------------------------------------------------------
271// Simple spinlock
272// -----------------------------------------------------------------------------
273typedef struct srn_spinlock_t {
274 atomic_flag flag;
276
277static inline void srn_spinlock_unlock(srn_spinlock_t *lock) {
278 atomic_flag_clear_explicit(&lock->flag, memory_order_release);
279}
280
281static inline void srn_spinlock_init(srn_spinlock_t *lock) {
282 lock->flag = (atomic_flag)ATOMIC_FLAG_INIT;
284}
285
286static inline void srn_spinlock_lock(srn_spinlock_t *lock) {
287 while (atomic_flag_test_and_set_explicit(&lock->flag, memory_order_acquire)) {
288 // Keep busy for the lock to get unlocked
289 cpu_relax();
290 }
291}
292
293// -----------------------------------------------------------------------------
294// Strings and buffers related helpers
295// -----------------------------------------------------------------------------
296char *srn_copy_bytes(srn_context_t *ctx, const char *src, size_t len,
297 size_t alignment);
298
299/// Copy a null terminated C string into a fresh allocation made through
300/// `mm`. Returns nullptr on allocation failure.
301[[gnu::nonnull(1, 2)]] char *srn_dup_str(srn_mm_t *mm, const char *s);
static int const char * fmt
Definition acutest.h:535
int const char * file
Definition acutest.h:788
va_end(args)
void int line
Definition acutest.h:743
va_start(args, fmt)
Main memory manager structure that will own all the allocated blocks and data.
Definition interface.h:112
atomic_flag flag
Definition utils.h:274
static void srn_spinlock_lock(srn_spinlock_t *lock)
Definition utils.h:286
#define SRN_ATTR_FMT(a, b)
Definition utils.h:104
char * srn_copy_bytes(srn_context_t *ctx, const char *src, size_t len, size_t alignment)
Definition utils.c:42
#define SHOULD_NOT_HAPPEN
Definition utils.h:77
static void srn_spinlock_unlock(srn_spinlock_t *lock)
Definition utils.h:277
#define SRN_ATTR_COLD
Definition utils.h:103
#define UNUSED(x)
Definition utils.h:43
static uint32_t srn_popcnt32(uint32_t x)
Definition utils.h:192
static void srn_spinlock_init(srn_spinlock_t *lock)
Definition utils.h:281
static uint64_t srn_popcnt64(uint64_t x)
Definition utils.h:202
static void cpu_relax(void)
A portable way to use pasue instructions.
Definition utils.h:221
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:56
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:78