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

Configure Feed

Select the types of activity you want to include in your feed.

cleanup: Basic compatibility with context analysis

Introduce basic compatibility with cleanup.h infrastructure.

We need to allow the compiler to see the acquisition and release of the
context lock at the start and end of a scope. However, the current
"cleanup" helpers wrap the lock in a struct passed through separate
helper functions, which hides the lock alias from the compiler (no
inter-procedural analysis).

While Clang supports scoped guards in C++, it's not possible to apply in
C code: https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#scoped-context

However, together with recent improvements to Clang's alias analysis
abilities, idioms such as this work correctly now:

void spin_unlock_cleanup(spinlock_t **l) __releases(*l) { .. }
...
{
spinlock_t *lock_scope __cleanup(spin_unlock_cleanup) = &lock;
spin_lock(&lock); // lock through &lock
... critical section ...
} // unlock through lock_scope -[alias]-> &lock (no warnings)

To generalize this pattern and make it work with existing lock guards,
introduce DECLARE_LOCK_GUARD_1_ATTRS() and WITH_LOCK_GUARD_1_ATTRS().

These allow creating an explicit alias to the context lock instance that
is "cleaned" up with a separate cleanup helper. This helper is a dummy
function that does nothing at runtime, but has the release attributes to
tell the compiler what happens at the end of the scope.

Example usage:

DECLARE_LOCK_GUARD_1_ATTRS(mutex, __acquires(_T), __releases(*(struct mutex **)_T))
#define class_mutex_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(mutex, _T)

Note: To support the for-loop based scoped helpers, the auxiliary
variable must be a pointer to the "class" type because it is defined in
the same statement as the guard variable. However, we initialize it with
the lock pointer (despite the type mismatch, the compiler's alias
analysis still works as expected). The "_unlock" attribute receives a
pointer to the auxiliary variable (a double pointer to the class type),
and must be cast and dereferenced appropriately.

Signed-off-by: Marco Elver <elver@google.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20251219154418.3592607-7-elver@google.com

authored by

Marco Elver and committed by
Peter Zijlstra
3931d4b9 25d3b21e

+50
+50
include/linux/cleanup.h
··· 278 278 279 279 #define DEFINE_CLASS(_name, _type, _exit, _init, _init_args...) \ 280 280 typedef _type class_##_name##_t; \ 281 + typedef _type lock_##_name##_t; \ 281 282 static __always_inline void class_##_name##_destructor(_type *p) \ 283 + __no_context_analysis \ 282 284 { _type _T = *p; _exit; } \ 283 285 static __always_inline _type class_##_name##_constructor(_init_args) \ 286 + __no_context_analysis \ 284 287 { _type t = _init; return t; } 285 288 286 289 #define EXTEND_CLASS(_name, ext, _init, _init_args...) \ 290 + typedef lock_##_name##_t lock_##_name##ext##_t; \ 287 291 typedef class_##_name##_t class_##_name##ext##_t; \ 288 292 static __always_inline void class_##_name##ext##_destructor(class_##_name##_t *p) \ 289 293 { class_##_name##_destructor(p); } \ 290 294 static __always_inline class_##_name##_t class_##_name##ext##_constructor(_init_args) \ 295 + __no_context_analysis \ 291 296 { class_##_name##_t t = _init; return t; } 292 297 293 298 #define CLASS(_name, var) \ ··· 479 474 */ 480 475 481 476 #define __DEFINE_UNLOCK_GUARD(_name, _type, _unlock, ...) \ 477 + typedef _type lock_##_name##_t; \ 482 478 typedef struct { \ 483 479 _type *lock; \ 484 480 __VA_ARGS__; \ 485 481 } class_##_name##_t; \ 486 482 \ 487 483 static __always_inline void class_##_name##_destructor(class_##_name##_t *_T) \ 484 + __no_context_analysis \ 488 485 { \ 489 486 if (!__GUARD_IS_ERR(_T->lock)) { _unlock; } \ 490 487 } \ ··· 495 488 496 489 #define __DEFINE_LOCK_GUARD_1(_name, _type, _lock) \ 497 490 static __always_inline class_##_name##_t class_##_name##_constructor(_type *l) \ 491 + __no_context_analysis \ 498 492 { \ 499 493 class_##_name##_t _t = { .lock = l }, *_T = &_t; \ 500 494 _lock; \ ··· 504 496 505 497 #define __DEFINE_LOCK_GUARD_0(_name, _lock) \ 506 498 static __always_inline class_##_name##_t class_##_name##_constructor(void) \ 499 + __no_context_analysis \ 507 500 { \ 508 501 class_##_name##_t _t = { .lock = (void*)1 }, \ 509 502 *_T __maybe_unused = &_t; \ 510 503 _lock; \ 511 504 return _t; \ 512 505 } 506 + 507 + #define DECLARE_LOCK_GUARD_0_ATTRS(_name, _lock, _unlock) \ 508 + static inline class_##_name##_t class_##_name##_constructor(void) _lock;\ 509 + static inline void class_##_name##_destructor(class_##_name##_t *_T) _unlock; 510 + 511 + /* 512 + * To support Context Analysis, we need to allow the compiler to see the 513 + * acquisition and release of the context lock. However, the "cleanup" helpers 514 + * wrap the lock in a struct passed through separate helper functions, which 515 + * hides the lock alias from the compiler (no inter-procedural analysis). 516 + * 517 + * To make it work, we introduce an explicit alias to the context lock instance 518 + * that is "cleaned" up with a separate cleanup helper. This helper is a dummy 519 + * function that does nothing at runtime, but has the "_unlock" attribute to 520 + * tell the compiler what happens at the end of the scope. 521 + * 522 + * To generalize the pattern, the WITH_LOCK_GUARD_1_ATTRS() macro should be used 523 + * to redefine the constructor, which then also creates the alias variable with 524 + * the right "cleanup" attribute, *after* DECLARE_LOCK_GUARD_1_ATTRS() has been 525 + * used. 526 + * 527 + * Example usage: 528 + * 529 + * DECLARE_LOCK_GUARD_1_ATTRS(mutex, __acquires(_T), __releases(*(struct mutex **)_T)) 530 + * #define class_mutex_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(mutex, _T) 531 + * 532 + * Note: To support the for-loop based scoped helpers, the auxiliary variable 533 + * must be a pointer to the "class" type because it is defined in the same 534 + * statement as the guard variable. However, we initialize it with the lock 535 + * pointer (despite the type mismatch, the compiler's alias analysis still works 536 + * as expected). The "_unlock" attribute receives a pointer to the auxiliary 537 + * variable (a double pointer to the class type), and must be cast and 538 + * dereferenced appropriately. 539 + */ 540 + #define DECLARE_LOCK_GUARD_1_ATTRS(_name, _lock, _unlock) \ 541 + static inline class_##_name##_t class_##_name##_constructor(lock_##_name##_t *_T) _lock;\ 542 + static __always_inline void __class_##_name##_cleanup_ctx(class_##_name##_t **_T) \ 543 + __no_context_analysis _unlock { } 544 + #define WITH_LOCK_GUARD_1_ATTRS(_name, _T) \ 545 + class_##_name##_constructor(_T), \ 546 + *__UNIQUE_ID(unlock) __cleanup(__class_##_name##_cleanup_ctx) = (void *)(unsigned long)(_T) 513 547 514 548 #define DEFINE_LOCK_GUARD_1(_name, _type, _lock, _unlock, ...) \ 515 549 __DEFINE_CLASS_IS_CONDITIONAL(_name, false); \