Search code examples
cbit-manipulationc-preprocessorbitmaskavr-gcc

Writing a variadic macro which sets specific bits in an integer (bit-mask)


I'm trying to write a macro which simplifies setting multiple bits in an integer. This commonly occurs in microcontroller code when initializing configuration registers. For example, one might configure an 8-bit timer by setting 3 bits in the register TCCR0A like this:

// WGM01, WGM00 and COM0A1 are constants between 0 and 7
// There are hundreds of these constants defined in avr-libc
TCCR0A |= (1<<WGM01) | (1<<WGM00) | (1<<COM0A1);

// Another way to write this:
#define _BV(bit) (1 << (bit)) // <-- defined in avr-libc
TCCR0A |= _BV(WGM01) | _BV(WGM00) | _BV(COM0A1);

However, I'd find it a lot easier to write something like this:

TCCR0A |= BITS(WGM01, WGM00, COM0A1); // <- Variable # of arguments please!

Since I can't imagine that nobody has thought of this yet, I searched around but found nothing which does exactly this. I wonder if this is possible at all, but I gave it a shot anyways while reading https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html and https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms.


Here's what I tried so far. I imagine the solution must be recursive macro, but didn't get very far when trying to get it to expand correctly. Since all my registers are 8 bits long, 8 expansion passes should be sufficient (for a first try).

#define BITS_EVAL(...)  BITS_EVAL1(BITS_EVAL1(BITS_EVAL1(__VA_ARGS__)))
#define BITS_EVAL1(...) BITS_EVAL2(BITS_EVAL2(BITS_EVAL2(__VA_ARGS__)))
#define BITS_EVAL2(...) __VA_ARGS__

#define BITS(bit, ...) ((1 << bit) | BITS_EVAL(BITS(__VA_ARGS__)))

The above doesn't quite work. What it currently does is:

// BITS(2,5,7) --> ((1 << 2) | BITS(5, 7))

However, what I would like to achieve is one of these (or equivalent):

// BITS(2,5,7) --> ((1 << 2) | (1 << 5) | (1 << 7))
// BITS(2,5,7) --> ((1 << 2) | ((1 << 5) | ((1 << 7))))

Can anyone help me with my quest, or tell me that it's impossible to achieve this?


Solution

  • Warning: Writing this was mostly a learning exercise.

    DO NOT USE IN PRODUCTION CODE. People will rightly curse at you if you do.

    So, after playing around a bit more with the macros from Paul's answers and github wiki, I actually managed to produce a working BITS(...) macro which does what I intended. It is a recursive macro that is scanned multiple times to expand the recursive replacements. It handles a variable number of arguments and supports integers up to 64 bits.

    // test.c
    #include "bits.h"
    int a = BITS(1,5,7);
    int b = BITS(3);
    int c = BITS(); // This case is broken but irrelevant
    

    Using gcc -E test.c -o test.txt this expands to:

    int a = (0 | (1ull<<1) | (1ull<<5) | (1ull<<7));
    int b = (0 | (1ull<<3));
    int c = (0 | (1ull<<)); // This case is broken but irrelevant
    

    The 0 | at the beginning is an artifact of the implementation but obviously does not affect the result of the expression.


    Here's the actual implementation including comments:

    // bits.h
    // Macros partially from https://github.com/pfultz2/Cloak
    #define EMPTY(...)
    // Defers expansion of the argument by 1, 2 or 3 scans
    #define DEFER(...) __VA_ARGS__ EMPTY()
    #define DEFER2(...) __VA_ARGS__ DEFER(EMPTY)()
    #define DEFER3(...) __VA_ARGS__ DEFER2(EMPTY)()
    
    // Concatenate the arguments to one token
    #define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
    
    // Apply multiple scans to the argument expression (>64 to allow uint64_t masks)
    #define EVAL(...)  EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
    #define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
    #define EVAL2(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
    #define EVAL3(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
    #define EVAL4(...) __VA_ARGS__
    
    // Always expand to the second token after expansion of arguments.
    // One level of indirection to expand arguments before selecting.
    #define SELECT_2ND(...) SELECT_2ND_INDIRECT(__VA_ARGS__, , )
    #define SELECT_2ND_INDIRECT(x1, x2, ...) x2
    
    // Expands to a comma (which means two empty tokens in a parameter list).
    // Thus, SELECT_2ND will expand to an empty token if this is the first argument.
    #define BITS_RECURSIVE__END_RECURSION ,
    
    // Adds the END_RECURSION parameter, which marks the end of the arguments
    #define BITS(...) \
        (0 EVAL(BITS_RECURSIVE(__VA_ARGS__, END_RECURSION,)))
    
    // When hitting END_RECURSION, the CAT will expand to "," and SELECT_2ND
    // will select the empty argument instead of the recursive call.
    #define BITS_RECURSIVE(bit, ...) \
        SELECT_2ND(PRIMITIVE_CAT(BITS_RECURSIVE__, bit), \
                 | (1ull<<(bit)) DEFER3(BITS_INDIRECT)()(__VA_ARGS__))
    // Needed to circumvent disabling contexts for recursive expansion
    #define BITS_INDIRECT() BITS_RECURSIVE
    

    And some code to test the extreme cases:

    // test2.c
    #include "bits.h"
    #include <inttypes.h>
    #include <stdio.h>
    
    uint8_t u8 = BITS(0,1,2,3,4,5,6,7);
    uint32_t u32 = BITS(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
            16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31);
    uint64_t u64 = BITS(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
            16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,
            32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,
            48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63);
    uint64_t a64 = BITS(0,1,2,3,4,5,6,7,
            16,17,18,19,20,21,22,23,
            32,33,34,35,36,37,38,39,
            48,49,50,51,52,53,54,55);
    
    int main(void) {
        printf("0x%02" PRIX8 "\n", u8);    // Prints 0xFF
        printf("0x%08" PRIX32 "\n", u32);  // Prints 0xFFFFFFFF
        printf("0x%016" PRIX64 "\n", u64); // Prints 0xFFFFFFFFFFFFFFFF
        printf("0x%016" PRIX64 "\n", a64); // Prints 0x00FF00FF00FF00FF
        return 0;
    }