Skip to content

Fix static initialization order fiasco for psram_allocator globals#837

Open
robertlipe wants to merge 1 commit intoPlummersSoftwareLLC:mainfrom
robertlipe:fix/global-static-order
Open

Fix static initialization order fiasco for psram_allocator globals#837
robertlipe wants to merge 1 commit intoPlummersSoftwareLLC:mainfrom
robertlipe:fix/global-static-order

Conversation

@robertlipe
Copy link
Copy Markdown
Collaborator

Description

Wrap global static objects that use psram_allocator in Meyer's singletons (Construct-On-First-Use) to prevent PSRAM allocation before the memory subsystem is initialized during app_main().

Affected globals:

  • AnimatedGIFs map and g_ptrGIFDecoder (PatternAnimatedGIF.h)
  • weatherIcons map (PatternWeather.h)
  • CWebServer::mySettingSpecs (webserver.cpp/h)

This is particularly terrible because we call psramAlloc() before main() but psram isn't configured until after we're inside of setup().

Contributing requirements

  • I read the contribution guidelines in CONTRIBUTING.md.
  • I understand the BlinkenPerBit metric, and maximized it in this PR.
  • I selected main as the target branch.
  • All code herein is subjected to the license terms in COPYING.txt.

Wrap global static objects that use psram_allocator in Meyer's
singletons (Construct-On-First-Use) to prevent PSRAM allocation
before the memory subsystem is initialized during app_main().

Affected globals:
- AnimatedGIFs map and g_ptrGIFDecoder (PatternAnimatedGIF.h)
- weatherIcons map (PatternWeather.h)
- CWebServer::mySettingSpecs (webserver.cpp/h)
@robertlipe robertlipe marked this pull request as ready for review April 8, 2026 14:47
Comment on lines +271 to +272
auto gif = GetAnimatedGIFs().find(_gifIndex);
if (gif == GetAnimatedGIFs().end())
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two notes before the actual single comment I want to make:

  • I will accept a "nitpicker" sneer on what I'm about to argue for
  • I'll write the suggestion once, but it can be projected to all cases where a global variable was replaced by a function (call), and I do indeed mean to make it for all of those.

Now that the global variable has become a function that is called, can we call the function once within a single function context, and stick the result of the call in a local variable that we then use, if we use it more than once? I know the simple return of a static function variable will be inlined down to nothing, but I'd like to keep our improved code clean in terms of intent.

Copy link
Copy Markdown
Collaborator Author

@robertlipe robertlipe Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepted. I'll change it. (you can quit reading here if you like :-) )

It's funny because this particular instance is one I DID outsource to AI (once I recognized the problem well enough to describe it) and actually MADE it do that. Pointer-chasing has always been a pet peeve of mine. I know the OO die-hards love it, but I understand branch prediction, cache misses, pipeline flushes, the difficulty of proving that a function is pure and that nothing has changed since the last call to it (good luck with that in a threaded world), the need to have the register allocator tiptoe around callee registers to avoid spills, and all that. I thought this project LIKED that model, though, and I was pretty sure that you were the one pitched me on it.

I've not disassembled it, but I'm pretty sure I know that the number of overhea clock cycles in things like

256:        g()->setTextColor(g()->to16bit(CRGB::Black));
257:        g()->setCursor(x-1, y);   g()->print(pszText);
258:        g()->setCursor(x+1, y);   g()->print(pszText);
259:        g()->setCursor(x, y-1);   g()->print(pszText);
260:        g()->setCursor(x, y+1);   g()->print(pszText);
262:        g()->setTextColor(g()->to16bit(CRGB::White));
263:        g()->setCursor(x, y);

or

147:        g()->GetNoise().noise_y += dy * 4;
148:        g()->GetNoise().noise_x += dx * 4;
149:        g()->GetNoise().noise_z += dz * 4;

would make me queasy. Most of the things in deviceConfig resolve down to runtime constants anyway—effectively globals. It's not like we have multiple EffectManagers or JSONWriters or DrawSafeCircles or whatever. They're globals. So I actually rocked the boat here to make it more in line with my understanding of what's "NightDriver-y." :-)

I'll change this to allocate these things once and put them in a member or pass them around as arguments.

That said, if I'm cracking this PR open again (that's OK), I have a different fix in mind for all of this anyway...but that's another area where once I start chopping at that tree, its' going to be a project of its own. :-) At least one of these tables that we're carefully crafting ... sure seems to be an array of constants.

I would offer to move this back to 'draft' to show that I agree this needs some attention and I'll fix it, but, well...

@davepl
Copy link
Copy Markdown
Contributor

davepl commented Apr 9, 2026 via email

@davepl
Copy link
Copy Markdown
Contributor

davepl commented Apr 9, 2026 via email

@robertlipe
Copy link
Copy Markdown
Collaborator Author

robertlipe commented Apr 9, 2026 via email

@davepl
Copy link
Copy Markdown
Contributor

davepl commented Apr 9, 2026 via email

@robertlipe
Copy link
Copy Markdown
Collaborator Author

robertlipe commented Apr 9, 2026 via email

@rbergen
Copy link
Copy Markdown
Collaborator

rbergen commented Apr 9, 2026

@robertlipe @davepl I'll make the comment here because both of you seem to be watching this PR: I'm mildly confused about what the discussion you're having means for this and subsequent PRs. As in, there is a mention of a future PR from Dave, but I don't currently know if that's supposed to be a PR that alters this one, or a follow-up PR, assuming that the one I'm adding this comment to is merged.

The facts being that I now have the time to review things and I like empty PR lists :), I will proceed with reviewing and merging this PR, #801 and #836, and in that order for reasons of review volume - unless I encounter merge conflicts on that path, in which case I will skip the PRs that turn red.

@rbergen
Copy link
Copy Markdown
Collaborator

rbergen commented Apr 9, 2026

Ah, there are pending changes to this PR, which means only the other two I mentioned remain. Onwards and upwards.

@robertlipe
Copy link
Copy Markdown
Collaborator Author

robertlipe commented Apr 9, 2026 via email

@rbergen
Copy link
Copy Markdown
Collaborator

rbergen commented Apr 9, 2026

I can make this draft to show that I have ownership until then (I know you
have mixed feelings about that) if it improves your day in some way.

Actually, I don't like PRs being opened in draft status, but I don't mind them entering that state if the review cycle yields that it should be ignored for a while. I know, that's a very specific Rutgerian distinction, but sorrynotsorry - yeah, I actually did that.

All that being as it may, it doesn't matter anymore, because I have now already established there's nothing to look at. Which makes I will not look at it again until I'm informed (by GitHub or otherwise) that things here have changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants