Friday, September 2, 2016

Even safer bitfields

Safer bitfields

I was reading “Preshing on Programming”'s entry on bitfields; he mentions the lack of runtime checks for overflow on normal bitfields, and sets about dealing with the problem by creating a template and some preprocessing glue to implement the run-time checks.

I thought this effort is valuable and interesting, the bitfield feature of C++ is very hostile to the usual template programming: For example, in a template, how do you get the total number of bits in two adjacent members of a bitfield structure? Bitfield-structures in general are not very useful. One of my fundamental tenets about how to get performance and reliability is by expressing into code sophisticated invariants, I think, for example, the size of a member of a bitfield is something that should be made available to templates, but it is not, which leads to very dangerous ad-hoc code to use them. This is another instance of the missing parts regarding introspection in C++.

John Lakos advocates offering to users your library in several versions, versions that will have the same semantics except performance so that they can trade performance for more thoroughness in the checking of their correct usage. This is very useful for development and debugging. Using the normal templates of C++ it is trivial to introduce a tiny bit of conditional compilation, with very clear semantics, to enable or disable these run-time checks. Since bitfield-structs are hostile to templates, you can’t easily turn on or off safety checks if you implement them.

Preshing’s implementation of the supporting BitFieldMember is sound for the purpose of supporting runtime safety checks, and it would be straightforward to adapt it disable the checks for no performance loss. However, there is an important guarantee absent from his preprocessing macros, that the offset of a field is exactly the sum of sizes of the preceding fields. Another feature I wanted to provide support to is more compile-time introspection (reflection).

Here is a template used to support the rest of the work in bitfields I made:

#include <array>

template<typename T, unsigned... S> struct base {
    constexpr static std::array<unsigned, sizeof...(S)> sizes = { S... };
    constexpr static unsigned displacement(unsigned ndx) {
        return ndx ? sizes[ndx - 1] + displacement(ndx - 1) : 0;
    }
    T value;
};

So far, just a template that gives you a choice for the integral used to hold the bitfield, an array of field sizes and a constexpr function, displacement, to tally the sizes of the predecessors.

We can now use Preshing’s BitFieldMember template, for example:

union manually_done_bitfield {
    using element_t = long;
    using base_t = base<long, 4, 3, 5>;
    BitFieldMember<element_t, base_t::displacement(0), 4> fourBits;
    BitFieldMember<element_t, base_t::displacement(1), 3> threeBits;
    BitFieldMember<element_t, base_t::displacement(2), 5> fiveBits;
};

The manual part of setting the bit sizes to the same as the declarations of the members, as well as the progression of indices, can be coded using boost seq preprocessing:

// Generic macros not specific to this article
#define UNTUPLE_2_1(a, b) a
#define UNTUPLE_2_2(a, b) b
#define PP_SEQ_TUPLE_2_2(s, d, element) UNTUPLE_2_2 element
#define PP_SEQ_ENUM_UNTUPLE_2_2(r, data, e) ,UNTUPLE_2_2 e

// Macros actually used from boost seq preprocessing
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/seq/for_each.hpp>

// Macro to declare a member
#define PP_SEQ_I_MAKE_BITFIELD(r, data, i, element) BitFieldMember<element_t, base_t::displacement(i), UNTUPLE_2_2 element> UNTUPLE_2_1 element;

// The actual union defining macro
#define SAFE_BITFIELD(name, type, fields)\
    union name {\
        using element_t = type;\
        using base_t = base<type BOOST_PP_SEQ_FOR_EACH(PP_SEQ_ENUM_UNTUPLE_2_2, ~, fields)>;\
        BOOST_PP_SEQ_FOR_EACH_I(PP_SEQ_I_MAKE_BITFIELD, ~, fields)\
    }

// The example above expressed as a boost seq
#define example ((fourBits, 4))((threeBits, 3))((fiveBits, 5))

SAFE_BITFIELD(automatic, long, example);

Now, beautifully, we can use static_assert to our hearts’ content:

static_assert(7 == automatic::base_t::displacement(2), "");

From here, there are ways to map the fields to their indices, etc.