diff --git a/include/macros.h b/include/macros.h index 184a0a2..402ae94 100644 --- a/include/macros.h +++ b/include/macros.h @@ -37,6 +37,16 @@ #define restrict __restrict__ #endif +// Argument attribute + +// Used like: `int *pOUT output, const int *pIN input` +#define pOUT restrict +#define pIN const restrict + +// Used like: `int _OUT output, const int _IN input` +#define _IN *pIN +#define _OUT *pOUT + // Type macros #define AS(x, t) ((t)(x)) @@ -155,9 +165,9 @@ static_assert_eq(bswap(bswap(128lu)), 128, "bswap128 (lu) failed (3)"); #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 VERSINO_MIN(ver) _AS_u32(VERSION_COMP((ver), 16u) >> 16u) -#define VERSINO_BF(ver) _AS_u32(VERSION_COMP((ver), 8u) >> 8u) -#define VERSINO_REV(ver) _AS_u32(_AS_u32(ver) & 0xffu) +#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"); #endif /* _MACROS_H */ diff --git a/include/version.h b/include/version.h new file mode 100644 index 0000000..7d0789a --- /dev/null +++ b/include/version.h @@ -0,0 +1,79 @@ +#ifndef _VERSION_H +#define _VERSION_H + +#include "macros.h" +#include "ints.h" + +typedef union { + uint32_t raw; + // XXX: Punning through this union is unsafe, it may work (on little endian machines), but it is not intended to be punned. Use `v_rawtoc` and `v_ctoraw` instead. + struct v_comp { + u8 revision, bugfix, minor, major; + } comp; +} version_t; + +// Convert a native `u32` representation of a version (the output of `VERSION()` macro) to a `version_t`. +#define v_rawtoc_unsafe(raw) ((version_t){ .raw = (raw) }) +_mixin version_t v_rawtoc(uint32_t raw) +{ + register version_t out = { + .comp = { + .major = VERSION_MAJ(raw), + .minor = VERSION_MIN(raw), + .bugfix = VERSION_BF(raw), + .revision = VERSION_REV(raw), + }, + }; + return out; +} + +/// Convert a `version_t` to its native `u32` representation. +#define v_ctoraw_unsafe(ver) ((ver).raw) +_mixin uint32_t v_ctoraw(version_t ver) +{ + return VERSION(ver.comp.major, + ver.comp.minor, + ver.comp.bugfix, + ver.comp.revision); +} + +#define VERSION_C(ma,mi,bf,re) v_rawtoc(VERSION((ma),(mi),(bf),(re))) + +#define VERSION_STRING_MAX AS(3 + 3 + 3 + 3 + 3, usize) // maj + min + bf + rev + (..r) + +//TODO: Move the below functions to `version.c` and have them prototyped, not mixins. + +/// Print a version to a string buffer `out`. Return the number of bytes written. +/// +/// Has the same semantics as `snprintf`; to calculate the length of the string needed to allocate, invoke like: `size_t sz = v_ctosn(vers, 0, NULL)`. The buffer should be allocated as `sz + 1`, and then the next call to `v_ctosn` should be like: `v_ctosn(vers, sz+1, buffer); +_mixin usize v_ctosn(version_t ver, usize max, char out[static pOUT max]) +{ + struct v_comp comp = ver.comp; + const char* const fmt = comp.revision ? "%hhu.%hhu.%hhur%hhu" : "%hhu.%hhu.%hhu"; + + + return snprintf(out, max, fmt, + comp.major, + comp.minor, + comp.bugfix, + comp.revision); +} + +/// Print a version to a static string buffer. The string buffer is thread local. The next call to this function on the same thread will overwrite the returned buffer. +_mixin const char* v_ctoss(version_t ver) +{ + _Thread_local static char buffer[VERSION_STRING_MAX+1]; + buffer[v_ctosn(ver, VERSION_STRING_MAX+1, buffer)] = 0; + return buffer; +} + +/// Print a version to a `malloc()`d string buffer. The buffer is unique and owned by the callee. The buffer must be `free()`d. +_mixin char* v_ctos(version_t ver) +{ + usize sz = v_ctosn(ver, 0, NULL); + char* buf = malloc(sz+1); + v_ctosn(ver, sz+1, buf); + return buf; +} + +#endif /* _VERSION_H */ diff --git a/src/main.c b/src/main.c index b9ef6a5..cfce1ae 100644 --- a/src/main.c +++ b/src/main.c @@ -6,17 +6,28 @@ #include #include +#include int main(int argc, char** argv) { IGNORE(argc); IGNORE(argv); - u64 i = 128; + const u32 vraw = VERSION(1,2,3,240); + version_t vers = v_rawtoc(vraw); - u32 vers = VERSION(1,2,3,4); + usize sz = v_ctosn(vers, 0, NULL); + char verstr[sz+1]; + v_ctosn(vers, sz+1, verstr); - INFO("Hello world %lu %lu %lu!", i, bswap(i), bswap(bswap(i))); + char* verstr_h; + INFO("Version: 0x%x, raw: 0x%x", vers.raw, vraw); + INFO("Output is: (%lu chars)\t%s", sz, verstr); + INFO("Output is (static):\t%s", v_ctoss(vers)); + INFO("Output is (leak):\t\t%s", (verstr_h = v_ctos(vers))); + + free(verstr_h); + //INFO("Hello world %lu %lu %lu!", i, bswap(i), bswap(bswap(i))); //INFO("Version: 0x%x (0x%x)", vers, bswap(vers)); // Check which version endian was preferrable, we'll stick with the default system endian (little, for this one.) return 0; }