Variable type check in preprocessor

Go To Last Post
4 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I made myself a macro to avoid the expensive use of modulo for a 1-incrementing value. Unlike "var = (var+1)%MOD", which seem to be interrupt safe, this macro is not.

 

Actually I have two versions which execute in slightly different time but also have a slightly different effective program size depending on the location in the code. I don't quite understand it at the moment.

 

The two versions of the macro:

// fast except with volatile; often larger

#define plusOneModuloX(var, x) if (++(var)>=(x)) (var)=0;

// fast with volatile; often smaller

#define plusOneModuloX(var, x) (var) = ((var) >= (x)-1 ? 0 : (var)+1)

 

 

I would like to combine this macro with calls to cli() and sei() to make it interrupt-safe, only if one of the input variables are defined as volatile.

 

Is there a way to do this in the preprocessor?

(I know there's generally not much point in shaving off two cycles or four program bytes like this.)

 

The two options I currently have are to either make sure to manually supply calls to cli() and sei() for interrupt-sensitive sections, or to put these calls into the macro in general.

 

Approximate average execution speeds over 50000 calls using 16 MHz ATmega328 (e.g. ~0.0625 us per cycle). MODULO is a constant, var is uint8_t.

non-volatile var:

if (++var>=MODULO) var=0: 0.24 us per call // 4 cycles

cli(); if (++var>=MODULO) var=0; sei(): 0.37 us per call // 6 cycles

var = (var >= MODULO-1 ? 0 : var+1): 0.35 us per call
cli(); var = (var >= MODULO-1 ? 0 : var+1); sei(): 0.48 us per call

 

volatile var:

if (++var>=MODULO) var=0: 0.76 us per call // 12 cycles

cli(); if (++var>=MODULO) var=0; sei(): 0.88 us per call // 14 cycles

var = (var >= MODULO-1 ? 0 : var+1): 0.64 us per call

cli(); var = (var >= MODULO-1 ? 0 : var+1); sei(): 0.77 us per call

 

 

 

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Is there a way to do this in the preprocessor?

No the preprocessor is just that - it's a text substitution system that runs on what could actually be just plain text, not even C and does nothing but text substituting - as such it knows nothing about "types" and so on.

 

What might work for you are "templates". This would mean switching from C to C++ where you want to use this mechanism but templates in C++ are often described as "type aware macros" and they might be used to achieve what you want.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A partial solution ( but the title is wrong: type is defined in a macro, not checked at all ) would look like that:
 

//1rst test (with avr-cpp )

#define vol_create(x) volatile int x = 0;

//2nd test : type is transmitted (t)
#define vol_create2(x, t) volatile t x = 0;

#define VOL_INCR(x,m) do {\
       cli();\
       if (x >= (m+1) ) { x =0;} else { x++;}\
       sei();\
  } while (1 == 0)

//

// semicolon is within this macro...

vol_create2(g_i, char)
int main() {
//  g_i++;
   VOL_INCR(g_i,111);

......

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson, thanks for getting me in on the use of templates! I had not used these previously.

 

I came up with two solutions.

 

Solution 1: make two template functions, one of which is for volatile main variable.

Drawback: When using #define constants for the modulo value, they seem to be defined as regular int type when using template functions, which is 2 bytes long, even if the value would fit in 1 byte. (I do not see this "problem" of using more space than necessary when using preprocessor substitution as in my initial example.)

 

template<typename T, typename M>
static inline __attribute__((always_inline)) void plusOneModuloX(T &var, M mod) {
  if (++var >= mod) var=0;
};

template<typename T, typename M> 
static inline __attribute__((always_inline)) void plusOneModuloX(T volatile &var, M mod) {
  cli();
  if (++var >= mod) var=0;
  sei();
};

 

Solution 2: make four template functions, only for calls to cli() and sei(), and insert on either end of the original macro.

#define plusOneModuloX(var, x) cli_ifvolatile(var); if (++(var)>=(x)) { (var)=0; } sei_ifvolatile(var)

template<typename T>
static inline __attribute__((always_inline)) void cli_ifvolatile(T volatile &var) { cli(); };
template<typename T>
static inline __attribute__((always_inline)) void sei_ifvolatile(T volatile &var) { sei(); };

template<typename T>
static inline __attribute__((always_inline)) void cli_ifvolatile(T &var) { };
template<typename T>
static inline __attribute__((always_inline)) void sei_ifvolatile(T &var) { };

 

Time comparison:

mod argument: #define MOD 5
variable type defined below

S1, uint8_t, 0.43 us per call
S2, uint8_t, 0.25 us per call

S1, uint16_t, 0.38 us per call
S2, uint16_t, 0.51 us per call

S1, uint32_t, 0.77 us per call
S2, uint32_t, 0.65 us per call


S1, volatile uint8_t, 0.76 us per call
S2, volatile uint8_t, 0.65 us per call

S1, volatile uint16_t, 1.04 us per call
S2, volatile uint16_t, 1.05 us per call

S1, volatile uint32_t, 1.84 us per call
S2, volatile uint32_t, 1.85 us per call

Solution 1 and solution 2 speeds above appear different only because modulo argument from #define is treated as different types.
When using the same type, the solutions appear to execute in the same amount of time.