Why Did Templating a Function Free Up Space in drom0_0_seg?

Yoneech
Posts: 2
Joined: Fri May 30, 2025 7:25 pm

Why Did Templating a Function Free Up Space in drom0_0_seg?

Postby Yoneech » Fri May 30, 2025 8:01 pm

Hello!

I'm currently making a project using a FireBeetle 2 ESP32-E using PlatformIO through VSCode, inspired by this project:
https://github.com/lmarzen/esp32-weather-epd

Instead of using this project directly, I wanted to stretch my skills and learn what it's like to develop for a more restrictive platform.

To speed up some of the boilerplate work, I copied the "esp32-weather-epd-assets" library provided in the above repo. This is a library of fonts and bitmaps of varying sizes converted into header files.

I wrote a function that takes an unsigned short ID, an unsigned short for the bitmap size requested, and a bool to determine which version of the bitmap to return. Below is a (very truncated) snippet of the code:

Code: Select all

const uint8_t* GetBitmapFromCode(const uint16_t code, const uint16_t bitmapSize, const bool isDay)
{
    switch(code)
    {
        case 200:
            if (ICON_TYPE == IT_DAY_NIGHT) { return isDay ? getBitmap(wi_day_storm_showers, bitmapSize) : getBitmap(wi_night_storm_showers, bitmapSize); }
            else if (ICON_TYPE == IT_DAY_NIGHT_ALT) { return isDay ? getBitmap(wi_day_storm_showers, bitmapSize) : getBitmap(wi_night_alt_storm_showers, bitmapSize); }
            return getBitmap(wi_storm_showers, bitmapSize); 
        case 221:
            if (ICON_TYPE == IT_DAY_NIGHT) { return isDay ? getBitmap(wi_day_thunderstorm, bitmapSize) : getBitmap(wi_night_thunderstorm, bitmapSize); }
            else if (ICON_TYPE == IT_DAY_NIGHT_ALT) { return isDay ? getBitmap(wi_day_thunderstorm, bitmapSize) : getBitmap(wi_night_alt_thunderstorm, bitmapSize); }
            return getBitmap(wi_thunderstorm, bitmapSize);            
        // Etc...
        default:
            return getBitmap(wi_na, bitmapSize);
    }
}
(ICON_TYPE is defined in a separate header.)

However, when I called this function I received the following error:
section `.flash.rodata' will not fit in region `drom0_0_seg'

My understanding is that "drom" is where read-only data is stored. My assumption is that something about this function caused the compiler to store too much data in the drom segment of memory.

I looked back and forth between my code and the repo my project is based on, and I was struggled to figure out the difference. I first added the following to my platformio.ini:

Code: Select all

board_build.partitions = huge_app.csv

However, I still ran into the same error.

Eventually I noticed that the repo's version of this function is not called directly. It's located in a CPP file (not declared in a header), and is templated. It's then called by other functions.

I tried putting it solely in the CPP file and calling it from a different function, but I still ran into the same error. However, Once I templated the function everything worked.

Code: Select all

template<int bitmapSize>
const uint8_t* GetBitmapFromCode(const uint16_t code, const bool isDay)
{
    // Code...
}

// Instead of calling GetBitmapFromCode directly from other parts of the code, we call GetCurrentConditionBitmap instead.
const uint8_t* GetCurrentConditionBitmap(const Weather::Forecast& forecast)
{
    return GetBitmapFromCode<160>(forecast.weather.weatherCode, true);
}

In fact provided me with more space than I had before.

Code: Select all

RAM:   [===       ]  28.8% (used 94320 bytes from 327680 bytes)
Flash: [===       ]  32.6% (used 1025661 bytes from 3145728 bytes)

I was wondering if anyone had any insight as to why templating this function solved my issue? I have some guesses, but I'd like to improve my understanding of C++ and compilers, so any insight would be helpful!

Thanks!

ahsrabrifat
Posts: 201
Joined: Sat Jan 18, 2025 2:31 pm

Re: Why Did Templating a Function Free Up Space in drom0_0_seg?

Postby ahsrabrifat » Sat May 31, 2025 11:24 am

Use PROGMEM or ESP32-specific attribute macros (like DRAM_ATTR, RODATA_ATTR) if you want finer control over memory placement.

Avoid global variables or static data in header files (unless templated or constexpr) — they often lead to duplicate symbol definitions or unintentional data linking.

MicroController
Posts: 2668
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Why Did Templating a Function Free Up Space in drom0_0_seg?

Postby MicroController » Sun Jun 01, 2025 9:21 pm

In short, by turning the function into a template you 1) eliminated one function argument and 2) had to make sure the compiiler 'sees' the definition of the template wherever you call the templated function.
Taken together, 1) and 2) mean that the former bitmapSize argument is now always, and compiler-visibly, a compile-time constant (constexpr in fact), which can also be propagated to transitively called functions, allowing the compiler to omit all code, and data, which it can now determine at compile-time is not reachable from the instantiated function.

Phrased differently, by templating the function each set of template arguments creates a different function, with potentially different dependencies on global data. Only the template instantiations you actually use will be compiled into the binary.

For a regular, non-static function (external linkage), the compiler cannot make any assumptions about what argument values some other code may pass to the function, and hence it cannot omit any code/data which might be reachable by any combination of arguments.

A minimal example:

Code: Select all

static const uint8_t BIG_DATA_1[1000000] = ...;
static const uint8_t BIG_DATA_2[1000000] = ...;

/* non-static! */ const uint8_t* getData(const bool selector) {
  if(selector) {
    return BIG_DATA_1;
  } else {
    return BIG_DATA_2;
  }
}
Here, because getData() has external linkage, the compiler must assume that at some point, from some (external) code, getData(true) and getData(false) may be called, so it must include both branches of the if and both arrays of constant data in the binary.
If getData(...) were 'static', and all calls were made with the same (compile-time constant) argument, e.g. getData(true), the compiler could omit one of the constant arrays.

Turned into a template function, the same effect occurs:

Code: Select all

// Template:
template<bool SELECTOR>
const uint8_t* getData(void) {
  if (SELECTOR) {
    return BIG_DATA_1;
  } else {
    return BIG_DATA_2;
  }
}
From this template, you can create exactly 2 different instantiations, which the compiler will turn into two functions like:

Code: Select all

// All possible instances of the template:
// getData<true>(void):
const uint8_t* getData__true(void) {
  /*
  if (true) {
    return BIG_DATA_1;
  } else {
    return BIG_DATA_2;
  }
  optimizes to:
  */
  return BIG_DATA_1;
}

// getData<false>(void):
const uint8_t* getData__false(void) {
  /*
  if (false) {
    return BIG_DATA_1;
  } else {
    return BIG_DATA_2;
  }
  optimizes to:
  */
  return BIG_DATA_2;
}
If your code only ever calls getData<true>(), i.e. the "getData__true(void)" instantiation, the other instantiation "getData__false(void)" will never be generated or compiled, so BIG_DATA_2 is never referenced and can be omitted.
Last edited by MicroController on Mon Jun 09, 2025 9:10 pm, edited 2 times in total.

Yoneech
Posts: 2
Joined: Fri May 30, 2025 7:25 pm

Re: Why Did Templating a Function Free Up Space in drom0_0_seg?

Postby Yoneech » Mon Jun 02, 2025 2:12 pm

I see, that makes sense! That was along the lines of what I theorized was the case, however I wasn't aware that the compiler treated templates like that!

Thank you so much!

MicroController
Posts: 2668
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Why Did Templating a Function Free Up Space in drom0_0_seg?

Postby MicroController » Mon Jun 16, 2025 12:03 am

It is helpful to recall that in C++ a "template" is actually a template.
A "template function" is not a function, and a "template type" is not a type. Both are templates which tell the compiler how to generate functions or types. Therefore, the more precise wording is actually "function template" and "type template".

The 'trick' is that the compiler creates ("instantiates") functions/types from templates automatically whenever you 'use' them.

Who is online

Users browsing this forum: No registered users and 1 guest