diff mbox series

riscv/atomic: Do proper sign extension also for unsigned in arch_cmpxchg

Message ID mvmed0k4prh.fsf@suse.de (mailing list archive)
State New
Headers show
Series riscv/atomic: Do proper sign extension also for unsigned in arch_cmpxchg | expand

Checks

Context Check Description
bjorn/pre-ci_am success Success
bjorn/build-rv32-defconfig success build-rv32-defconfig
bjorn/build-rv64-clang-allmodconfig success build-rv64-clang-allmodconfig
bjorn/build-rv64-gcc-allmodconfig success build-rv64-gcc-allmodconfig
bjorn/build-rv64-nommu-k210-defconfig success build-rv64-nommu-k210-defconfig
bjorn/build-rv64-nommu-k210-virt success build-rv64-nommu-k210-virt
bjorn/checkpatch success checkpatch
bjorn/dtb-warn-rv64 success dtb-warn-rv64
bjorn/header-inline success header-inline
bjorn/kdoc success kdoc
bjorn/module-param success module-param
bjorn/verify-fixes success verify-fixes
bjorn/verify-signedoff success verify-signedoff

Commit Message

Andreas Schwab Jan. 30, 2025, 9:25 a.m. UTC
Sign extend also an unsigned compare value to match what lr.w is doing.
Otherwise try_cmpxchg may spuriously return true when used on a u32 value
that has the sign bit set, as it happens often in inode_set_ctime_current.

Do this in three conversion steps.  The first conversion to long is needed
to avoid a -Wpointer-to-int-cast warning when arch_cmpxchg is used with a
pointer type.  Then convert to int and back to long to always sign extend
the 32-bit value to 64-bit.

Fixes: 6c58f25e6938 ("riscv/atomic: Fix sign extension for RV64I")
Signed-off-by: Andreas Schwab <schwab@suse.de>
---
 arch/riscv/include/asm/cmpxchg.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Comments

Alexandre Ghiti Jan. 30, 2025, 2:18 p.m. UTC | #1
Hi Andreas,

On 30/01/2025 10:25, Andreas Schwab wrote:
> Sign extend also an unsigned compare value to match what lr.w is doing.
> Otherwise try_cmpxchg may spuriously return true when used on a u32 value
> that has the sign bit set, as it happens often in inode_set_ctime_current.
>
> Do this in three conversion steps.  The first conversion to long is needed
> to avoid a -Wpointer-to-int-cast warning when arch_cmpxchg is used with a
> pointer type.  Then convert to int and back to long to always sign extend
> the 32-bit value to 64-bit.
>
> Fixes: 6c58f25e6938 ("riscv/atomic: Fix sign extension for RV64I")
> Signed-off-by: Andreas Schwab <schwab@suse.de>
> ---
>   arch/riscv/include/asm/cmpxchg.h | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/arch/riscv/include/asm/cmpxchg.h b/arch/riscv/include/asm/cmpxchg.h
> index 4cadc56220fe..427c41dde643 100644
> --- a/arch/riscv/include/asm/cmpxchg.h
> +++ b/arch/riscv/include/asm/cmpxchg.h
> @@ -231,7 +231,7 @@
>   		__arch_cmpxchg(".w", ".w" sc_sfx, ".w" cas_sfx,		\
>   			       sc_prepend, sc_append,			\
>   			       cas_prepend, cas_append,			\
> -			       __ret, __ptr, (long), __old, __new);	\
> +			       __ret, __ptr, (long)(int)(long), __old, __new);	\
>   		break;							\
>   	case 8:								\
>   		__arch_cmpxchg(".d", ".d" sc_sfx, ".d" cas_sfx,		\


That's a nice catch indeed. IIUC, we have the same issue here 
https://elixir.bootlin.com/linux/v6.13/source/arch/riscv/include/asm/futex.h#L89 
right?

hanks,

Alex
Andrew Jones Jan. 30, 2025, 2:40 p.m. UTC | #2
On Thu, Jan 30, 2025 at 10:25:38AM +0100, Andreas Schwab wrote:
> Sign extend also an unsigned compare value to match what lr.w is doing.
> Otherwise try_cmpxchg may spuriously return true when used on a u32 value
> that has the sign bit set, as it happens often in inode_set_ctime_current.
> 
> Do this in three conversion steps.  The first conversion to long is needed
> to avoid a -Wpointer-to-int-cast warning when arch_cmpxchg is used with a
> pointer type.  Then convert to int and back to long to always sign extend
> the 32-bit value to 64-bit.
> 
> Fixes: 6c58f25e6938 ("riscv/atomic: Fix sign extension for RV64I")
> Signed-off-by: Andreas Schwab <schwab@suse.de>
> ---
>  arch/riscv/include/asm/cmpxchg.h | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/arch/riscv/include/asm/cmpxchg.h b/arch/riscv/include/asm/cmpxchg.h
> index 4cadc56220fe..427c41dde643 100644
> --- a/arch/riscv/include/asm/cmpxchg.h
> +++ b/arch/riscv/include/asm/cmpxchg.h
> @@ -231,7 +231,7 @@
>  		__arch_cmpxchg(".w", ".w" sc_sfx, ".w" cas_sfx,		\
>  			       sc_prepend, sc_append,			\
>  			       cas_prepend, cas_append,			\
> -			       __ret, __ptr, (long), __old, __new);	\
> +			       __ret, __ptr, (long)(int)(long), __old, __new);	\
>  		break;							\
>  	case 8:								\
>  		__arch_cmpxchg(".d", ".d" sc_sfx, ".d" cas_sfx,		\
> -- 
> 2.48.1
> 
>

Reviewed-by: Andrew Jones <ajones@ventanamicro.com>
Andreas Schwab Jan. 30, 2025, 2:52 p.m. UTC | #3
On Jan 30 2025, Alexandre Ghiti wrote:

> That's a nice catch indeed. IIUC, we have the same issue here
> https://elixir.bootlin.com/linux/v6.13/source/arch/riscv/include/asm/futex.h#L89
> right?

Indeed, though it doesn't result in wrong code currently.  This is
because the compare value is passed unmodified as u32 to the asm and the
compiler keeps the value sign extended in registers.  That would break
if you would add a cast to long like in commit 6c58f25e6938 as that
would erroneously zero extend it.
Alexandre Ghiti Jan. 30, 2025, 3:23 p.m. UTC | #4
On 30/01/2025 15:52, Andreas Schwab wrote:
> On Jan 30 2025, Alexandre Ghiti wrote:
>
>> That's a nice catch indeed. IIUC, we have the same issue here
>> https://elixir.bootlin.com/linux/v6.13/source/arch/riscv/include/asm/futex.h#L89
>> right?
> Indeed, though it doesn't result in wrong code currently.  This is
> because the compare value is passed unmodified as u32 to the asm and the
> compiler keeps the value sign extended in registers.  That would break
> if you would add a cast to long like in commit 6c58f25e6938 as that
> would erroneously zero extend it.
>

This is the disassembly I get:

ffffffff800fc540 <futex_atomic_cmpxchg_inatomic>:

...

ffffffff800fc566:       1605a8af                lr.w.aqrl a7,(a1)
ffffffff800fc56a:       00c89563                bne 
a7,a2,ffffffff800fc574 <futex_atomic_cmpxchg_inatomic+0x3
4>
ffffffff800fc56e:       1ed5a52f                sc.w.aqrl a0,a3,(a1)

a2 is used as it is passed by the calling function, so we can't be sure 
a2 is sign extended to me, what am I missing?
Andreas Schwab Jan. 30, 2025, 3:53 p.m. UTC | #5
On Jan 30 2025, Alexandre Ghiti wrote:

> a2 is used as it is passed by the calling function, so we can't be sure a2
> is sign extended to me, what am I missing?

32-bit scalar arguments are guaranteed to be sign extended on entry.
Jessica Clarke Jan. 30, 2025, 5:21 p.m. UTC | #6
On 30 Jan 2025, at 15:53, Andreas Schwab <schwab@suse.de> wrote:
> 
> On Jan 30 2025, Alexandre Ghiti wrote:
> 
>> a2 is used as it is passed by the calling function, so we can't be sure a2
>> is sign extended to me, what am I missing?
> 
> 32-bit scalar arguments are guaranteed to be sign extended on entry.

Firstly, the calling convention is irrelevant if the function is
inlined, which this almost always will be.

Secondly, whilst GCC (still?) maintains the invariant that 32-bit
registers are kept sign-extended in registers on RISC-V, and there’s a
non-normative note in the unprivileged spec about this, LLVM does not,
any extra bits are wholly unspecified, so you must not write inline
assembly that depends on them being well-define, either by ignoring the
bits or by converting your arguments to be whole-register types. If
your compiler has already extended the value in the manner you care
about then that’ll be a no-op, and if it hasn’t then it’ll do what you
need.

We had an issue like this years ago in FreeBSD (also for a uint32_t
CAS) and this was the conclusion I reached [1].

Jess

[1] https://reviews.freebsd.org/D22084
David Laight Jan. 30, 2025, 9:35 p.m. UTC | #7
On Thu, 30 Jan 2025 10:25:38 +0100
Andreas Schwab <schwab@suse.de> wrote:

> Sign extend also an unsigned compare value to match what lr.w is doing.
> Otherwise try_cmpxchg may spuriously return true when used on a u32 value
> that has the sign bit set, as it happens often in inode_set_ctime_current.
> 
> Do this in three conversion steps.  The first conversion to long is needed
> to avoid a -Wpointer-to-int-cast warning when arch_cmpxchg is used with a
> pointer type.

Doesn't that break things by discarding the high bits of a pointer value?

	David

> Then convert to int and back to long to always sign extend
> the 32-bit value to 64-bit.
> 
> Fixes: 6c58f25e6938 ("riscv/atomic: Fix sign extension for RV64I")
> Signed-off-by: Andreas Schwab <schwab@suse.de>
> ---
>  arch/riscv/include/asm/cmpxchg.h | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/arch/riscv/include/asm/cmpxchg.h b/arch/riscv/include/asm/cmpxchg.h
> index 4cadc56220fe..427c41dde643 100644
> --- a/arch/riscv/include/asm/cmpxchg.h
> +++ b/arch/riscv/include/asm/cmpxchg.h
> @@ -231,7 +231,7 @@
>  		__arch_cmpxchg(".w", ".w" sc_sfx, ".w" cas_sfx,		\
>  			       sc_prepend, sc_append,			\
>  			       cas_prepend, cas_append,			\
> -			       __ret, __ptr, (long), __old, __new);	\
> +			       __ret, __ptr, (long)(int)(long), __old, __new);	\
>  		break;							\
>  	case 8:								\
>  		__arch_cmpxchg(".d", ".d" sc_sfx, ".d" cas_sfx,		\
Xi Ruoyao Feb. 1, 2025, 12:17 p.m. UTC | #8
On Thu, 2025-01-30 at 10:25 +0100, Andreas Schwab wrote:
> Sign extend also an unsigned compare value to match what lr.w is doing.
> Otherwise try_cmpxchg may spuriously return true when used on a u32 value
> that has the sign bit set, as it happens often in inode_set_ctime_current.
> 
> Do this in three conversion steps.  The first conversion to long is needed
> to avoid a -Wpointer-to-int-cast warning when arch_cmpxchg is used with a
> pointer type.  Then convert to int and back to long to always sign extend
> the 32-bit value to 64-bit.
> 
> Fixes: 6c58f25e6938 ("riscv/atomic: Fix sign extension for RV64I")
> Signed-off-by: Andreas Schwab <schwab@suse.de>

It fixes the gnulib test-stat-time failure for me.

Tested-by: Xi Ruoyao <xry111@xry111.site>

> ---
>  arch/riscv/include/asm/cmpxchg.h | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/arch/riscv/include/asm/cmpxchg.h b/arch/riscv/include/asm/cmpxchg.h
> index 4cadc56220fe..427c41dde643 100644
> --- a/arch/riscv/include/asm/cmpxchg.h
> +++ b/arch/riscv/include/asm/cmpxchg.h
> @@ -231,7 +231,7 @@
>  		__arch_cmpxchg(".w", ".w" sc_sfx, ".w" cas_sfx,		\
>  			       sc_prepend, sc_append,			\
>  			       cas_prepend, cas_append,			\
> -			       __ret, __ptr, (long), __old, __new);	\
> +			       __ret, __ptr, (long)(int)(long), __old, __new);	\
>  		break;							\
>  	case 8:								\
>  		__arch_cmpxchg(".d", ".d" sc_sfx, ".d" cas_sfx,		\
> -- 
> 2.48.1
Andreas Schwab Feb. 3, 2025, 8:42 a.m. UTC | #9
On Jan 30 2025, David Laight wrote:

> On Thu, 30 Jan 2025 10:25:38 +0100
> Andreas Schwab <schwab@suse.de> wrote:
>
>> Sign extend also an unsigned compare value to match what lr.w is doing.
>> Otherwise try_cmpxchg may spuriously return true when used on a u32 value
>> that has the sign bit set, as it happens often in inode_set_ctime_current.
>> 
>> Do this in three conversion steps.  The first conversion to long is needed
>> to avoid a -Wpointer-to-int-cast warning when arch_cmpxchg is used with a
>> pointer type.
>
> Doesn't that break things by discarding the high bits of a pointer value?

If you have 32-bit pointers then the conversions are no-ops.
Andreas Schwab Feb. 3, 2025, 8:42 a.m. UTC | #10
On Jan 30 2025, Jessica Clarke wrote:

> On 30 Jan 2025, at 15:53, Andreas Schwab <schwab@suse.de> wrote:
>> 
>> On Jan 30 2025, Alexandre Ghiti wrote:
>> 
>>> a2 is used as it is passed by the calling function, so we can't be sure a2
>>> is sign extended to me, what am I missing?
>> 
>> 32-bit scalar arguments are guaranteed to be sign extended on entry.
>
> Firstly, the calling convention is irrelevant if the function is
> inlined, which this almost always will be.

This is only about the non-inlined variant.
Maciej W. Rozycki Feb. 3, 2025, 1:57 p.m. UTC | #11
On Thu, 30 Jan 2025, Jessica Clarke wrote:

> >> a2 is used as it is passed by the calling function, so we can't be sure a2
> >> is sign extended to me, what am I missing?
> > 
> > 32-bit scalar arguments are guaranteed to be sign extended on entry.
> 
> Firstly, the calling convention is irrelevant if the function is
> inlined, which this almost always will be.

 Umm, that would be a compiler bug then, as inlining is supposed not to 
change language semantics.  IOW the compiler is expected to explicitly 
sign-extend the arguments of an inlined function at their evaluation point 
just as it would at an actual function call unless the compiler is able to 
prove they have come out sign-extended already from previous operations.

  Maciej
Jessica Clarke Feb. 3, 2025, 9:22 p.m. UTC | #12
On 3 Feb 2025, at 13:57, Maciej W. Rozycki <macro@orcam.me.uk> wrote:
> 
> On Thu, 30 Jan 2025, Jessica Clarke wrote:
> 
>>>> a2 is used as it is passed by the calling function, so we can't be sure a2
>>>> is sign extended to me, what am I missing?
>>> 
>>> 32-bit scalar arguments are guaranteed to be sign extended on entry.
>> 
>> Firstly, the calling convention is irrelevant if the function is
>> inlined, which this almost always will be.
> 
> Umm, that would be a compiler bug then, as inlining is supposed not to 
> change language semantics.  IOW the compiler is expected to explicitly 
> sign-extend the arguments of an inlined function at their evaluation point 
> just as it would at an actual function call unless the compiler is able to 
> prove they have come out sign-extended already from previous operations.

No it’s not. The calling convention is there so that each side of the
call know how the data is being passed between them. When inlining
occurs there is no such call and compilers can do whatever they like.
Calling conventions say absolutely nothing about what the
representation of a value is whilst inside a function, only at
boundaries. The ABI can also tell you what the observable
representation of values within a function should be (e.g. that int is
a 32-bit two’s complement value), but just like other platforms there
is nothing in the RISC-V ABI specifications to say that 32-bit integers
are kept sign-extended in registers, and that’s not going to be added
because LLVM has never implemented that for RISC-V so it would be an
ABI break for all LLVM-compiled RV64 code in existence.

Jess
Alexandre Ghiti Feb. 4, 2025, 8:33 a.m. UTC | #13
Hi Andreas,

On 30/01/2025 10:25, Andreas Schwab wrote:
> Sign extend also an unsigned compare value to match what lr.w is doing.
> Otherwise try_cmpxchg may spuriously return true when used on a u32 value
> that has the sign bit set, as it happens often in inode_set_ctime_current.
>
> Do this in three conversion steps.  The first conversion to long is needed
> to avoid a -Wpointer-to-int-cast warning when arch_cmpxchg is used with a
> pointer type.  Then convert to int and back to long to always sign extend
> the 32-bit value to 64-bit.
>
> Fixes: 6c58f25e6938 ("riscv/atomic: Fix sign extension for RV64I")
> Signed-off-by: Andreas Schwab <schwab@suse.de>
> ---
>   arch/riscv/include/asm/cmpxchg.h | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/arch/riscv/include/asm/cmpxchg.h b/arch/riscv/include/asm/cmpxchg.h
> index 4cadc56220fe..427c41dde643 100644
> --- a/arch/riscv/include/asm/cmpxchg.h
> +++ b/arch/riscv/include/asm/cmpxchg.h
> @@ -231,7 +231,7 @@
>   		__arch_cmpxchg(".w", ".w" sc_sfx, ".w" cas_sfx,		\
>   			       sc_prepend, sc_append,			\
>   			       cas_prepend, cas_append,			\
> -			       __ret, __ptr, (long), __old, __new);	\
> +			       __ret, __ptr, (long)(int)(long), __old, __new);	\
>   		break;							\
>   	case 8:								\
>   		__arch_cmpxchg(".d", ".d" sc_sfx, ".d" cas_sfx,		\


Reviewed-by: Alexandre Ghiti <alexghiti@rivosinc.com>

Thanks,

Alex
Maciej W. Rozycki Feb. 4, 2025, 4:44 p.m. UTC | #14
On Mon, 3 Feb 2025, Jessica Clarke wrote:

> > Umm, that would be a compiler bug then, as inlining is supposed not to 
> > change language semantics.  IOW the compiler is expected to explicitly 
> > sign-extend the arguments of an inlined function at their evaluation point 
> > just as it would at an actual function call unless the compiler is able to 
> > prove they have come out sign-extended already from previous operations.
> 
> No it’s not. The calling convention is there so that each side of the
> call know how the data is being passed between them. When inlining
> occurs there is no such call and compilers can do whatever they like.

 Right, I guess I got influenced too much by my MIPS experience here.

 The contract here is to pass a 32-bit integer as an argument through a 
function call boundary and with the RISC-V ISA saying the upper 32-bits of 
an input hardware register are don't-cares for 32-bit integer operations 
this contract is still met if inlining discards the sign-extension madated 
by the psABI.  And consequently a piece of inline assembly cannot expect 
an incoming 32-bit integer in a register to be correctly interpreted as a 
64-bit integer of equal value instead.

 Although I find it odd in these circumstances that the psABI mandates 
sign-extension here.  And honestly I think an explicit cast ought to be 
added to the relevant assembly input operands (or a temporary variable of 
the appropriate type added and used to convert between the incoming value 
and the input operands); it will be a no-op for an optimising compiler 
when it knows the value has been sign-extended already and will carry the 
semantics information for a human code reader even if sign-extension has 
been implicitly ensured by the psABI despite the data type itself not 
making it evident.

 Thanks for correcting me overall.

  Maciej
diff mbox series

Patch

diff --git a/arch/riscv/include/asm/cmpxchg.h b/arch/riscv/include/asm/cmpxchg.h
index 4cadc56220fe..427c41dde643 100644
--- a/arch/riscv/include/asm/cmpxchg.h
+++ b/arch/riscv/include/asm/cmpxchg.h
@@ -231,7 +231,7 @@ 
 		__arch_cmpxchg(".w", ".w" sc_sfx, ".w" cas_sfx,		\
 			       sc_prepend, sc_append,			\
 			       cas_prepend, cas_append,			\
-			       __ret, __ptr, (long), __old, __new);	\
+			       __ret, __ptr, (long)(int)(long), __old, __new);	\
 		break;							\
 	case 8:								\
 		__arch_cmpxchg(".d", ".d" sc_sfx, ".d" cas_sfx,		\