Message ID | 20241009114446.14873-1-przemyslaw.kitszel@intel.com (mailing list archive) |
---|---|
State | Changes Requested |
Headers | show |
Series | [v2] cleanup: adjust scoped_guard() to avoid potential warning | expand |
Context | Check | Description |
---|---|---|
netdev/tree_selection | success | Not a local patch |
On Wed, Oct 09, 2024 at 01:44:17PM +0200, Przemek Kitszel wrote: > Change scoped_guard() to make reasoning about it easier for static > analysis tools (smatch, compiler diagnostics), especially to enable them > to tell if the given scoped_guard() is conditional (interruptible-locks, > try-locks) or not (like simple mutex_lock()). > > Add compile-time error if scoped_cond_guard() is used for non-conditional > lock class. > > Beyond easier tooling and a little shrink reported by bloat-o-meter: > add/remove: 3/2 grow/shrink: 45/55 up/down: 1573/-2069 (-496) > this patch enables developer to write code like: > > int foo(struct my_drv *adapter) > { > scoped_guard(spinlock, &adapter->some_spinlock) > return adapter->spinlock_protected_var; > } > > Current scoped_guard() implementation does not support that, > due to compiler complaining: > error: control reaches end of non-void function [-Werror=return-type] > > Technical stuff about the change: > scoped_guard() macro uses common idiom of using "for" statement to declare > a scoped variable. Unfortunately, current logic is too hard for compiler > diagnostics to be sure that there is exactly one loop step; fix that. > > To make any loop so trivial that there is no above warning, it must not > depend on any non-const variable to tell if there are more steps. There is > no obvious solution for that in C, but one could use the compound > statement expression with "goto" jumping past the "loop", effectively > leaving only the subscope part of the loop semantics. > > More impl details: > one more level of macro indirection is now needed to avoid duplicating > label names; > I didn't spot any other place that is using the > "for (...; goto label) if (0) label: break;" idiom, so it's not packed > for reuse, what makes actual macros code cleaner. > > There was also a need to introduce const true/false variable per lock > class, it is used to aid compiler diagnostics reasoning about "exactly > 1 step" loops (note that converting that to function would undo the whole > benefit). > > Big thanks to Andy Shevchenko for help on this patch, both internal and > public, ranging from whitespace/formatting, through commit message > clarifications, general improvements, ending with presenting alternative > approaches - all despite not even liking the idea. > > Big thanks to Dmitry Torokhov for the idea of compile-time check for > scoped_cond_guard(), and general improvements for the patch. ... > @@ -149,14 +149,21 @@ static inline class_##_name##_t class_##_name##ext##_constructor(_init_args) \ > * similar to scoped_guard(), except it does fail when the lock > * acquire fails. > * > + * Only for conditional locks. > + * Slipped redundant blank line. > */ ... > +/* helper for the scoped_guard() macro /* * This is wrong style of the comment block, it's not network * related code where it's acceptable. Also, respect English, * i.e. capitalisation and punctuation in the sentences. */ > + * > + * Note that the "!__is_cond_ptr(_name)" part of the condition ensures > + * that compiler would be sure that for unconditional locks the body of > + * the loop could not be skipped; it is needed because the other > + * part - "__guard_ptr(_name)(&scope)" - is too hard to deduce (even if > + * could be proven true for unconditional locks). > + */
On 10/9/24 6:44 AM, Przemek Kitszel wrote: > Change scoped_guard() to make reasoning about it easier for static > analysis tools (smatch, compiler diagnostics), especially to enable them > to tell if the given scoped_guard() is conditional (interruptible-locks, > try-locks) or not (like simple mutex_lock()). > > Add compile-time error if scoped_cond_guard() is used for non-conditional > lock class. > > Beyond easier tooling and a little shrink reported by bloat-o-meter: > add/remove: 3/2 grow/shrink: 45/55 up/down: 1573/-2069 (-496) > this patch enables developer to write code like: > > int foo(struct my_drv *adapter) > { > scoped_guard(spinlock, &adapter->some_spinlock) > return adapter->spinlock_protected_var; > } > > > Current scoped_guard() implementation does not support that, > due to compiler complaining: > error: control reaches end of non-void function [-Werror=return-type] I was hoping that this would allow us to do the same with scoped_cond_guard() so that we could remove a bunch of unreachable(); that we had to add in the IIO subsystem. But with this patch we still get compile errors if we remove them. Is it possible to apply the same if/goto magic to scoped_cond_guard() to make it better too? --- Here is a test case if that helps... For example, I made this change: diff --git a/drivers/iio/adc/ad7380.c b/drivers/iio/adc/ad7380.c index e8bddfb0d07d..f1c85690af1e 100644 --- a/drivers/iio/adc/ad7380.c +++ b/drivers/iio/adc/ad7380.c @@ -577,7 +577,6 @@ static int ad7380_debugfs_reg_access(struct iio_dev *indio_dev, u32 reg, else return regmap_write(st->regmap, reg, writeval); } - unreachable(); } /* @@ -824,7 +823,6 @@ static int ad7380_read_raw(struct iio_dev *indio_dev, return ad7380_read_direct(st, chan->scan_index, scan_type, val); } - unreachable(); case IIO_CHAN_INFO_SCALE: /* * According to the datasheet, the LSB size is: @@ -933,7 +931,6 @@ static int ad7380_write_raw(struct iio_dev *indio_dev, FIELD_PREP(AD7380_CONFIG2_RESET, AD7380_CONFIG2_RESET_SOFT)); } - unreachable(); default: return -EINVAL; } And then I get the following compiler errors/warnings: /home/david/work/linux/drivers/iio/adc/ad7380.c: In function ‘ad7380_debugfs_reg_access’: /home/david/work/linux/drivers/iio/adc/ad7380.c:580:1: error: control reaches end of non-void function [-Werror=return-type] 580 | } | ^ In file included from /home/david/work/linux/include/linux/irqflags.h:17, from /home/david/work/linux/arch/arm/include/asm/bitops.h:28, from /home/david/work/linux/include/linux/bitops.h:68, from /home/david/work/linux/drivers/iio/adc/ad7380.c:20: /home/david/work/linux/drivers/iio/adc/ad7380.c: In function ‘ad7380_write_raw’: /home/david/work/linux/include/linux/cleanup.h:337:9: warning: this statement may fall through [-Wimplicit-fallthrough=] 337 | for (CLASS(_name, scope)(args), \ | ^~~ /home/david/work/linux/include/linux/iio/iio.h:689:9: note: in expansion of macro ‘scoped_cond_guard’ 689 | scoped_cond_guard(iio_claim_direct_try, fail, iio_dev) | ^~~~~~~~~~~~~~~~~ /home/david/work/linux/drivers/iio/adc/ad7380.c:910:17: note: in expansion of macro ‘iio_device_claim_direct_scoped’ 910 | iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/david/work/linux/drivers/iio/adc/ad7380.c:934:9: note: here 934 | default: | ^~~~~~~ /home/david/work/linux/drivers/iio/adc/ad7380.c: In function ‘ad7380_read_raw’: /home/david/work/linux/include/linux/cleanup.h:337:9: warning: this statement may fall through [-Wimplicit-fallthrough=] 337 | for (CLASS(_name, scope)(args), \ | ^~~ /home/david/work/linux/include/linux/iio/iio.h:689:9: note: in expansion of macro ‘scoped_cond_guard’ 689 | scoped_cond_guard(iio_claim_direct_try, fail, iio_dev) | ^~~~~~~~~~~~~~~~~ /home/david/work/linux/drivers/iio/adc/ad7380.c:822:17: note: in expansion of macro ‘iio_device_claim_direct_scoped’ 822 | iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /home/david/work/linux/drivers/iio/adc/ad7380.c:826:9: note: here 826 | case IIO_CHAN_INFO_SCALE: | ^~~~ Using compiler: $ arm-linux-gnueabi-gcc --version arm-linux-gnueabi-gcc (Ubuntu 13.2.0-23ubuntu4) 13.2.0
On 10/10/24 22:13, David Lechner wrote: > On 10/9/24 6:44 AM, Przemek Kitszel wrote: >> Change scoped_guard() to make reasoning about it easier for static >> analysis tools (smatch, compiler diagnostics), especially to enable them >> to tell if the given scoped_guard() is conditional (interruptible-locks, >> try-locks) or not (like simple mutex_lock()). >> >> Add compile-time error if scoped_cond_guard() is used for non-conditional >> lock class. >> >> Beyond easier tooling and a little shrink reported by bloat-o-meter: >> add/remove: 3/2 grow/shrink: 45/55 up/down: 1573/-2069 (-496) >> this patch enables developer to write code like: >> >> int foo(struct my_drv *adapter) >> { >> scoped_guard(spinlock, &adapter->some_spinlock) >> return adapter->spinlock_protected_var; >> } >>> >> Current scoped_guard() implementation does not support that, >> due to compiler complaining: >> error: control reaches end of non-void function [-Werror=return-type] > > I was hoping that this would allow us to do the same with > scoped_cond_guard() so that we could remove a bunch of > unreachable(); that we had to add in the IIO subsystem. But > with this patch we still get compile errors if we remove them. > > Is it possible to apply the same if/goto magic to scoped_cond_guard() > to make it better too? sure, will do I will also combine both macros __helper at the same time to reduce duplication
diff --git a/include/linux/cleanup.h b/include/linux/cleanup.h index a3d3e888cf1f..7cb733bc981e 100644 --- a/include/linux/cleanup.h +++ b/include/linux/cleanup.h @@ -149,14 +149,21 @@ static inline class_##_name##_t class_##_name##ext##_constructor(_init_args) \ * similar to scoped_guard(), except it does fail when the lock * acquire fails. * + * Only for conditional locks. + * */ +#define __DEFINE_CLASS_IS_CONDITIONAL(_name, _is_cond) \ +static __maybe_unused const bool class_##_name##_is_conditional = _is_cond + #define DEFINE_GUARD(_name, _type, _lock, _unlock) \ + __DEFINE_CLASS_IS_CONDITIONAL(_name, false); \ DEFINE_CLASS(_name, _type, if (_T) { _unlock; }, ({ _lock; _T; }), _type _T); \ static inline void * class_##_name##_lock_ptr(class_##_name##_t *_T) \ { return *_T; } #define DEFINE_GUARD_COND(_name, _ext, _condlock) \ + __DEFINE_CLASS_IS_CONDITIONAL(_name##_ext, true); \ EXTEND_CLASS(_name, _ext, \ ({ void *_t = _T; if (_T && !(_condlock)) _t = NULL; _t; }), \ class_##_name##_t _T) \ @@ -167,14 +174,33 @@ static inline class_##_name##_t class_##_name##ext##_constructor(_init_args) \ CLASS(_name, __UNIQUE_ID(guard)) #define __guard_ptr(_name) class_##_name##_lock_ptr +#define __is_cond_ptr(_name) class_##_name##_is_conditional + +/* helper for the scoped_guard() macro + * + * Note that the "!__is_cond_ptr(_name)" part of the condition ensures + * that compiler would be sure that for unconditional locks the body of + * the loop could not be skipped; it is needed because the other + * part - "__guard_ptr(_name)(&scope)" - is too hard to deduce (even if + * could be proven true for unconditional locks). + */ +#define __scoped_guard_labeled(_label, _name, args...) \ + for (CLASS(_name, scope)(args); \ + __guard_ptr(_name)(&scope) || !__is_cond_ptr(_name); \ + ({ goto _label; })) \ + if (0) { \ +_label: \ + break; \ + } else + +#define scoped_guard(_name, args...) \ + __scoped_guard_labeled(__UNIQUE_ID(label), _name, args) -#define scoped_guard(_name, args...) \ - for (CLASS(_name, scope)(args), \ - *done = NULL; __guard_ptr(_name)(&scope) && !done; done = (void *)1) #define scoped_cond_guard(_name, _fail, args...) \ for (CLASS(_name, scope)(args), \ - *done = NULL; !done; done = (void *)1) \ + *done = NULL; !done; done = (void *)1 + \ + BUILD_BUG_ON_ZERO(!__is_cond_ptr(_name))) \ if (!__guard_ptr(_name)(&scope)) _fail; \ else @@ -233,14 +259,17 @@ static inline class_##_name##_t class_##_name##_constructor(void) \ } #define DEFINE_LOCK_GUARD_1(_name, _type, _lock, _unlock, ...) \ +__DEFINE_CLASS_IS_CONDITIONAL(_name, false); \ __DEFINE_UNLOCK_GUARD(_name, _type, _unlock, __VA_ARGS__) \ __DEFINE_LOCK_GUARD_1(_name, _type, _lock) #define DEFINE_LOCK_GUARD_0(_name, _lock, _unlock, ...) \ +__DEFINE_CLASS_IS_CONDITIONAL(_name, false); \ __DEFINE_UNLOCK_GUARD(_name, void, _unlock, __VA_ARGS__) \ __DEFINE_LOCK_GUARD_0(_name, _lock) #define DEFINE_LOCK_GUARD_1_COND(_name, _ext, _condlock) \ + __DEFINE_CLASS_IS_CONDITIONAL(_name##_ext, true); \ EXTEND_CLASS(_name, _ext, \ ({ class_##_name##_t _t = { .lock = l }, *_T = &_t;\ if (_T->lock && !(_condlock)) _T->lock = NULL; \