Re: [PATCH] locking/atomic: cmpxchg: Make __generic_cmpxchg_local compare against zero-extended 'old' value

From: Matt Evans
Date: Wed Mar 01 2023 - 10:19:53 EST


Hi Arnd,

> On 26 Feb 2023, at 10:13, Arnd Bergmann <arnd@xxxxxxxx> wrote:
>
> On Wed, Feb 1, 2023, at 19:39, Matt Evans wrote:
>> __generic_cmpxchg_local takes unsigned long old/new arguments which
>> might end up being up-cast from smaller signed types (which will
>> sign-extend). The loaded compare value must be compared against a
>> truncated smaller type, so down-cast appropriately for each size.
>>
>> The issue is apparent on 64-bit machines with code, such as
>> atomic_dec_unless_positive(), that sign-extends from int.
>>
>> 64-bit machines generally don't use the generic cmpxchg but
>> development/early ports might make use of it, so make it correct.
>>
>> Signed-off-by: Matt Evans <mev@xxxxxxxxxxxx>
>
> Hi Matt,
>
> I'm getting emails about nios2 sparse warnings from the
> kernel test robot about your patch. I can also reproduce
> this on armv5:
>
>
> fs/erofs/zdata.c: note: in included file (through /home/arnd/arm-soc/arch/arm/include/asm/cmpxchg.h, /home/arnd/arm-soc/arch/arm/include/asm/atomic.h, /home/arnd/arm-soc/include/linux/atomic.h, ...):
> include/asm-generic/cmpxchg-local.h:29:33: warning: cast truncates bits from constant value (5f0ecafe becomes fe)
> include/asm-generic/cmpxchg-local.h:33:34: warning: cast truncates bits from constant value (5f0ecafe becomes cafe)
> include/asm-generic/cmpxchg-local.h:29:33: warning: cast truncates bits from constant value (5f0ecafe becomes fe)
> include/asm-generic/cmpxchg-local.h:30:42: warning: cast truncates bits from constant value (5f0edead becomes ad)
> include/asm-generic/cmpxchg-local.h:33:34: warning: cast truncates bits from constant value (5f0ecafe becomes cafe)
> include/asm-generic/cmpxchg-local.h:34:44: warning: cast truncates bits from constant value (5f0edead becomes dead)
>
> This was already warning for the 'new' cast, but now also warns
> for the 'old' cast, so the bot thinks this is a new problem.

Thank you! Hmm, indeed, it’s “more of the same” warning-wise but your alternative is nicer.

> I managed to shut up the warning by using a binary '&' operator
> instead of the cast, but I wonder if it would be better to do
> also mask this in the caller, when arch_atomic_cmpxchg() with its
> signed argument calls into arch_cmpxchg() with its unsigned argument:

Proposed patch LGTM, but one query: are the casts in arch_[cmp]xchg()’s args necessary? The new masks should deal with the issue (and consistency would imply same for all other users of arch_cmpxchg(), not ideal).

> diff --git a/include/asm-generic/atomic.h b/include/asm-generic/atomic.h
> index 04b8be9f1a77..e271d6708c87 100644
> --- a/include/asm-generic/atomic.h
> +++ b/include/asm-generic/atomic.h
> @@ -130,7 +130,7 @@ ATOMIC_OP(xor, ^)
> #define arch_atomic_read(v) READ_ONCE((v)->counter)
> #define arch_atomic_set(v, i) WRITE_ONCE(((v)->counter), (i))
>
> -#define arch_atomic_xchg(ptr, v) (arch_xchg(&(ptr)->counter, (v)))
> -#define arch_atomic_cmpxchg(v, old, new) (arch_cmpxchg(&((v)->counter), (old), (new)))
> +#define arch_atomic_xchg(ptr, v) (arch_xchg(&(ptr)->counter, (u32)(v)))
> +#define arch_atomic_cmpxchg(v, old, new) (arch_cmpxchg(&((v)->counter), (u32)(old), (u32)(new)))
>
> #endif /* __ASM_GENERIC_ATOMIC_H */
> diff --git a/include/asm-generic/cmpxchg-local.h b/include/asm-generic/cmpxchg-local.h
> index c3e7315b7c1d..f9d52d1f0472 100644
> --- a/include/asm-generic/cmpxchg-local.h
> +++ b/include/asm-generic/cmpxchg-local.h
> @@ -26,20 +26,20 @@ static inline unsigned long __generic_cmpxchg_local(volatile void *ptr,
> raw_local_irq_save(flags);
> switch (size) {
> case 1: prev = *(u8 *)ptr;
> - if (prev == (u8)old)
> - *(u8 *)ptr = (u8)new;
> + if (prev == (old & 0xff))
> + *(u8 *)ptr = (new & 0xffu);
> break;
> case 2: prev = *(u16 *)ptr;
> - if (prev == (u16)old)
> - *(u16 *)ptr = (u16)new;
> + if (prev == (old & 0xffffu))
> + *(u16 *)ptr = (new & 0xffffu);
> break;
> case 4: prev = *(u32 *)ptr;
> - if (prev == (u32)old)
> - *(u32 *)ptr = (u32)new;
> + if (prev == (old & 0xffffffffu))
> + *(u32 *)ptr = (new & 0xffffffffu);
> break;
> case 8: prev = *(u64 *)ptr;
> if (prev == old)
> - *(u64 *)ptr = (u64)new;
> + *(u64 *)ptr = new;
> break;
> default:
> wrong_size_cmpxchg(ptr);

FWIW (including the casts in atomic.h, if you prefer):

Reviewed-by: Matt Evans <mev@xxxxxxxxxxxx>


Thanks,


Matt


>
>
> Arnd