initial commit

Fortune for tracemac's current commit: Small blessing − 小吉
master
Avril 3 years ago
commit e59c3bb618
Signed by: flanchan
GPG Key ID: 284488987C31F630

2
.gitignore vendored

@ -0,0 +1,2 @@
*.a
*.o

@ -0,0 +1,6 @@
Useful macros and trace logging output for C
Headers:
- macros.h - Useful preprocessor defines. A lot use GNU extensions. Include this after all other includes to not poison any other headers.
- ints.h - Uniform naming convention for integer types: i8, u8, i16, u64 ... and for floats: f32, f64, etc.
- trace.h - setting up and managing the logging level. call `trace_init()` to load form env-var, or `trace_init_with()` to set the level explicitly.

@ -0,0 +1,49 @@
#ifndef _INTS_H
#define _INTS_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#define DEF(s, n) typedef s ## int ## n ## _t s ## n
#define DEFINT(n) typedef uint ## n ## _t u ## n; \
typedef int ## n ## _t i ## n
DEFINT(8);
DEFINT(16);
DEFINT(32);
DEFINT(64);
#ifdef __cplusplus
#else
#include <stdbool.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
typedef signed __int128 i128;
typedef unsigned __int128 u128;
#pragma GCC diagnostic pop
#endif
typedef float f32;
typedef double f64;
typedef size_t usize;
typedef ssize_t isize;
typedef bool u1;
typedef bool i1;
typedef uintptr_t ptr_t;
#undef DEFINT
#undef DEF
#ifdef __cplusplus
}
#endif
#endif /* _INTS_H */

@ -0,0 +1,285 @@
/// Macros header
///
/// # Features
/// * `_EVAL_DEBUG_ONLY_STMTS` - Evaluate expressions passed to macros that are no-ops when not in debug mode.
#ifndef _MACROS_H
#define _MACROS_H
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <assert.h>
// Attribute macros
#define _pure __attribute__((const))
#define _readonly __attribute__((pure))
#define _cold __attribute__((cold))
#define _hot __attribute__((hot))
#define _dead __attribute__((unused))
#ifndef __cplusplus
#define noreturn __attribute__((noreturn))
#define _deprecated __attribute__((deprecated))
#define deprecated_message(str) __attribute__((deprecated(str)))
#define _deprecated_msg(str) deprecated_message(str)
#endif
#define noinline __attribute__((noinline))
#define always_inline __attribute__((always_inline))
#define force_inline __attribute__((gnu_inline))
#ifdef DEBUG
#define _mixin static inline force_inline
#else
#define _mixin extern inline force_inline
#endif
#define unrestrict __attribute__((may_alias))
#define _shared unrestrict
#ifdef __cplusplus
#define restrict __restrict__
#endif
// Function conventions
#define _cconv__mixin _mixin
#define _cconv__pure _pure
#define _cconv__readonly _readonly
#define _cconv__cold _cold
#define _cconv__dead _dead
#define _cconv__unused _dead
#define _cconv__hot _hot
#define _cconv__entry __attribute__((constructor))
#define _cconv__exit __attribute__((destructor))
// Allocates
#define _cconv__alloc __attribute__((malloc))
// Infallible alloc
#define _cconv__ialloc __attribute__((malloc)) __attribute__((returns_nonnull))
#define _cconv__nonnull __attribute__((returns_nonnull))
#define _callcv(name) _cconv__ ## name
#define _callconv(name) _callcv(name)
#define _callat_entry _callcv(entry)
#define _callat_exit _callcv(exit)
#define _callat(when) _callat_ ## when
// Argument attribute
// Used like: `int *pOUT output, const int *pIN input`
#define pOUT restrict
#define pIN const restrict
#define pINOUT restrict
// Used like: `int _OUT output, const int _IN input`
#define _IN *pIN
#define _OUT *pOUT
#define _INOUT *pINOUT
// Type macros
#define AS(x, t) ((t)(x))
#ifdef __cplusplus
#define let auto
#define var(x) decltype(x)
#else
#define let __auto_type
#define var(x) __typeof((x))
#endif
// Control flow macros
#define LIKELY(x) __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
#define ifL(x) if(LIKELY((x)))
#define ifU(x) if(UNLIKELY((x)))
#define COLD_EXPR(expr) ({ \
goto _cold_expr; \
UNREACHABLE_UNSAFE(); \
_cold_expr: \
__attribute__((cold)); \
expr; \
})
#define HOT_EXPR(expr) ({ \
goto _hot_expr; \
UNREACHABLE_UNSAFE(); \
_hot_expr: \
__attribute__((hot)); \
expr; \
})
// Use like: COLD_BLOCK { ... }
// XXX: I'm not exactly sure if these hacks work.. Should test.
#define COLD_BLOCK if(UNLIKELY(1))
// Use like: HOT_BLOCK { ... }
#define HOT_BLOCK if(LIKELY(1))
/// Equivalent to GNU C `x ?: y` operator.
#define TERN_OR(x, y) ({ let _x = (x); _x ? _x : (y); })
// Statement macros
#define _NO_OP ((void)0)
#define IGNORE(x) ((void)(x))
_mixin void _drain_val(void* x, ...) { IGNORE(x); } // This compiles to no-op on -O0 and -O2+ due to `gnu_inline` (_mixin)
#define _drain(...) _drain_val(NULL, __VA_ARGS__)
#define IGNORE_ALL(...) _drain(__VA_ARGS__)
// Allocation macros
#define box(t) aligned_alloc(_Alignof(t), sizeof(t))
#define box_value(v) ({ let _box = box(var(v)); *_box = (v); _box; })
#define unbox_value(v) ({ let _v = (v); let _res = *_v; free(_v); _res; })
#define stackalloc(t) __builtin_alloca_with_align(sizeof(t), _Alignof(t))
// Function macros
// debug-only statements
#ifdef DEBUG
#define DBG(x) x
#else
#define DBG(x) ((void)0)
#endif
// Assertions
#define assert_msg(expr, ...) ( (expr) ? 1 : FATAL(__VA_ARGS__))
#define UNREACHABLE_UNSAFE() __builtin_unreachable()
#ifdef DEBUG
#define UNREACHABLE() do { COLD_EXPR(FATAL("unreachable code entered")); } while(0)
#else
#define UNREACHABLE() UNREACHABLE_UNSAFE()
#endif
#ifdef DEBUG
#define debug_assert(x) assert((x))
#elif defined(_EVAL_DEBUG_ONLY_STMTS)
#define debug_assert(x) IGNORE(x)
#else
#define debug_assert(x) _NO_OP
#endif
#define assert_eq(x,y) assert((x) == (y))
#define assert_ne(x,y) assert((x) != (y))
#define debug_assert_eq(x,y) debug_assert((x) == (y))
#define debug_assert_ne(x,y) debug_assert((x) != (y))
#ifdef DEBUG
#define debug_static_assert(x, msg) _Static_assert((x), msg)
#else
#define debug_static_assert(x, msg) _Static_assert(1, msg)
#endif
#define static_assert_eq(x,y,msg) _Static_assert((x) == (y), msg)
#define static_assert_ne(x,y,msg) _Static_assert((x) != (y), msg)
// Bitmasking
#define _BSWAP(x) __builtin_bswa x
#define BSWAP_16(x) _BSWAP(p16) (AS((x), uint16_t))
#define BSWAP_32(x) _BSWAP(p32) (AS((x), uint32_t))
#define BSWAP_64(x) _BSWAP(p64) (AS((x), uint64_t))
#define BSWAP_128(x) _BSWAP(p128)(AS((x), uint128_t))
#undef _BSWAP
#define _BSWAP(n) __builtin_bswap ## n
#define bswap(x) _Generic((x), \
uint64_t: _BSWAP(64), int64_t: _BSWAP(64), \
uint32_t: _BSWAP(32), int32_t: _BSWAP(32), \
uint16_t: _BSWAP(16), int16_t: _BSWAP(16), \
__uint128_t: _BSWAP(128), __int128_t: _BSWAP(128)) \
((x))
static_assert_eq(bswap(128lu), 9223372036854775808lu, "bswap128 (lu) failed (1)");
static_assert_eq(128lu, bswap(9223372036854775808lu), "bswap128 (lu) failed (2)");
static_assert_eq(bswap(bswap(128lu)), 128, "bswap128 (lu) failed (3)");
// Transmute
#define TRANSMUTE(val, type) ({ union _trans { var(val) input; type output; }; \
_Static_assert(sizeof(var(val)) == sizeof(type), "Cannot transmute values of different sizes"); \
_Static_assert(sizeof(union _trans) == sizeof(type), "Cannot transmute values of different sizes"); \
/* XXX: Do we need do check this for equality? Can we re-word it or do we even need it at all? _Static_assert(_Alignof(union _trans) == _Alignof(type), "Cannot transmute values of different alignments");*/ \
union _trans _trans__value = { .input = (val), }; \
_trans__value.output; })
// Trace message output
#include "trace.h"
#define TRACEx(L, X, msg, ...) (_t_fprintf(TRACE_LEVEL_ ## L, stderr, "[" X "] " __FILE__ ":%d->%s(): " msg "\n", __LINE__, __func__, ## __VA_ARGS__))
#ifdef DEBUG
#define dprintf(msg, ...) TRACEx(DEBUG, "debug", msg, ## __VA_ARGS__)
#elif defined(_EVAL_DEBUG_ONLY_STMTS)
#define dprintf(msg, ...) _drain(msg, ## __VA_ARGS__)
#endif
#define TRACE(msg, ...) TRACEx(DEBUG, "trace", msg, ## __VA_ARGS__)
#define INFO(msg, ...) TRACEx(INFO, "info", msg, ## __VA_ARGS__)
#define WARN(msg, ...) TRACEx(WARN, "warning", msg, ## __VA_ARGS__)
#define ERROR(msg, ...) TRACEx(ERROR, "error", msg, ## __VA_ARGS__)
#define FATAL(msg, ...) (TRACEx(FATAL, "FATAL", msg, ## __VA_ARGS__), abort())
#define TODO(x, ...) FATAL("function %s() is unimplemented: " x, __func__, ## __VA_ARGS__)
// Debug testing
#define PASSERT_OR(expr, err, message) do { if ((expr)) break; perror("[FATAL] `" #expr "`: " message); ({ err; }) } while(0)
#define PASSERT(expr, message) do { if ((expr)) break; perror("[FATAL] `" #expr "`: " message); exit(-1); } while(0)
#define PASSERT_R(expr, rv, message) PASSERT_OR(expr, return (rv), message)
#define TEST_OK 1
#define TEST_FAILED 0
#define TESTFUN(name) _test__ ## name
#define CALLTEST(name) (assert_msg(TESTFUN(name)() == TEST_OK, "Test `" #name "` failed"), TRACE("Test `" #name "` passed!"))
#define DEFTEST(name) int _test__ ## name(void)
#define TEST_ASSERT(x) do { if (!(x)) {ERROR("Test `%s' assertion failure: `" #x "`", __func__ + 7); return TEST_FAILED;} } while(0)
#define TEST_ASSERT_EQ(x,y) TEST_ASSERT( (x) == (y) )
#define TEST_ASSERT_NE(x,y) TEST_ASSERT( (x) != (y) )
// Version macros
#define _AS_u32(x) ((uint32_t)(x))
#define VER_COMP_MAJ 24u
#define VER_COMP_MIN 16u
#define VER_COMP_BF 8u
#define VER_COMP_REV 0u
/// Create a `uint32_t` from these version componants
#define VERSION(maj,min,bf,rev) _AS_u32( _AS_u32(maj) << 24u | _AS_u32(min) << 16u | _AS_u32(bf) << 8u | _AS_u32(rev) )
/// Mask `ver` to retain only the version component specified by `mask`
///
/// # Example
/// `_Static_assert( (VERSION_COMP(VERSION(1,2,3,4), VER_COMP_MIN) >> VER_COMP_MIN) == 2u)`
#define VERSION_COMP(ver, mask) _AS_u32( _AS_u32(ver) & (0xffu << _AS_u32(mask)) /*>> _AS_u32(mask)*/ )
#define VERSION_MAJ(ver) _AS_u32(VERSION_COMP((ver), 24u) >> 24u)
#define VERSION_MIN(ver) _AS_u32(VERSION_COMP((ver), 16u) >> 16u)
#define VERSION_BF(ver) _AS_u32(VERSION_COMP((ver), 8u) >> 8u)
#define VERSION_REV(ver) _AS_u32(_AS_u32(ver) & 0xffu)
_Static_assert( (VERSION_COMP(VERSION(1,2,3,4), VER_COMP_MIN) >> VER_COMP_MIN) == 2u, "invalid native version spec");
// Misc.
#define PTR_ASSIGN(ptr, val) ( (ptr) ? (*(ptr) = (val), (ptr)) : (ptr) )
#define PTR_ASSIGNv(ptr, val) ({ let _ptr = (ptr); let _val = (val); PTR_ASSIGN(_ptr, _val); })
#endif /* _MACROS_H */

@ -0,0 +1,37 @@
//! Trace messages
#ifndef _TRACE_H
#define _TRACE_H
#include <stdio.h>
#include <stdarg.h>
enum trace_level {
TRACE_LEVEL_DEBUG = 0,
TRACE_LEVEL_INFO,
TRACE_LEVEL_WARN,
TRACE_LEVEL_ERROR,
TRACE_LEVEL_FATAL,
_TRACE_LEVEL_NUM,
};
#ifdef DEBUG
#define _TRACE_LEVEL_DEFAULT TRACE_LEVEL_DEBUG
#else
#define _TRACE_LEVEL_DEFAULT TRACE_LEVEL_WARN
#endif
int _t_fprintf(enum trace_level l, FILE* output, const char* msg, ...);
enum trace_level trace_max_level();
void trace_init_with(enum trace_level max_level);
// Initialise the tracer with env-var `_TRACE_CONTROL_ENV_NAME`. If the env-var is not found, the max trace level is not modified. If it is found but has an invalid value, this function returns 0. Otherwise it modifies the max trace level value and returns 1.
int trace_init();
const char* trace_name_of(enum trace_level lv);
extern const char* const _TRACE_CONTROL_ENV_NAME;
extern const char* const _trace_level_descriptions[_TRACE_LEVEL_NUM];
#endif /* _TRACE_H */

@ -0,0 +1,119 @@
// Trace messages
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <trace.h>
#include <macros.h>
#include <ints.h>
static _Atomic enum trace_level _t_level = _TRACE_LEVEL_DEFAULT;
const char* const _TRACE_CONTROL_ENV_NAME = "LOG_LEVEL";
const struct {const char* const name; const enum trace_level level; } _trace_level_names[] = {
#define X(name) { #name, TRACE_LEVEL_ ## name }
#define Y(name, val) { #name, TRACE_LEVEL_ ## val }
X(DEBUG), Y(TRACE, DEBUG),
X(INFO),
X(WARN),
X(ERROR),
X(FATAL),
#undef Y
#undef X
};
#define _trace_level_names_n (sizeof(_trace_level_names) / sizeof(_trace_level_names[0]))
const char* const _trace_level_descriptions[_TRACE_LEVEL_NUM] = {
"All trace messages regarding internals, only useful for debugging",
"Information messages regarding processes, recommended level",
"Only show warnings",
"Only show errors",
"Only show fatal (aborting) errors",
};
static bool _trace_lookup(const char*pIN name, enum trace_level *pOUT olevel)
{
debug_assert(name);
for(usize i=0;i<_trace_level_names_n;i++) {
let level = &_trace_level_names[i];
debug_assert(level && level->name);
if(strcmp(name, level->name) == 0) {
*olevel =level->level;
return true;
}
}
return false;
}
static bool _trace_reverse_lookup(enum trace_level level, const char* *pOUT name)
{
for(usize i=0;i<_trace_level_names_n;i++) {
let lv = &_trace_level_names[i];
debug_assert(lv && lv->name);
if(lv->level == level) {
*name = lv->name;
return true;
}
}
return false;
}
const char* trace_name_of(enum trace_level lv)
{
const char* ret;
if(!_trace_reverse_lookup(lv, &ret)) return NULL;
else return ret;
}
inline enum trace_level trace_max_level()
{
return _t_level;
}
inline static bool _trace_hidden(enum trace_level l)
{
return l < trace_max_level();
}
int _t_fprintf(enum trace_level l, FILE* output, const char* msg, ...)
{
if(_trace_hidden(l)) return 0;
va_list ap;
va_start(ap, msg);
register int r = vfprintf(output, msg, ap);
va_end(ap);
return r;
}
void trace_init_with(enum trace_level max_level)
{
_t_level = max_level;
}
static inline bool _try_get_env(const char* name, const char* *pOUT value)
{
return !!(*value = (const char*)getenv(name));
}
int trace_init()
{
const char* eval;
if(!_try_get_env(_TRACE_CONTROL_ENV_NAME, &eval)) return 1;
else {
assert(eval);
if(*eval == 0) return 1; // Count empty string as default.
enum trace_level level;
if(!_trace_lookup(eval, &level)) return 0;
else {
trace_init_with(level);
}
}
return 1;
}
Loading…
Cancel
Save