diff options
Diffstat (limited to 'src/chunklets/msg.c')
-rw-r--r-- | src/chunklets/msg.c | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/src/chunklets/msg.c b/src/chunklets/msg.c new file mode 100644 index 0000000..0e26a80 --- /dev/null +++ b/src/chunklets/msg.c @@ -0,0 +1,275 @@ +/* + * Copyright © 2023 Michael Smith <mikesmiffy128@gmail.com> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifdef __cplusplus +#error This file should not be compiled as C++. It relies on C-specific union \ +behaviour which is undefined in C++. +#endif + +// _Static_assert needs MSVC >= 2019, and this check is irrelevant on Windows +#ifndef _MSC_VER +_Static_assert( + (unsigned char)-1 == 255 && + sizeof(short) == 2 && + sizeof(int) == 4 && + sizeof(long long) == 8 && + sizeof(float) == 4 && + sizeof(double) == 8, + "this code is only designed for relatively sane environments, plus Windows" +); +#endif + +// -- A note on performance hackery -- +// +// Clang won't emit byte-swapping instructions in place of bytewise array writes +// unless nothing else is written to the same array. MSVC won't do it at all. +// For these compilers on little-endian platforms that can also do unaligned +// writes efficiently, we do so explicitly and handle the byte-swapping +// manually, which then tends to get optimised pretty well. +// +// GCC, somewhat surprisingly, seems to be much better at optimising the naïve +// version of the code, so we don't try to do anything clever there. Also, for +// unknown, untested compilers and/or platforms, we stick to the safe approach. +#if defined(_MSC_VER) || defined(__clang__) && (defined(__x86_64__) || \ + defined(__i386__) || defined(__aarch64__) || defined(__arm__)) +#define USE_BSWAP_NONSENSE +#endif + +#ifdef USE_BSWAP_NONSENSE +#if defined(_MSC_VER) && !defined(__clang__) +// MSVC prior to 2022 won't even optimise shift/mask swaps into a bswap +// instruction. Screw it, just use the intrinsics. +unsigned long _byteswap_ulong(unsigned long); +unsigned long long _byteswap_uint64(unsigned long long); +#define swap32 _byteswap_ulong +#define swap64 _byteswap_uint64 +#else +static inline unsigned int swap32(unsigned int x) { + return x >> 24 | x << 24 | x >> 8 & 0xFF00 | x << 8 & 0xFF0000; +} +static inline unsigned long long swap64(unsigned long long x) { + return x >> 56 | x << 56 | + x >> 40 & 0xFF00 | x << 40 & 0xFF000000000000 | + x >> 24 & 0xFF0000 | x << 24 & 0xFF0000000000 | + x >> 8 & 0xFF000000 | x << 8 & 0xFF00000000; +} +#endif +#endif + +static inline void doput16(unsigned char *out, unsigned short val) { +#ifdef USE_BSWAP_NONSENSE + // Use swap32() here because x86 and ARM don't have instructions for 16-bit + // swaps, and Clang doesn't realise it could just use the 32-bit one anyway. + *(unsigned short *)(out + 1) = swap32(val) >> 16; +#else + out[1] = val >> 8; out[2] = val; +#endif +} + +static inline void doput32(unsigned char *out, unsigned int val) { +#ifdef USE_BSWAP_NONSENSE + *(unsigned int *)(out + 1) = swap32(val); +#else + out[1] = val >> 24; out[2] = val >> 16; out[3] = val >> 8; out[4] = val; +#endif +} + +static inline void doput64(unsigned char *out, unsigned int val) { +#ifdef USE_BSWAP_NONSENSE + // Clang is smart enough to make this into two bswaps and a word swap in + // 32-bit builds. MSVC seems to be fine too when using the above intrinsics. + *(unsigned long long *)(out + 1) = swap64(val); +#else + out[1] = val >> 56; out[2] = val >> 48; + out[3] = val >> 40; out[4] = val >> 32; + out[5] = val >> 24; out[6] = val >> 16; + out[7] = val >> 8; out[8] = val; +#endif +} + +void msg_putnil(unsigned char *out) { + *out = 0xC0; +} + +void msg_putbool(unsigned char *out, _Bool val) { + *out = 0xC2 | val; +} + +void msg_puti7(unsigned char *out, signed char val) { + *out = val; // oh, so a fixnum is just the literal byte! genius! +} + +int msg_puts8(unsigned char *out, signed char val) { + int off = val < -32; // out of -ve fixnum range? + out[0] = 0xD0; + out[off] = val; + return off + 1; +} + +int msg_putu8(unsigned char *out, unsigned char val) { + int off = val > 127; // out of +ve fixnum range? + out[0] = 0xCC; + out[off] = val; + return off + 1; +} + +int msg_puts16(unsigned char *out, short val) { + if (val >= -128 && val <= 127) return msg_puts8(out, val); + out[0] = 0xD1; + doput16(out, val); + return 3; +} + +int msg_putu16(unsigned char *out, unsigned short val) { + if (val <= 255) return msg_putu8(out, val); + out[0] = 0xCD; + doput16(out, val); + return 3; +} + +int msg_puts32(unsigned char *out, int val) { + if (val >= -32768 && val <= 32767) return msg_puts16(out, val); + out[0] = 0xD2; + doput32(out, val); + return 5; +} + +int msg_putu32(unsigned char *out, unsigned int val) { + if (val <= 65535) return msg_putu16(out, val); + out[0] = 0xCE; + doput32(out, val); + return 5; +} + +int msg_puts(unsigned char *out, long long val) { + if (val >= -2147483648 && val <= 2147483647) { + return msg_puts32(out, val); + } + out[0] = 0xD3; + doput64(out, val); + return 9; +} + +int msg_putu(unsigned char *out, unsigned long long val) { + if (val <= 4294967295) return msg_putu32(out, val); + out[0] = 0xCF; + doput64(out, val); + return 9; +} + +static inline unsigned int floatbits(float f) { + return (union { float f; unsigned int i; }){f}.i; +} + +static inline unsigned long long doublebits(double d) { + return (union { double d; unsigned long long i; }){d}.i; +} + +void msg_putf(unsigned char *out, float val) { + out[0] = 0xCA; + doput32(out, floatbits(val)); +} + +int msg_putd(unsigned char *out, double val) { + // XXX: is this really the most efficient way to check this? + float f = val; + if ((double)f == val) { msg_putf(out, f); return 5; } + out[0] = 0xCA; + doput64(out, doublebits(val)); + return 9; +} + +void msg_putssz5(unsigned char *out, int sz) { + *out = 0xA0 | sz; +} + +int msg_putssz8(unsigned char *out, int sz) { + if (sz < 64) { msg_putssz5(out, sz); return 1; } + out[0] = 0xD9; + out[1] = sz; + return 2; +} + +int msg_putssz16(unsigned char *out, int sz) { + if (sz < 256) return msg_putssz8(out, sz); + out[0] = 0xDA; + doput16(out, sz); + return 3; +} + +int msg_putssz(unsigned char *out, unsigned int sz) { + if (sz < 65536) return msg_putssz16(out, sz); + out[0] = 0xDB; + doput32(out, sz); + return 5; +} + +void msg_putbsz8(unsigned char *out, int sz) { + out[0] = 0xC4; + out[1] = sz; +} + +int msg_putbsz16(unsigned char *out, int sz) { + if (sz < 256) { msg_putbsz8(out, sz); return 2; } + out[0] = 0xC5; + doput16(out, sz); + return 2 + sz; +} + +int msg_putbsz(unsigned char *out, unsigned int sz) { + if (sz < 65536) return msg_putbsz16(out, sz); + out[0] = 0xC6; + doput32(out, sz); + return 5; +} + +void msg_putasz4(unsigned char *out, int sz) { + *out = 0x90 | sz; +} + +int msg_putasz16(unsigned char *out, int sz) { + if (sz < 32) { msg_putasz4(out, sz); return 1; } + out[0] = 0xDC; + doput16(out, sz); + return 3; +} + +int msg_putasz(unsigned char *out, unsigned int sz) { + if (sz < 65536) return msg_putasz16(out, sz); + out[0] = 0xDD; + doput32(out, sz); + return 5; +} + +void msg_putmsz4(unsigned char *out, int sz) { + *out = 0x80 | sz; +} + +int msg_putmsz16(unsigned char *out, int sz) { + if (sz < 32) { msg_putmsz4(out, sz); return 1; } + out[0] = 0xDE; + doput16(out, sz); + return 3; +} + +int msg_putmsz(unsigned char *out, unsigned int sz) { + if (sz < 65536) return msg_putmsz16(out, sz); + out[0] = 0xDF; + doput32(out, sz); + return 5; +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 |