Electronics > Microcontrollers

[C/C++] Force subtraction to perform comparison

(1/11) > >>

emece67:
Hi all,

In an application I use some timestamps (signed 32 bit integers counting μs since system reset) and I want to test if they have elapsed comparing them against current time. Now consider this code (compiled with armclang for Cortex-M4):

--- Code: ---int a, b, cntcomp, cntsubs;

a = 0;
cntcomp = 0;
cntsubs = 0;

do {
  b = a + 5;      // b is "always" greater that n
 
  if (!(b > a)) {
    ++cntcomp;    // triggered 5 times, when previous add overflows
  }
 
  if (!(b - a > 0)) {
    ++cntsubs;    // triggered 0 times
  }
} while (++a);
--- End code ---

I do know that the proper way to compare signed timestamps is by subtraction and then checking the sign of the result, as done above in the 2nd test. On such test the generated code does really subtract, get the result into a register and them compares such register against 0. The first test, instead, is compiled into a compare between registers, not storing the result, and a status flag test.

My problem is that I cannot (easily) coerce the compiler to always perform the comparison in the right way, and the same (b - a > 0) expression above, placed in other part of the program (or using different compiler options), may result in the wrong test.

Up to now I have found 2 ways to circumvent this:

* insert some inline assembly to perform the comparison in the right way (works, but ugly, and the compiler inserts a pair of unneeded instructions to convert the boolean I compute into, well, a boolean). My current version is:
--- Code: ---bool        timedout;
// ...
__asm (
    "SUBS   %[timedout], %[__ticks_low], %[t_s]\n"
    "ASRS   %[timedout], #31\n"
    "MVNS   %[timedout], %[timedout]\n"
  : [timedout] "=r" (timedout)
  : [__ticks_low] "r" (__ticks_low), [t_s] "r" (t_s)
  : // empty
);
--- End code ---

* use an intermediate variable to store the result, but such variable must be qualified as volatile in order to ensure that the comparison against 0 is performed, so there are 2 unneeded memory accesses to the stack. My new candidate is:
--- Code: ---int volatile  diff;
// ...
(diff = __ticks_low - t_s, diff >= 0)
--- End code ---

So my question is: is there a way to enforce the right kind of comparison (subtracting and comparing against 0) not suffering from the previous drawbacks?

Thanks & regards.


EDIT: note that modifying the underlying framework to use 64 bit timestamps is not an option.

EDIT2: maybe am I getting to much anal?

DavidAlfa:
Use parenthesis to perform an operation before anything else.

--- Code: ---  if ( !( (b - a) > 0) ) {
    ++cntsubs;    // triggered 0 times
  }

--- End code ---

Are a and b modified by some interrupt?
Then you must declare them as volatile, or the compile won't expect them randomly changing, ex.:

--- Code: ---uint32_t time=0;
void main(){
  if(time>1000){
    // This might never work, compiler thinks it's always 0
    // because there's no direct call to Timer ISR
    // (Because it's an interrupt)
  }
}

void Timer_ISR(){
  time++;
}

--- End code ---
Declaring as volatile completely changes how the compiler treats the variable:

--- Code: ---volatile uint32_t time=0;
void main(){
  if(time>1000){
    // This will work, compiler understands time can change at any time
    // So it'll always check it, not optimizing or caching
  }
}

void Timer_ISR(){
  time++;
}

--- End code ---

brucehoult:

--- Quote from: DavidAlfa on May 13, 2022, 10:55:39 am ---Use parenthesis to perform an operation before anything else.

--- End quote ---

No, that's fine as is. The compiler understands C.


--- Quote ---Are a and b modified by some interrupt?
Then you must declare them as volatile, or the compile won't expect them randomly changing, ex.:

--- End quote ---

That's not the problem either. The problem is using signed int.

brucehoult:

--- Quote from: emece67 on May 13, 2022, 10:22:23 am ---Hi all,

In an application I use some timestamps (signed 32 bit integers counting μs since system reset) and I want to test if they have elapsed comparing them against current time. Now consider this code (compiled with armclang for Cortex-M4):

--- Code: ---int a, b, cntcomp, cntsubs;

a = 0;
cntcomp = 0;
cntsubs = 0;

do {
  b = a + 5;      // b is "always" greater that n
 
  if (!(b > a)) {
    ++cntcomp;    // triggered 5 times, when previous add overflows
  }
 
  if (!(b - a > 0)) {
    ++cntsubs;    // triggered 0 times
  }
} while (++a);
--- End code ---

--- End quote ---

What are you actually trying to do here?

The test code is crazy because the compiler can easily see the fixed relationship between a and b. And since you declared them as signed types, the compiler is entitled to assume that you know what you are doing and a will never have a value that makes a+5 overflow. It can assume the mathematical relationship between a and b.

A correct thing for the compiler to do here is to replace the whole thing with "a=cntcomp=cntsubs=0". And in fact I see gcc -O1 doing exactly that.

The correct way to do this is to store the timestamps as unsigned -- which the compiler must treat as modulo(2^32) arithmetic -- and to cast the result of the subtraction to signed.


--- Code: ---int timeHasElapsed(unsigned timeStamp, unsigned now){
  return (int)(now - timeStamp) > 0;
}

--- End code ---

Note that you can only set a timestamp a maximum of 2^31-1 ticks ahead of the current time. If you need a larger delay than that then you'll have to do it in parts. You also must call timeHasElapsed() less than 2^31 ticks after the current time is past the timestamp.

wek:

--- Quote from: brucehoult on May 13, 2022, 11:53:15 am --- The correct way to do this is to store the timestamps as unsigned -- which the compiler must treat as modulo(2^32) arithmetic -- and to cast the result of the subtraction to signed.
--- End quote ---

Strictly speaking, this is not entirely flawless either. While subtraction of two unsigned integers indeed won't overflow thanks to second sentence of C99 6.2.5#9, casting unsigned to signed of the same width is implementation defined (6.3.1.3#3), so you have to check documentation of your compiler and accept that it's non-portable.

JW

Navigation

[0] Message Index

[#] Next page

There was an error while thanking
Thanking...
Go to full version