Skip to content

8bit operations are always compiled into 16bit instructions #361

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
michele-bertoni opened this issue Oct 29, 2020 · 2 comments
Closed

8bit operations are always compiled into 16bit instructions #361

michele-bertoni opened this issue Oct 29, 2020 · 2 comments
Labels
conclusion: invalid Issue/PR not valid topic: code Related to content of the project itself type: imperfection Perceived defect in any part of project

Comments

@michele-bertoni
Copy link

After refactoring my code so that all unsigned long I used for managing timestamp became uint8_t in order to save memory and time, I realized that an operation uint8_t - uint8_t could give as result a negative number (e.g. -252); that was very strange for two reasons:

  • unsigned long or unsigned int operations always give a result of the same data type (so definitely non-negative)
  • -252 is not 8bit because byte (or char, or int8_t) values are in [-128, 127] range

I tried investigating and I found out that, not only the operations that can have overflows or underflows (i.e. sum, subtraction, multiplication, logical left shift, etc.), but also those that are guaranteed, given two 8bit operands, to have an 8bit result (i.e. bitwise and, bitwise or, logical right shift, division, etc...) are always executed as 16bit instructions.

I tried to find the origin of this problem and I came across this stackoverflow question: summarazing, on 32bit machines, of course, nbit operations (with n<32) need operands to be 32bit and that's why an uint8_t or a uint16_t difference can give a negative result.
This was partially fixed in the arduino-builder, since uint16_t difference gives a uint16_t result, but of course 8bit operands are still transformed into 16bit numbers: this would be correct if and only if ATmega328P was a 16bit microcontroller, but of course it's an 8bit microcontroller with instructions for handling natively 16bit operations (e.g. a 16 bit sum is done with one add and one adc).

This issue can bring many problems such as:

  • worse performance: any operation needs at least twice the time it needed if 8bit instructions were used (2 clock cycles instead of 1 for sums, subtractions, etc. and probably more for multiplications and divisions)
  • unexpected results: on an 8bit microcontroller I don't expect to put an explicit cast to uint8_t when doing an operation between uint8_t operands; so I will likely have bugs in the code that are very difficult to spot.
@facchinm
Copy link
Member

Hi @michele-bertoni ,
in C/C++ there's an implicit type conversion for operation that cannot fit the given "space".
Writing

  uint8_t x = 1;
  uint8_t y = 255;
  uint8_t z = x-y;
  Serial.println(z);

gives a different result than

  uint8_t x = 1;
  uint8_t y = 255;
  Serial.println(x-y);

The latter will automatically promote the operation result to int since an overload for Serial.print(int) exists.
This is not about arduino-builder but more about C++ standard and compilation flags.
At the end, always better to forcefully cast your numbers 🙂

@michele-bertoni
Copy link
Author

michele-bertoni commented Oct 29, 2020

@facchinm thanks for help. A few moments ago I found this: https://electronics.stackexchange.com/questions/327992/c-integer-promotion-on-8-bit-mcus so I realized that it could be a C/C++ problem. Anyway, I read that for the C standard promotion to int16_t is mandatory but compiler can optimize this operation if he knows that the result is 8bit: so in this code

uint8_t lastTime = millis();
...
uint8_t time = millis();
uint8_t deltaTime = 5;
if((uint8_t)(time - lastTime) < deltaTime) {
    ...
}

time - lastTime will be done in only 1 instruction, but are the subtraction necessary for the blt and the blt itself done in 1 instruction each?

Moreover, if I have

uint8_t lastTime = millis();
...
uint8_t time = millis();
uint8_t deltaTime = time - lastTime;

is it enough, or do I need to write

uint8_t deltaTime = (uint8_t)(time - lastTime);

in order to have the compiler optimization?

Thank you very much for your help.

@per1234 per1234 added conclusion: invalid Issue/PR not valid topic: code Related to content of the project itself type: imperfection Perceived defect in any part of project labels Sep 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
conclusion: invalid Issue/PR not valid topic: code Related to content of the project itself type: imperfection Perceived defect in any part of project
Projects
None yet
Development

No branches or pull requests

3 participants