Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create an uint8_t array that does not undermine strict aliasing?

I recently asked this question:

Using this pointer causes strange deoptimization in hot loop

The problem was that I was writing to an array of type uint8_t and the compiler treated it as if it could alias with the this pointer of the method (of type struct T*), because void* and char* (=uint8_t*) can always alias any other pointer in C++. This behaviour caused a missed optimization opportunity. I want to avoid this, of course. So the question is: Can I declare an uint8_t array that enforces strict aliasing, i.e., that the compiler treats as never aliased with any pointer of another type? I.e., I am looking for something like a strict_uint8_t type that is an uint8_t with special aliasing behaviour. Is there a way to achieve this?

Example code to show what I mean, borrowed from other question and simplified. For more details, read the linked question and its accepted answer:

struct T{
   uint8_t* target;
   void unpack3bit(char* source, int size) {
        while(size > 0){
           uint64_t t = *reinterpret_cast<uint64_t*>(source);
           /** `this->target` cannot be cached in a register here but has
               to be reloaded 16 times because the compiler
               thinks that `this->target` could alias with `this` itself.
               What I want is a special uint8_t type that does not trigger
               this behaviour. */
           this->target[0] = t & 0x7; 
           this->target[1] = (t >> 3) & 0x7;
           this->target[2] = (t >> 6) & 0x7;
           this->target[3] = (t >> 9) & 0x7;
           this->target[4] = (t >> 12) & 0x7;
           this->target[5] = (t >> 15) & 0x7;
           this->target[6] = (t >> 18) & 0x7;
           this->target[7] = (t >> 21) & 0x7;
           this->target[8] = (t >> 24) & 0x7;
           this->target[9] = (t >> 27) & 0x7;
           this->target[10] = (t >> 30) & 0x7;
           this->target[11] = (t >> 33) & 0x7;
           this->target[12] = (t >> 36) & 0x7;
           this->target[13] = (t >> 39) & 0x7;
           this->target[14] = (t >> 42) & 0x7;
           this->target[15] = (t >> 45) & 0x7;
           source+=6;
           size-=6;
           target+=16;
        }
}
};
like image 265
gexicide Avatar asked Oct 10 '14 10:10

gexicide


1 Answers

You can use a fixed-size enumeration with base type uint8_t:

enum strict_uint8_t : uint8_t {};

If you want to be able to convert to and from uint8_t transparently, you can wrap it in a struct with converting constructor and conversion operator:

struct strict_uint8_t {
    enum : uint8_t {} i;
    strict_uint8_t(uint8_t i) : i{i} {}
    operator uint8_t() const { return i; }
};

This appears to eliminate the aliasing pessimization in gcc and clang: https://godbolt.org/g/9Ta98b

(Note: the previous approach, using a bitfield, worked in gcc but not in clang.)

like image 179
ecatmur Avatar answered Oct 12 '22 23:10

ecatmur