From 4a937f477d4ec171e5b479d713ea48e406e4445a Mon Sep 17 00:00:00 2001 From: Ray Morris Date: Wed, 20 May 2026 15:12:07 -0500 Subject: [PATCH 1/2] fix: prevent LTO from breaking settingGet/settingGetIndex round-trip Under INTERPROCEDURAL_OPTIMIZATION, the compiler inlines settingGet and settingGetIndex with divergent static settingsTable base addresses, causing MSPV2_SETTING to return wrong data for some settings while MSP2_COMMON_SETTING_INFO (name lookup) is correct. --- src/main/fc/settings.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/fc/settings.c b/src/main/fc/settings.c index 912c38362bf..dda81b61491 100644 --- a/src/main/fc/settings.c +++ b/src/main/fc/settings.c @@ -124,12 +124,14 @@ const setting_t *settingFind(const char *name) return NULL; } -const setting_t *settingGet(unsigned index) +// noinline: LTO inlines these with divergent settingsTable base addresses, +// breaking the settingGetIndex -> settingGet round-trip. +__attribute__((noinline)) const setting_t *settingGet(unsigned index) { return index < SETTINGS_TABLE_COUNT ? &settingsTable[index] : NULL; } -unsigned settingGetIndex(const setting_t *val) +__attribute__((noinline)) unsigned settingGetIndex(const setting_t *val) { return val - settingsTable; } From 44c5934ffef7013ad7b1fd42c4b004cfb614d38e Mon Sep 17 00:00:00 2001 From: Ray Morris Date: Thu, 21 May 2026 16:15:00 -0500 Subject: [PATCH 2/2] fix: address code review issues in LTO noinline fix - Improve comment accuracy: describe observed behavior rather than the inferred mechanism; note why raw attribute is used instead of NOINLINE macro (NOINLINE is empty on non-F7/H7 targets but bug affects all LTO release builds) - Add noinline to settingGetPgn which has identical pointer arithmetic against settingsTable and is called from settingGetValuePointer - Add brief comments above settingGetIndex and settingGetPgn pointing back to the main explanation on settingGet --- src/main/fc/settings.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/fc/settings.c b/src/main/fc/settings.c index dda81b61491..e3be177fb16 100644 --- a/src/main/fc/settings.c +++ b/src/main/fc/settings.c @@ -124,13 +124,19 @@ const setting_t *settingFind(const char *name) return NULL; } -// noinline: LTO inlines these with divergent settingsTable base addresses, -// breaking the settingGetIndex -> settingGet round-trip. +// Under release builds (LTO enabled), inlining settingGet and settingGetIndex +// at their call sites in fc_msp.c produces wrong results for the +// settingGetIndex(ptr)->settingGet(index) round-trip, causing MSPV2_SETTING +// to return incorrect byte counts for some uint16_t settings. +// Use the raw attribute rather than NOINLINE because NOINLINE expands to +// nothing on non-F7/H7 targets (common.h:29), but LTO is enabled for all +// release targets and the bug affects all of them. __attribute__((noinline)) const setting_t *settingGet(unsigned index) { return index < SETTINGS_TABLE_COUNT ? &settingsTable[index] : NULL; } +// noinline: same reason as settingGet above __attribute__((noinline)) unsigned settingGetIndex(const setting_t *val) { return val - settingsTable; @@ -217,7 +223,9 @@ size_t settingGetValueSize(const setting_t *val) return 0; // Unreachable } -pgn_t settingGetPgn(const setting_t *val) +// noinline: same reason as settingGet above; also does pointer arithmetic +// against settingsTable and is called from settingGetValuePointer. +__attribute__((noinline)) pgn_t settingGetPgn(const setting_t *val) { uint16_t pos = val - (const setting_t *)settingsTable; uint16_t acc = 0;