Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86: Add __attribute_const__ to ffs()-family implementations

While tracking down a problem where constant expressions used by
BUILD_BUG_ON() suddenly stopped working[1], we found that an added static
initializer was convincing the compiler that it couldn't track the state
of the prior statically initialized value. Tracing this down found that
ffs() was used in the initializer macro, but since it wasn't marked with
__attribute__const__, the compiler had to assume the function might
change variable states as a side-effect (which is not true for ffs(),
which provides deterministic math results).

Add missing __attribute_const__ annotations to x86's implementations of
variable__ffs(), variable_ffz(), __fls(), variable_ffs(), and fls() functions.
These are pure mathematical functions that always return the same result for the same
input with no side effects, making them eligible for compiler optimization.

Build tested ARCH=x86_64 defconfig with GCC gcc 14.2.0.

Link: https://github.com/KSPP/linux/issues/364 [1]
Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://lore.kernel.org/r/20250804164417.1612371-4-kees@kernel.org
Signed-off-by: Kees Cook <kees@kernel.org>

Kees Cook fca08b74 4452a0df

+6 -6
+6 -6
arch/x86/include/asm/bitops.h
··· 246 246 variable_test_bit(nr, addr); 247 247 } 248 248 249 - static __always_inline unsigned long variable__ffs(unsigned long word) 249 + static __always_inline __attribute_const__ unsigned long variable__ffs(unsigned long word) 250 250 { 251 251 asm("tzcnt %1,%0" 252 252 : "=r" (word) ··· 265 265 (unsigned long)__builtin_ctzl(word) : \ 266 266 variable__ffs(word)) 267 267 268 - static __always_inline unsigned long variable_ffz(unsigned long word) 268 + static __always_inline __attribute_const__ unsigned long variable_ffz(unsigned long word) 269 269 { 270 270 return variable__ffs(~word); 271 271 } ··· 287 287 * 288 288 * Undefined if no set bit exists, so code should check against 0 first. 289 289 */ 290 - static __always_inline unsigned long __fls(unsigned long word) 290 + static __always_inline __attribute_const__ unsigned long __fls(unsigned long word) 291 291 { 292 292 if (__builtin_constant_p(word)) 293 293 return BITS_PER_LONG - 1 - __builtin_clzl(word); ··· 301 301 #undef ADDR 302 302 303 303 #ifdef __KERNEL__ 304 - static __always_inline int variable_ffs(int x) 304 + static __always_inline __attribute_const__ int variable_ffs(int x) 305 305 { 306 306 int r; 307 307 ··· 355 355 * set bit if value is nonzero. The last (most significant) bit is 356 356 * at position 32. 357 357 */ 358 - static __always_inline int fls(unsigned int x) 358 + static __always_inline __attribute_const__ int fls(unsigned int x) 359 359 { 360 360 int r; 361 361 ··· 400 400 * at position 64. 401 401 */ 402 402 #ifdef CONFIG_X86_64 403 - static __always_inline int fls64(__u64 x) 403 + static __always_inline __attribute_const__ int fls64(__u64 x) 404 404 { 405 405 int bitpos = -1; 406 406