Messages in this thread | | | Date | Mon, 20 Sep 2010 00:29:07 -0400 | From | Mathieu Desnoyers <> | Subject | [RFC] Portable bitfield library for trace format (v2) |
| |
I'm keeping the same introduction as (v1), but I'm appending v2 of my portable bitfield library below. Comments are welcome,
Changelog since v1: * Add missing brackets around "ptr" in the macros. * Add more tests, including tests using randomly generated input values.
Thanks,
Mathieu
* Mathieu Desnoyers (mathieu.desnoyers@efficios.com) wrote: > Hi, > > After looking at gcc bitfields, I found out that they were really non-portable. > One particularity of these bitfields is that the fields are put from high to low > bits on big endian, and from low to high bits on little endian. So it is not > enough to do the byte swap between architectures, one must also take care to > reorder the fields. > > Also, the fact that gcc requires bitfields to fit in the current unit adds much > padding that is unwanted in a trace e.g. > > struct { > int a:3; > int b:31; > int c:3; > }; > > will be 12 bytes in size because "b" does not fit in the first "int" unit. > > So I created a bitfield library with write and read primitives that can be made > compatible with gcc bitfields by specifying the bit padding manually. The > write-side writes bitfields in the native endianness, either with 1, 2, 4 or 8 > bytes memory writes. The read side is architecture-agnostic, so it can read > bitfields generated by either little or big endian architectures. > > The userspace code is below. It includes a rather useful test-suite. The code > generated is quite compact when the parameters are fixed. A x86-32 disassembly > of fct() below which contains the macro instance: > > unsigned int glob[1]; > > void fct(void) > { > bitfield_write(glob, 0x12345678, 12, 15); > } > > Compared to this, we have the generated assembly from gcc 4.3.4 for: > > struct d3 { > unsigned int a:12; > unsigned int b:15; > }; > > struct d3 glob; > > void fct(void) > { > glob.b = 0x12345678; > } > > glob.b = 0x12345678; > > On x86-32, the bitfield library looks like: > > 08048470 <fct>: > 8048470: a1 70 ba 04 08 mov 0x804ba70,%eax > 8048475: 55 push %ebp > 8048476: 89 e5 mov %esp,%ebp > 8048478: 5d pop %ebp > 8048479: 25 ff 0f 00 f8 and $0xf8000fff,%eax > 804847e: 0d 00 80 67 05 or $0x5678000,%eax > 8048483: a3 70 ba 04 08 mov %eax,0x804ba70 > 8048488: c3 ret > 8048489: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi > > The gcc bitfields generate: > > 080483d0 <fct>: > 80483d0: a1 64 96 04 08 mov 0x8049664,%eax > 80483d5: 55 push %ebp > 80483d6: 89 e5 mov %esp,%ebp > 80483d8: 5d pop %ebp > 80483d9: 25 ff 0f 00 f8 and $0xf8000fff,%eax > 80483de: 0d 00 80 67 05 or $0x5678000,%eax > 80483e3: a3 64 96 04 08 mov %eax,0x8049664 > 80483e8: c3 ret > 80483e9: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi > > > and on powerpc 32, the bitfield library: > > 10000520 <fct>: > 10000520: 3d 20 10 01 lis r9,4097 > 10000524: 80 09 33 80 lwz r0,13184(r9) > 10000528: 54 00 06 d6 rlwinm r0,r0,0,27,11 > 1000052c: 64 00 00 0a oris r0,r0,10 > 10000530: 60 00 cf 00 ori r0,r0,52992 > 10000534: 90 09 33 80 stw r0,13184(r9) > 10000538: 4e 80 00 20 blr > 1000053c: 60 00 00 00 nop > > And gcc bitfields: > > 10000490 <fct>: > 10000490: 3d 20 10 01 lis r9,4097 > 10000494: 39 60 56 78 li r11,22136 > 10000498: 80 09 0a a4 lwz r0,2724(r9) > 1000049c: 51 60 2b 34 rlwimi r0,r11,5,12,26 > 100004a0: 90 09 0a a4 stw r0,2724(r9) > 100004a4: 4e 80 00 20 blr > 100004a8: 60 00 00 00 nop > 100004ac: 60 00 00 00 nop > > > Comments are welcome (code below). > > Thanks, > > Mathieu >
/* * Common Trace Format * * Bitfields implementation * * Copyright 2010 - Mathieu Desnoyers <mathieu.desnoyers@efficios.com> * * Dual LGPL v2.1/GPL v2 license. */
#include <endian.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <assert.h> #include <time.h> #include <strings.h>
/* We can't shift a int from 32 bit, >> 32 on int is a no-op on x86 */ #define piecewise_rshift(v, shift) \ do { \ int sb = (shift) / (sizeof(v) * 8 - 1); \ int final = (shift) % (sizeof(v) * 8 - 1); \ \ for (; sb; sb--) \ (v) >>= sizeof(v) * 8 - 1; \ (v) >>= final; \ } while (0)
/* * Save integer to the bitfield, which starts at the "start" bit, has "len" * bits. * The inside of a bitfield is from high bits to low bits. * Uses native endianness. * For unsigned "v", pad MSB with 0 if bitfield is larger than v. * For signed "v", sign-extend v if bitfield is larger than v. * * On little endian, bytes are placed from the less significant to the most * significant. Also, consecutive bitfields are placed from lower bits to higher * bits. * * On big endian, bytes are places from most significant to less significant. * Also, consecutive bitfields are placed from higher to lower bits. */
#if (BYTE_ORDER == LITTLE_ENDIAN)
#define bitfield_write(ptr, _v, _start, _length) \ do { \ typeof(*(ptr)) mask, cmask; \ unsigned int start = (_start), length = (_length); \ typeof(_v) v = (_v); \ int start_unit, end_unit, this_unit; \ unsigned int end, cshift; /* cshift is "complement shift" */ \ unsigned int ts = sizeof(__typeof__(*(ptr))) * 8; /* type size */ \ \ if (!length) \ break; \ \ end = start + length; \ start_unit = start / ts; \ end_unit = (end + (ts - 1)) / ts; \ \ /* Trim v high bits */ \ if (length < sizeof(v) * 8) \ v &= ~(~0ULL << length); \ \ /* We can now append v with a simple "or", shift it piece-wise */ \ this_unit = start_unit; \ if (start_unit == end_unit - 1) { \ mask = ~((typeof(*(ptr))) ~0ULL << (start % ts)); \ mask |= (typeof(*(ptr))) ~0ULL << (end % ts ? : ts); \ cmask = (typeof(*(ptr))) v << (start % ts); \ (ptr)[this_unit] &= mask; \ (ptr)[this_unit] |= cmask; \ break; \ } \ if (start % ts) { \ cshift = start % ts; \ mask = ~((typeof(*(ptr))) ~0ULL << cshift); \ cmask = (typeof(*(ptr))) v << cshift; \ (ptr)[this_unit] &= mask; \ (ptr)[this_unit] |= cmask; \ if (ts - cshift >= sizeof(v) * 8) \ piecewise_rshift(v, ts - cshift); \ else \ v >>= ts - cshift; \ start += ts - cshift; \ this_unit++; \ } \ for (; this_unit < end_unit - 1; this_unit++) { \ (ptr)[this_unit] = (typeof(*(ptr))) v; \ if (ts >= sizeof(v) * 8) \ piecewise_rshift(v, ts); \ else \ v >>= ts; \ start += ts; \ } \ if (end % ts) { \ mask = (typeof(*(ptr))) ~0ULL << (end % ts); \ cmask = (typeof(*(ptr))) v; \ (ptr)[this_unit] &= mask; \ (ptr)[this_unit] |= cmask; \ } else \ (ptr)[this_unit] = (typeof(*(ptr))) v; \ } while (0)
#else /* (BYTE_ORDER != LITTLE_ENDIAN) */
#define bitfield_write(ptr, _v, _start, _length) \ do { \ typeof(_v) v = (_v); \ unsigned int start = _start, length = _length; \ int start_unit, end_unit, this_unit; \ unsigned int end, cshift; /* cshift is "complement shift" */ \ typeof(*(ptr)) mask, cmask; \ unsigned int ts = sizeof(__typeof__(*(ptr))) * 8; /* type size */ \ \ if (!length) \ break; \ \ end = start + length; \ start_unit = start / ts; \ end_unit = (end + (ts - 1)) / ts; \ \ /* Trim v high bits */ \ if (length < sizeof(v) * 8) \ v &= ~(~0ULL << length); \ \ /* We can now append v with a simple "or", shift it piece-wise */ \ this_unit = end_unit - 1; \ if (start_unit == end_unit - 1) { \ mask = ~((typeof(*(ptr))) ~0ULL << ((ts - (end % ts)) % ts)); \ mask |= (typeof(*(ptr))) ~0ULL << (ts - (start % ts)); \ cmask = (typeof(*(ptr))) v << ((ts - (end % ts)) % ts); \ (ptr)[this_unit] &= mask; \ (ptr)[this_unit] |= cmask; \ break; \ } \ if (end % ts) { \ cshift = end % ts; \ mask = ~((typeof(*(ptr))) ~0ULL << (ts - cshift)); \ cmask = (typeof(*(ptr))) v << (ts - cshift); \ (ptr)[this_unit] &= mask; \ (ptr)[this_unit] |= cmask; \ if (cshift >= sizeof(v) * 8) \ piecewise_rshift(v, cshift); \ else \ v >>= cshift; \ end -= cshift; \ this_unit--; \ } \ for (; this_unit >= start_unit + 1; this_unit--) { \ (ptr)[this_unit] = (typeof(*(ptr))) v; \ if (ts >= sizeof(v) * 8) \ piecewise_rshift(v, ts); \ else \ v >>= ts; \ end -= ts; \ } \ if (start % ts) { \ mask = (typeof(*(ptr))) ~0ULL << (ts - (start % ts)); \ cmask = (typeof(*(ptr))) v; \ (ptr)[this_unit] &= mask; \ (ptr)[this_unit] |= cmask; \ } else \ (ptr)[this_unit] = (typeof(*(ptr))) v; \ } while (0)
#endif
/* * Read a bitfield byte-wise. This function is arch-agnostic. */
uint64_t bitfield_read_64(unsigned char *ptr, unsigned int start, unsigned int len, int byte_order, int signedness) { int start_unit, end_unit, this_unit; unsigned int end, cshift; /* cshift is "complement shift" */ unsigned int ts = sizeof(unsigned char) * 8; unsigned char mask, cmask; uint64_t v = 0;
if (!len) return 0; end = start + len; start_unit = start / ts; end_unit = (end + (ts - 1)) / ts;
/* * We can now fill v piece-wise, from lower bits to upper bits. * We read the bitfield in the opposite direction it was written. */ switch (byte_order) { case LITTLE_ENDIAN: this_unit = end_unit - 1; if (signedness) { if (ptr[this_unit] & (1U << ((end % ts ? : ts) - 1))) v = ~0ULL; } if (start_unit == end_unit - 1) { mask = (unsigned char) ~0ULL << (end % ts ? : ts); mask |= ~((unsigned char) ~0ULL << (start % ts)); cmask = ptr[this_unit]; cmask &= ~mask; cmask >>= (start % ts); v <<= end - start; v |= cmask; break; } if (end % ts) { cshift = (end % ts ? : ts); mask = (unsigned char) ~0ULL << cshift; cmask = ptr[this_unit]; cmask &= ~mask; v <<= cshift; v |= (uint64_t) cmask; end -= cshift; this_unit--; } for (; this_unit >= start_unit + 1; this_unit--) { v <<= ts; v |= (uint64_t) ptr[this_unit]; end -= ts; } if (start % ts) { cmask = ptr[this_unit] >> (start % ts); v <<= ts - (start % ts); v |= (uint64_t) cmask; } else { v <<= ts; v |= (uint64_t) ptr[this_unit]; } break; case BIG_ENDIAN: this_unit = start_unit; if (signedness) { if (ptr[this_unit] & (1U << (ts - (start % ts) - 1))) v = ~0ULL; } if (start_unit == end_unit - 1) { mask = (unsigned char) ~0ULL << (ts - (start % ts)); mask |= ~((unsigned char) ~0ULL << ((ts - (end % ts)) % ts)); cmask = ptr[this_unit]; cmask &= ~mask; cmask >>= (ts - (end % ts)) % ts; v <<= end - start; v |= (uint64_t) cmask; break; } if (start % ts) { mask = (unsigned char) ~0ULL << (ts - (start % ts)); cmask = ptr[this_unit]; cmask &= ~mask; v <<= ts - (start % ts); v |= (uint64_t) cmask; start += ts - (start % ts); this_unit++; } for (; this_unit < end_unit - 1; this_unit++) { v <<= ts; v |= (uint64_t) ptr[this_unit]; start += ts; } if (end % ts) { cmask = ptr[this_unit]; cmask >>= (ts - (end % ts)) % ts; v <<= (end % ts); v |= (uint64_t) cmask; } else { v <<= ts; v |= (uint64_t) ptr[this_unit]; } break; default: assert(0); } return v; }
/* * The code below is for testing the bitfield write/read functions. */
unsigned int glob;
/* * This function is only declared to show the size of a bitfield write in * objdump. */ void fct(void) { bitfield_write(&glob, 0x12345678, 12, 15); }
/* Test array size, in bytes */ #define TEST_LEN 128 #define NR_TESTS 10
unsigned int srcrand;
#if defined(__i386) || defined(__x86_64)
static inline int fls(int x) { int r; asm("bsrl %1,%0\n\t" "cmovzl %2,%0" : "=&r" (r) : "rm" (x), "rm" (-1)); return r + 1; }
#elif defined(__PPC__)
static __inline__ int fls(unsigned int x) { int lz;
asm ("cntlzw %0,%1" : "=r" (lz) : "r" (x)); return 32 - lz; }
#else
static int fls(unsigned int x) { int r = 32;
if (!x) return 0; if (!(x & 0xFFFF0000U)) { x <<= 16; r -= 16; } if (!(x & 0xFF000000U)) { x <<= 8; r -= 8; } if (!(x & 0xF0000000U)) { x <<= 4; r -= 4; } if (!(x & 0xC0000000U)) { x <<= 2; r -= 2; } if (!(x & 0x80000000U)) { x <<= 1; r -= 1; } return r; }
#endif
static void print_byte_array(const unsigned char *c, unsigned long len) { unsigned long i;
for (i = 0; i < len; i++) { printf("0x%X", c[i]); if (i != len - 1) printf(" "); } printf("\n"); }
static void init_byte_array(unsigned char *c, unsigned long len, unsigned char val) { unsigned long i;
for (i = 0; i < len; i++) c[i] = val; }
int run_test_unsigned(void) { unsigned int src, nrbits; union { unsigned char c[TEST_LEN]; unsigned short s[TEST_LEN/2]; unsigned int i[TEST_LEN/4]; unsigned long l[TEST_LEN/4]; unsigned long long ll[TEST_LEN/2]; } target; uint64_t readval; unsigned int s, l; int err = 0;
printf("Running unsigned test with 0x%X\n", srcrand);
src = srcrand; nrbits = fls(src);
for (s = 0; s < 8 * TEST_LEN; s++) { for (l = nrbits; l < (8 * TEST_LEN) - s; l++) { init_byte_array(target.c, TEST_LEN, 0xFF); bitfield_write(target.c, src, s, l); readval = bitfield_read_64(target.c, s, l, BYTE_ORDER, 0); if (readval != src) { printf("Error (bytewise) src %lX read %llX shift %d len %d\n", src, readval, s, l); print_byte_array(target.c, TEST_LEN); err = 1; }
init_byte_array(target.c, TEST_LEN, 0xFF); bitfield_write(target.s, src, s, l); readval = bitfield_read_64(target.c, s, l, BYTE_ORDER, 0); if (readval != src) { printf("Error (shortwise) src %lX read %llX shift %d len %d\n", src, readval, s, l); print_byte_array(target.c, TEST_LEN); err = 1; }
init_byte_array(target.c, TEST_LEN, 0xFF); bitfield_write(target.i, src, s, l); readval = bitfield_read_64(target.c, s, l, BYTE_ORDER, 0); if (readval != src) { printf("Error (intwise) src %lX read %llX shift %d len %d\n", src, readval, s, l); print_byte_array(target.c, TEST_LEN); err = 1; }
init_byte_array(target.c, TEST_LEN, 0xFF); bitfield_write(target.l, src, s, l); readval = bitfield_read_64(target.c, s, l, BYTE_ORDER, 0); if (readval != src) { printf("Error (longwise) src %lX read %llX shift %d len %d\n", src, readval, s, l); print_byte_array(target.c, TEST_LEN); err = 1; }
init_byte_array(target.c, TEST_LEN, 0xFF); bitfield_write(target.ll, src, s, l); readval = bitfield_read_64(target.c, s, l, BYTE_ORDER, 0); if (readval != src) { printf("Error (longlongwise) src %lX read %llX shift %d len %d\n", src, readval, s, l); print_byte_array(target.c, TEST_LEN); err = 1; } } } if (!err) printf("Success!\n"); else printf("Failed!\n"); return err; }
int run_test_signed(void) { int src, nrbits; union { char c[TEST_LEN]; short s[TEST_LEN/2]; int i[TEST_LEN/4]; long l[TEST_LEN/4]; long long ll[TEST_LEN/8]; } target; int64_t readval; unsigned int s, l; int err = 0;
printf("Running signed test with 0x%X\n", srcrand);
src = srcrand; if (src & 0x80000000U) nrbits = fls(~src) + 1; /* Find least significant bit conveying sign */ else nrbits = fls(src) + 1; /* Keep sign at 0 */
for (s = 0; s < 8 * TEST_LEN; s++) { for (l = nrbits; l < (8 * TEST_LEN) - s; l++) { init_byte_array(target.c, TEST_LEN, 0x0); bitfield_write(target.c, src, s, l); readval = bitfield_read_64(target.c, s, l, BYTE_ORDER, 1); if (readval != src) { printf("Error (bytewise) src %lX read %llX shift %d len %d\n", src, readval, s, l); print_byte_array(target.c, TEST_LEN); err = 1; }
init_byte_array(target.c, TEST_LEN, 0x0); bitfield_write(target.s, src, s, l); readval = bitfield_read_64(target.c, s, l, BYTE_ORDER, 1); if (readval != src) { printf("Error (shortwise) src %lX read %llX shift %d len %d\n", src, readval, s, l); print_byte_array(target.c, TEST_LEN); err = 1; }
init_byte_array(target.c, TEST_LEN, 0x0); bitfield_write(target.i, src, s, l); readval = bitfield_read_64(target.c, s, l, BYTE_ORDER, 1); if (readval != src) { printf("Error (intwise) src %lX read %llX shift %d len %d\n", src, readval, s, l); print_byte_array(target.c, TEST_LEN); err = 1; }
init_byte_array(target.c, TEST_LEN, 0x0); bitfield_write(target.l, src, s, l); readval = bitfield_read_64(target.c, s, l, BYTE_ORDER, 1); if (readval != src) { printf("Error (longwise) src %lX read %llX shift %d len %d\n", src, readval, s, l); print_byte_array(target.c, TEST_LEN); err = 1; }
init_byte_array(target.c, TEST_LEN, 0x0); bitfield_write(target.ll, src, s, l); readval = bitfield_read_64(target.c, s, l, BYTE_ORDER, 1); if (readval != src) { printf("Error (longlongwise) src %lX read %llX shift %d len %d\n", src, readval, s, l); print_byte_array(target.c, TEST_LEN); err = 1; } } } if (!err) printf("Success!\n"); else printf("Failed!\n"); return err; }
int run_test(void) { int err = 0; int i;
srand(time(NULL));
srcrand = 0; err |= run_test_unsigned(); srcrand = 0; err |= run_test_signed(); srcrand = 1; err |= run_test_unsigned(); srcrand = ~0U; err |= run_test_unsigned(); srcrand = -1; err |= run_test_signed(); srcrand = (int)0x80000000U; err |= run_test_signed();
for (i = 0; i < NR_TESTS; i++) { srcrand = rand(); err |= run_test_unsigned(); err |= run_test_signed(); } return err; }
int main(int argc, char **argv) { unsigned long src; unsigned int shift, len; int ret; union { unsigned char c[8]; unsigned short s[4]; unsigned int i[2]; unsigned long l[2]; unsigned long long ll[1]; } target; uint64_t readval;
if (argc > 1) src = atoi(argv[1]); else src = 0x12345678; if (argc > 2) shift = atoi(argv[2]); else shift = 12; if (argc > 3) len = atoi(argv[3]); else len = 40;
target.i[0] = 0xFFFFFFFF; target.i[1] = 0xFFFFFFFF; bitfield_write(target.c, src, shift, len); printf("bytewise\n"); print_byte_array(target.c, 8);
target.i[0] = 0xFFFFFFFF; target.i[1] = 0xFFFFFFFF; bitfield_write(target.s, src, shift, len); printf("shortwise\n"); print_byte_array(target.c, 8);
target.i[0] = 0xFFFFFFFF; target.i[1] = 0xFFFFFFFF; bitfield_write(target.i, src, shift, len); printf("intwise\n"); print_byte_array(target.c, 8);
target.i[0] = 0xFFFFFFFF; target.i[1] = 0xFFFFFFFF; bitfield_write(target.l, src, shift, len); printf("longwise\n"); print_byte_array(target.c, 8);
target.i[0] = 0xFFFFFFFF; target.i[1] = 0xFFFFFFFF; bitfield_write(target.ll, src, shift, len); printf("lluwise\n"); print_byte_array(target.c, 8);
readval = bitfield_read_64(target.c, shift, len, BYTE_ORDER, 0); printf("read: %llX\n", readval);
ret = run_test();
return ret; }
-- Mathieu Desnoyers Operating System Efficiency R&D Consultant EfficiOS Inc. http://www.efficios.com
| |