platformio / platform-atmelsam

Atmel SAM: development platform for PlatformIO
https://registry.platformio.org/platforms/platformio/atmelsam
Apache License 2.0
82 stars 108 forks source link

inttypes.h macro PRIu8 generates wrong format string on cortex m0 #70

Closed pbolduc closed 5 years ago

pbolduc commented 5 years ago

Platform IO:

Home: 2.2.0
Core: 4.0.0

VS Code Version:

Version: 1.36.1 (system setup)
Commit: 2213894ea0415ee8c85c5eea0d0ff81ecc191529
Date: 2019-07-08T22:59:35.033Z
Electron: 4.2.5
Chrome: 69.0.3497.128
Node.js: 10.11.0
V8: 6.9.427.31-electron.0
OS: Windows_NT x64 10.0.17763

I am not positive if the issue is related to the board definitions or the Arm Embedded tool chain. I have also created a bug on the GNU Arm Embedded Toolchain - https://bugs.launchpad.net/gcc-arm-embedded/+bug/1836698

I am using Platform IO inside VS Code on Windows 10. I am compiling a library, MySensors, that uses the PRIu8 macro to format various values. I am using a Moteino M0. I have also checked compiled output for a adafruit feather m0. On these Cortex M0+ processors, the PRIu8 expands to hhu.

Note: when compiling on atmelavr, there is no problem.

When I got to the definition, the the following path using Platform IO's "Go to Definition"

C:\Users\username.platformio\packages\toolchain-gccarmnoneeabi\arm-none-eabi\include\inttypes.h

  #define __PRI8(x) __INT8 __STRINGIFY(x)
  #define PRIu8 __PRI8(u)

The macro expands using the __INT8 macro defined in toolchain-gccarmnoneeabi\arm-none-eabi\include\sys_intsup.h

#if (__INT8_TYPE__ == 0)
#define __INT8 "hh"
#elif (__INT8_TYPE__ == 1 || __INT8_TYPE__ == 3)
#define __INT8 "h"
#elif (__INT8_TYPE__ == 2)
#define __INT8
#elif (__INT8_TYPE__ == 4 || __INT8_TYPE__ == 6)
#define __INT8 "l"
#elif (__INT8_TYPE__ == 8 || __INT8_TYPE__ == 10)
#define __INT8 "ll"
#endif

These are common predefined macros as stated here https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html. I am not not really sure where to point the finger on the cause.

Here is an example program

#include <Arduino.h>

#ifdef MOTEINO_M0
#define Serial SerialUSB
#endif

void setup() {
  Serial.begin(9600);
  while (!Serial);

  char buffer[512];
  uint8_t value = 123;
  snprintf(buffer, sizeof(buffer), "value=%" PRIu8 "\n", value);

  Serial.print(buffer);

  Serial.print("PRIu8=");
  Serial.print(PRIu8);
}

void loop() {
}

platformio.ini

[env:adafruit_feather_m0]
platform = atmelsam
board = adafruit_feather_m0
framework = arduino

[env:moteino_zero]
platform = atmelsam
board = moteino_zero
framework = arduino

Output:

value=hu
PRIu8=hhu

Expected:

value=123
PRIu8=hu

When I compile with build_flags = -E, the output looks like:

void setup() {
  SerialUSB.begin(9600);
  while (!SerialUSB);

  char buffer[512];
  uint8_t value = 123;
  snprintf(buffer, sizeof(buffer), "value=%"
# 18 "src\\main.cpp" 3 4
                                            "hh"
# 18 "src\\main.cpp"
                                            "u" "\n", value);

  SerialUSB.print(buffer);

  SerialUSB.print("PRIu8=");
  SerialUSB.print(
# 23 "src\\main.cpp" 3 4
              "hh"
# 23 "src\\main.cpp"
              "u");
}
pbolduc commented 5 years ago

In the gcc arm ticket, they asked if Platform IO is using newlib_nano?

pbolduc commented 5 years ago

I tested this on Ardiuno IDE 1.8.9 and I got the same output,

value=hu
PRIu8=hhu
pbolduc commented 5 years ago

Based on feedback from the gcc-arm-embedded issue, the problem is related to linking to the smaller newlib-nano library. See https://community.arm.com/developer/ip-products/system/b/embedded-blog/posts/shrink-your-mcu-code-size-with-gcc-arm-embedded-4-7

Libraries also need optimizing, because the libraries included in GCC ARM Embedded were not actually designed for MCU programming. Newlib, the C library in the toolchain, implements printf functions that are so complicated they require about 37K bytes of FLASH and 5K bytes of RAM to run a simple hello-world program. That's far too large for MCU programming where you might need printf functionality for debugging and logging purposes. The good news is that there is plenty of unnecessary "fat" in libraries that can be cut.

Newlib-nano cuts some features that were added after C89, which are believed to be rarely used in MCU programming. By limiting the format converter to the C89 standard, format string processing code in printf is greatly reduced.

To summarize, the newlib-nano can cut the size of hello-world programs by around 80%. In extreme cases for C++ programs, the size reduction could exceed 90%.

I have to determine if there is a standard way to know if a certain platform is going to be linked with newlib-nano. However, this is not an issue for this project. For now, I think I will close this issue.