Seiten

Dienstag, 23. Dezember 2014

Part 9: enforceing invariants

Originally I considered explaining how metaprogramming can help enforce invariants which would otherwise be unknown to the compiler by giving the classic dimensional analysis of physics calculations as an example. However many have done this quite well in the past already so I will not attempt to write it all again. This paper by scott meyers explains how it works and the boost.units library is an excellent implementation.


Here I would like to provide an explanation of  using metaprogramming to enforce invariants on special function registers of the processor. This may not be a familiar domain to many of you so I will first provide a minimal explanation of the requirements.
Control of hardware is most commonly realized through the use of Special Function Registers (SFRs). An on chip serial port for example will usually have SFRs for controlling baud rate, pairity, interrupt behavior and a send and receive register connected to internal FIFO buffers. Small processors with a lot of built in periphery like the ARM Cortex-M0 or M3 often have hundreds if not thousands of SFRs. To the processor special function registers look like RAM in a certain address range, this is also true for the optimizer which causes the first problems when dealing with SFRs because the are not ram, they are special. Special functionality like clear on read, or registers who act as an alias into an otherwise unaddressable buffer and point to the next word in the sequence whenever read or written are common examples. The optimizer also disregards the fact that a change in the value of a register is usually observable from the outside in some form so reordering of reads or writes or caching values in registers can also cause problems.

Volatile to the rescue

The quick and dirty solution for teaching the Optimizer to play nice with SFRs is to declare all SFRs volatile (using the volatile keyword in C++). Reads from volatile variables cannot be reordered with respect to other volatile variables and cannot be omitted. For example:

volatile int i;
int j;
int k;

would result in the psydocode:

load i, W0
store W0,  j
load i, W0
store W0, k

If i were not volatile the second load could be optimized away. The same is true for writes.

Code readability and maintanability nightmeres
although volatile is an important tool it still results in ugly, abstract, error prone code. A SFR which contains 7 status bits, with the rest of the bits reserved and which is cleared on read is a good example of common pitfalls.

volatile int& status = (volatile int*)0xFF4478;

if(status == 3){ /* only flags 0 and 1 set */ }
else if(status == 8){ /* only flag 3 set */ }
else if(status == 0x7F) { /* Armageddon*/}

This code may seem correct at first, however consider that since the status register is clear on write we are getting a different value when we load it the second time (in the else if). We could fix the bug by introducing a local copy (which will probably be enregistered any way):

volatile int& status = (volatile int*)0xFF4478;
int s = status;
if(s == 3){ /* only flags 0 and 1 set */ }
else if(s == 8){ /* only flag 3 set */ }
else if(s == 0x7F) { /* Armageddon*/}
This code will probably pass code review and probably ship and will very probably work, however there is another bug here. We are reading a whole register, probably 32 bits, expecting 25 of them to be reserved bits with a value of 0. What if after a few years a new version of the processor comes out which uses a few more of the reserved bits in this register. What if they are 1's now and then, this will break this code in a potentially unpredictable way which will no doubt cause much embarrassment and little leisure time. So how do we fix it? We'll lets just mask off all the reserved bits, that way they cannot harm us.

volatile int& status = (volatile int*)0xFF4478;
int s = status & 0x7F;
if(s == 3){ /* only flags 0 and 1 set */ }
else if(s == 8){ /* only flag 3 set */ }
else if(s == 0x7F) { /* Armageddon*/}

Now this trivial piece of code is getting ugly and worse it is not understandable without intimate knowledge of the target hardware. It is also pretty impossible to unit test for these kind of bugs. This style may be adequate for a one man show where code durability and reliability are of low importance but that scenario is quite seldom.

Policy based class design to the rescue
It would be unfeasible to write a complete class specialization for every register, however using policy based class design we can build each register from an assortment of generic policies.

template<int I_Address, int I_Mask>
struct Readable{
    static inline int read(){
        return (volatile int*) I_Address & I_Mask;
    }
};

template<int I_Address, int I_Mask>
struct ClearOnRead{
    static inline int readAndClear(){
        return (volatile int*) I_Address & I_Mask;
    }
};

template<int I_Address, int I_Mask>
struct WriteOnly{};


template<int I_Address, int I_Mask>
struct Writeable{
    static inline write(int i){
        int r = *(volatile int*) I_Address & !I_Mask;
        *(volatile int*) I_Address) = r | (i&I_Mask);
    }
};

template<int I_Address, int I_Mask>
struct ReadOnly{};


template<int I_Address, int I_Mask, template<int,int> class T_Read, template<int,int> class T_Write>
struct Register : T_Read<I_Address,I_Mask>, T_Write<I_Address,I_Mask> {
   
};

using Status = Register<0xFF4478,0x0000007F,ClearOnRead,ReadOnly>;

 auto s = Status::readAndClear();
if(s == 3) {}
//...

With all the conceivable policies in place we have a per register cost of only one line of code (the using directive) and we have eliminated the possibility of most bugs. In fact the user could potentially write a lot of code without understanding what the volatile keyword is. With the optimizer enables there should also be no performance penalty. 

For more on this strategy I suggest Ken Smiths lightning talk at cppcon https://www.youtube.com/watch?v=lrrQaa_-hzU

There is still room for improvement, for example we should probably add a type parameter in case we want to use a char or some special wrapper class for our register value. We could also add a conversion policy with a meaningful default in case we need to shift around the bits or construct our class in some fancy way. These should however be trivial to implement.

This has taken us a long way towards enforcing invariants, however we can still do better. Most of the numbers we encounter, for example, are not conceptually numbers but represent some functionality. If we are to decouple intimate knowledge of the hardware from the task of writing the application we must use strongly classes enums or some similar syntactic mechanism to represent these values in text form. It would also be handy to define a list of changes which one could give a definitive name like ActivateUART0 and to which one could create aliases like

using ActivateWindSpeedUart = ActivateUart0;