bblanchon/ArduinoJson

Maybe use `gnu::noclone` to reduce program size

jputcu opened this issue · 9 comments

This is just an experiment I did with something new I found, so it could have consequences I'm didn't foresee.

Trying to get my code from avr-gcc-7.3 to avr-gcc-14.1 to fit my microcontroller, I had to get rid of some heavy cloned methods.
Researching the internet I found the following compiler attribute: gnu::noclone.

Appliying this technique with avr-gcc-7.3 and ArduinoJson-6.21.4 on a sample method CollectionData::getSlot:

0000206e 00000070 t ArduinoJson::V6214IA1::detail::VariantSlot* ArduinoJson::V6214IA1::detail::CollectionData::getSlot<ArduinoJson::V6214IA1::detail::FlashString>(ArduinoJson::V6214IA1::detail::FlashString) const [clone .isra.119]
00005aa4 00000072 t ArduinoJson::V6214IA1::detail::VariantSlot* ArduinoJson::V6214IA1::detail::CollectionData::getSlot<ArduinoJson::V6214IA1::detail::FlashString>(ArduinoJson::V6214IA1::detail::FlashString) const [clone .isra.67]

Adding the [[gnu::noclone]] to the relevant method in ArduinoJson/Collection/CollectionImpl.hpp:

template <typename TAdaptedString>
[[gnu::noclone]]
inline VariantSlot* CollectionData::getSlot(TAdaptedString key) const {

Leaving me with a single instance:

00002df8 00000074 W ArduinoJson::V6214IA1::detail::VariantSlot* ArduinoJson::V6214IA1::detail::CollectionData::getSlot<ArduinoJson::V6214IA1::detail::FlashString>(ArduinoJson::V6214IA1::detail::FlashString) const

Flash size went from 74364 to 74286 bytes.
I don't know why the compiler, especially the newer one, decides to clone large methods, but it probably has to do with locality to the source files where the methods are used.
Maybe lto could fix this?

Some other duplicate entries:

00001f24 00000038 t ArduinoJson::V6214IA1::Converter<char const*, void>::toJson(char const*, ArduinoJson::V6214IA1::JsonVariant) [clone .isra.109]
00006060 00000038 t ArduinoJson::V6214IA1::Converter<char const*, void>::toJson(char const*, ArduinoJson::V6214IA1::JsonVariant) [clone .isra.70]

000080ba 0000001e t ArduinoJson::V6214IA1::Converter<unsigned char, void>::toJson(unsigned char, ArduinoJson::V6214IA1::JsonVariant) [clone .isra.130]
00009c2c 0000001e t ArduinoJson::V6214IA1::Converter<unsigned char, void>::toJson(unsigned char, ArduinoJson::V6214IA1::JsonVariant) [clone .isra.191]
0000601a 0000001e t ArduinoJson::V6214IA1::Converter<unsigned char, void>::toJson(unsigned char, ArduinoJson::V6214IA1::JsonVariant) [clone .isra.61]
00001f06 0000001e t ArduinoJson::V6214IA1::Converter<unsigned char, void>::toJson(unsigned char, ArduinoJson::V6214IA1::JsonVariant) [clone .isra.90]

00000283 00000018 V ArduinoJson::V6214IA1::detail::FloatTraits<float, 4u>::negativeBinaryPowersOfTen()::factors
0000029b 00000018 V ArduinoJson::V6214IA1::detail::FloatTraits<float, 4u>::positiveBinaryPowersOfTen()::factors

How did you install avr-gcc 14.1?
I tried installing with apt on Ubuntu 24.04 and got avr-gcc 7.3.0.

I've been carefull to report this issue using the 7.3, which is provided by Arduino/Microchip and I'm currently using in production.

To get the newer compiler I use ZakKemble's avr-gcc build (https://github.com/ZakKemble/avr-gcc-build). I've created a conan package for the different variants: Microchip/Arduino and ZakKemble and just use different profiles: https://github.com/jputcu/avr_conan.

I compiled JsonParserExample.ino with the Arduino IDE targeting the Arduino UNO.
As far as I can tell, this is using avr-gcc 7.3.0.
Yet, when I run avr-nm --size-sort -C -r "<path-to-the-elf-file>", I don't see any clone.

Can you provide a complete reproduction procedure?

I was thinking about that, will see what I can do to trigger cloning with a minimal example

I found another workaround: The reason for the 2 getSlot was a custom DateTimeTm converter that was called from 2 different source files. When moving the functions calling these conversions into the same source file the clone goes aways:

Flash 74348 -> 74238

namespace ArduinoJson {
template <> struct Converter<DateTimeTm> {
  [[gnu::noinline]] static DateTimeTm fromJson(JsonVariantConst src) {
    return DateTimeTm{src[JKEY("datetime")].as<const char *>()};
  }
}

I'm trying to reproduce this situation with a simple Arduino program, but I notice it is using lto.

Ok, enabled lto on the newer avr-gcc-14.2 on msys2. This gave me 73586 instead of 76598 but there were some lto compilation warnings. Duplicates are gone now and the application still works

noclone works, but lto seems to be the correct way to avoid clones (and also template reuse).

lto is enabled by Arduino on the avr-gcc-7.3, even with debug, so I think it is a safe bet.
I did see more stack usage but this is probably due to some methods now no longer being inlined.

Thank you very much for the update.